From 460fef2f0e4f82b10c683878b000d71242d7b148 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 9 Jul 2021 00:39:24 -0700 Subject: [PATCH 01/73] Possibly fix building in a branch --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6a8036c..5c28a21 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,7 @@ lint: - mkdir -p /go/src/gitlab.com/signald/signald-go - cp -r * /go/src/gitlab.com/signald/signald-go script: - - git checkout "${CI_COMMIT_BRANCH}" + - git checkout "${CI_COMMIT_BRANCH}" || true - VERSION="$(git describe --abbrev=0 HEAD)-$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)-$(git rev-parse --short=8 HEAD)" - echo "building ${VERSION}" - cd /go/src/gitlab.com/signald/signald-go @@ -52,7 +52,7 @@ build:cross-compile: - mkdir -p /go/src/gitlab.com/signald/signald-go - cp -r * /go/src/gitlab.com/signald/signald-go script: - - git checkout "${CI_COMMIT_BRANCH}" + - git checkout "${CI_COMMIT_BRANCH}" || true - VERSION="$(git describe --abbrev=0 HEAD)-$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)-$(git rev-parse --short=8 HEAD)" - echo "building ${VERSION}" - cd /go/src/gitlab.com/signald/signald-go @@ -80,7 +80,7 @@ build:x86:deb: - apt-get install -y ./*.deb && rm -vf *.deb - "sed -i 's/^Architecture:.*/Architecture: amd64/g' debian/control" script: - - git checkout "${CI_COMMIT_BRANCH}" + - git checkout "${CI_COMMIT_BRANCH}" || true - go run ./cmd/signaldctl doc -o man - go run ./cmd/signaldctl completion bash > debian/package.bash-completion - ls *.1 > debian/manpages @@ -114,7 +114,7 @@ build:aarch64:deb: - apt-get install -y ./*.deb && rm -vf *.deb - "sed -i 's/^Architecture:.*/Architecture: arm64/g' debian/control" script: - - git checkout "${CI_COMMIT_BRANCH}" + - git checkout "${CI_COMMIT_BRANCH}" || true - go run ./cmd/signaldctl doc -o man - go run ./cmd/signaldctl completion bash > debian/package.bash-completion - ls *.1 > debian/manpages From 235ec55978959f9c3a427cb34c90d70f244565fe Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 9 Jul 2021 01:44:10 -0700 Subject: [PATCH 02/73] Standardize the deb build template --- .gitlab-ci.yml | 94 ++++++++++++++++++-------------------------------- 1 file changed, 34 insertions(+), 60 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5c28a21..d68881c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,6 +33,37 @@ lint: - signaldctl expire_in: 1 month +.build-deb: + stage: build + image: debian:latest + script: + - apt-get update + - apt-get install -y git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev + - apt-get install -y ./*.deb && rm -vf *.deb + - git checkout "${CI_COMMIT_BRANCH}" || true + - go run ./cmd/signaldctl doc -o man + - go run ./cmd/signaldctl completion bash > debian/package.bash-completion + - ls *.1 > debian/manpages + - gbp dch --ignore-branch --debian-tag="%(version)s" --git-author --new-version="$(git describe --abbrev=0 HEAD)+git$(date +%Y-%m-%d)r$(git rev-parse --short=8 HEAD).$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)" + - dpkg-buildpackage -us -uc -b + - mv ../*.deb . + needs: + - project: signald/libraries/golang-github-mdp-qrterminal + job: build + ref: master + artifacts: true + - project: signald/libraries/golang-github-jedib0t-go-pretty + job: build + ref: master + artifacts: true + variables: + SIGNALDCTL_PUBLIC_DOC_MODE: "on" + artifacts: + paths: + - "*.deb" + - "*.1" + expire_in: 1 month + build:x86: extends: .build @@ -72,72 +103,15 @@ build:cross-compile: GOARCH: amd64 build:x86:deb: - stage: build - image: debian:latest + extends: .build-deb before_script: - - apt-get update - - apt-get install -y git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev - - apt-get install -y ./*.deb && rm -vf *.deb - - "sed -i 's/^Architecture:.*/Architecture: amd64/g' debian/control" - script: - - git checkout "${CI_COMMIT_BRANCH}" || true - - go run ./cmd/signaldctl doc -o man - - go run ./cmd/signaldctl completion bash > debian/package.bash-completion - - ls *.1 > debian/manpages - - gbp dch --ignore-branch --debian-tag="%(version)s" --git-author --new-version="$(git describe --abbrev=0 HEAD)+git$(date +%Y-%m-%d)r$(git rev-parse --short=8 HEAD).$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)" - - dpkg-buildpackage -us -uc -b - - mv ../*.deb . - needs: - - project: signald/libraries/golang-github-mdp-qrterminal - job: build - ref: master - artifacts: true - - project: signald/libraries/golang-github-jedib0t-go-pretty - job: build - ref: master - artifacts: true - variables: - SIGNALDCTL_PUBLIC_DOC_MODE: "on" - artifacts: - paths: - - "*.deb" - - "*.1" - expire_in: 1 month + - "sed -i 's/^Architecture:.*/Architecture: amd64/g' debian/control" build:aarch64:deb: - stage: build - image: debian:latest + extends: .build-deb tags: [arm-builder] before_script: - - apt-get update - - apt-get install -y git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev - - apt-get install -y ./*.deb && rm -vf *.deb - "sed -i 's/^Architecture:.*/Architecture: arm64/g' debian/control" - script: - - git checkout "${CI_COMMIT_BRANCH}" || true - - go run ./cmd/signaldctl doc -o man - - go run ./cmd/signaldctl completion bash > debian/package.bash-completion - - ls *.1 > debian/manpages - - gbp dch --ignore-branch --debian-tag="%(version)s" --git-author --new-version="$(git describe --abbrev=0 HEAD)+git$(date +%Y-%m-%d)r$(git rev-parse --short=8 HEAD).$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)" - - dpkg-buildpackage -us -uc -b - - mv ../*.deb . - needs: - - project: signald/libraries/golang-github-mdp-qrterminal - job: build - ref: master - artifacts: true - - project: signald/libraries/golang-github-jedib0t-go-pretty - job: build - ref: master - artifacts: true - variables: - SIGNALDCTL_PUBLIC_DOC_MODE: "on" - artifacts: - paths: - - "*.deb" - - "*.1" - expire_in: 1 month - publish deb: image: registry.gitlab.com/signald/infrastructure/signald-builder-x86:d5e68709 From c5389eb6c24ccec8eb2a0459d3d406d418fde75f Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 9 Jul 2021 03:48:04 -0700 Subject: [PATCH 03/73] move some links from gitlab to signald.org --- .gitlab-ci.yml | 2 +- cmd/signaldctl/README.md | 2 +- cmd/signaldctl/cmd/account/register/register-account.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d68881c..dfe6927 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -111,7 +111,7 @@ build:aarch64:deb: extends: .build-deb tags: [arm-builder] before_script: - - "sed -i 's/^Architecture:.*/Architecture: arm64/g' debian/control" + - "sed -i 's/^Architecture:.*/Architecture: arm64/g' debian/control" publish deb: image: registry.gitlab.com/signald/infrastructure/signald-builder-x86:d5e68709 diff --git a/cmd/signaldctl/README.md b/cmd/signaldctl/README.md index d9e7541..9a4ac8e 100644 --- a/cmd/signaldctl/README.md +++ b/cmd/signaldctl/README.md @@ -3,7 +3,7 @@ # Install -If you have the [signald debian repo](https://gitlab.com/signald/signald/-/blob/main/docs/install/debian.md) installed: +If you have the [signald debian repo](https://docs.signald.org/articles/install/debian/) installed: ``` sudo apt install signaldctl diff --git a/cmd/signaldctl/cmd/account/register/register-account.go b/cmd/signaldctl/cmd/account/register/register-account.go index 868a3ec..d18477d 100644 --- a/cmd/signaldctl/cmd/account/register/register-account.go +++ b/cmd/signaldctl/cmd/account/register/register-account.go @@ -80,6 +80,6 @@ var ( func init() { RegisterAccountCmd.Flags().BoolVarP(&voice, "voice", "V", false, "request verification code be sent via an automated voice call (code is sent via SMS by default)") - RegisterAccountCmd.Flags().StringVarP(&captcha, "captcha", "c", "", "a captcha token may be required to register, see https://gitlab.com/signald/signald/-/wikis/Captchas for how to get one") + RegisterAccountCmd.Flags().StringVarP(&captcha, "captcha", "c", "", "a captcha token may be required to register, see https://docs.signald.org/articles/captcha/ for how to get one") RegisterAccountCmd.Flags().BoolVarP(&testing, "testing", "t", false, "use the Signal testing server") } From 15e3bd7298b772b11886b21a405f429b397a123d Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 9 Jul 2021 13:26:35 -0700 Subject: [PATCH 04/73] build armhf debs --- .gitlab-ci.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dfe6927..6870afd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -113,11 +113,18 @@ build:aarch64:deb: before_script: - "sed -i 's/^Architecture:.*/Architecture: arm64/g' debian/control" +build:armhf:deb: + extends: .build-deb + image: arm32v7/debian:latest + tags: [arm-builder] + before_script: + - "sed -i 's/^Architecture:.*/Architecture: armhf/g' debian/control" + publish deb: image: registry.gitlab.com/signald/infrastructure/signald-builder-x86:d5e68709 stage: publish tags: [deb-signer] - needs: ["build:x86:deb", "build:aarch64:deb"] + needs: ["build:x86:deb", "build:aarch64:deb", "build:armhf:deb"] script: - aptly repo create signald - aptly mirror create -ignore-signatures backfill-mirror https://updates.signald.org "${DISTRIBUTION}" main From 7ed55584919e339748bac99c6ee434be9665c222 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 9 Jul 2021 18:57:38 -0700 Subject: [PATCH 05/73] pull after checkout --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6870afd..6b36cf0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,7 @@ lint: - mkdir -p /go/src/gitlab.com/signald/signald-go - cp -r * /go/src/gitlab.com/signald/signald-go script: - - git checkout "${CI_COMMIT_BRANCH}" || true + - (git checkout "${CI_COMMIT_BRANCH}" && git pull)|| true - VERSION="$(git describe --abbrev=0 HEAD)-$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)-$(git rev-parse --short=8 HEAD)" - echo "building ${VERSION}" - cd /go/src/gitlab.com/signald/signald-go @@ -40,7 +40,7 @@ lint: - apt-get update - apt-get install -y git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev - apt-get install -y ./*.deb && rm -vf *.deb - - git checkout "${CI_COMMIT_BRANCH}" || true + - (git checkout "${CI_COMMIT_BRANCH}" && git pull)|| true - go run ./cmd/signaldctl doc -o man - go run ./cmd/signaldctl completion bash > debian/package.bash-completion - ls *.1 > debian/manpages @@ -83,7 +83,7 @@ build:cross-compile: - mkdir -p /go/src/gitlab.com/signald/signald-go - cp -r * /go/src/gitlab.com/signald/signald-go script: - - git checkout "${CI_COMMIT_BRANCH}" || true + - (git checkout "${CI_COMMIT_BRANCH}" && git pull)|| true - VERSION="$(git describe --abbrev=0 HEAD)-$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)-$(git rev-parse --short=8 HEAD)" - echo "building ${VERSION}" - cd /go/src/gitlab.com/signald/signald-go From 033bb59c763db5234863db47b6efcad7439e7428 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 16 Jul 2021 10:49:14 -0700 Subject: [PATCH 06/73] add flag to attach files to outbound messages --- cmd/signaldctl/cmd/message/send/send-message.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cmd/signaldctl/cmd/message/send/send-message.go b/cmd/signaldctl/cmd/message/send/send-message.go index 2e9b60f..b0cd39d 100644 --- a/cmd/signaldctl/cmd/message/send/send-message.go +++ b/cmd/signaldctl/cmd/message/send/send-message.go @@ -20,6 +20,7 @@ import ( "fmt" "log" "os" + "path/filepath" "strings" "github.com/jedib0t/go-pretty/v6/table" @@ -28,11 +29,13 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/common" "gitlab.com/signald/signald-go/cmd/signaldctl/config" + v0 "gitlab.com/signald/signald-go/signald/client-protocol/v0" "gitlab.com/signald/signald-go/signald/client-protocol/v1" ) var ( - account string + account string + attachments []string SendMessageCmd = &cobra.Command{ Use: "send ", @@ -56,6 +59,7 @@ var ( req := v1.SendRequest{ Username: account, MessageBody: strings.Join(args[1:], " "), + Attachments: []*v0.JsonAttachment{}, } if strings.HasPrefix(args[0], "+") { @@ -64,6 +68,15 @@ var ( req.RecipientGroupID = args[0] } + for _, attachment := range attachments { + path, err := filepath.Abs(attachment) + if err != nil { + log.Fatal("error resolving attachment", err) + } + log.Println(path) + req.Attachments = append(req.Attachments, &v0.JsonAttachment{Filename: path}) + } + resp, err := req.Submit(common.Signald) if err != nil { log.Fatal("error sending request to signald: ", err) @@ -121,4 +134,5 @@ var ( func init() { SendMessageCmd.Flags().StringVarP(&account, "account", "a", "", "local account to use") + SendMessageCmd.Flags().StringSliceVarP(&attachments, "attachment", "A", []string{}, "attach a file to your outbound message. may be specified multiple times.") } From fcae0fc903eb4cd89b31d628deda215ac24e4a89 Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 22 Jul 2021 21:42:55 -0700 Subject: [PATCH 07/73] Add session reset command --- cmd/signaldctl/cmd/root.go | 2 + cmd/signaldctl/cmd/session/root.go | 30 +++++++ cmd/signaldctl/cmd/session/session.go | 122 ++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 cmd/signaldctl/cmd/session/root.go create mode 100644 cmd/signaldctl/cmd/session/session.go diff --git a/cmd/signaldctl/cmd/root.go b/cmd/signaldctl/cmd/root.go index e6e5c36..ac78c5b 100644 --- a/cmd/signaldctl/cmd/root.go +++ b/cmd/signaldctl/cmd/root.go @@ -28,6 +28,7 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/key" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/message" + "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/session" "gitlab.com/signald/signald-go/cmd/signaldctl/common" "gitlab.com/signald/signald-go/cmd/signaldctl/config" "gitlab.com/signald/signald-go/signald" @@ -83,6 +84,7 @@ func init() { RootCmd.AddCommand(group.GroupCmd) RootCmd.AddCommand(key.KeyCmd) RootCmd.AddCommand(message.MessageCmd) + RootCmd.AddCommand(session.SessionCmd) } // initConfig reads in config file and ENV variables if set. diff --git a/cmd/signaldctl/cmd/session/root.go b/cmd/signaldctl/cmd/session/root.go new file mode 100644 index 0000000..29673b5 --- /dev/null +++ b/cmd/signaldctl/cmd/session/root.go @@ -0,0 +1,30 @@ +// Copyright © 2021 Finn Herzfeld +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package session + + +import ( + "github.com/spf13/cobra" +) + +var SessionCmd = &cobra.Command{ + Use: "session", + Aliases: []string{"sessions"}, +} + +func init() { + SessionCmd.AddCommand(ResetCmd) +} diff --git a/cmd/signaldctl/cmd/session/session.go b/cmd/signaldctl/cmd/session/session.go new file mode 100644 index 0000000..e0b035b --- /dev/null +++ b/cmd/signaldctl/cmd/session/session.go @@ -0,0 +1,122 @@ +// Copyright © 2021 Finn Herzfeld +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package session + +import ( + "encoding/json" + "fmt" + "log" + "os" + "strings" + + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" + + "gitlab.com/signald/signald-go/cmd/signaldctl/common" + "gitlab.com/signald/signald-go/cmd/signaldctl/config" + "gitlab.com/signald/signald-go/signald/client-protocol/v1" +) + +var ( + account string + + ResetCmd = &cobra.Command{ + Use: "reset ", + Short: "reset secure session with another Signal user", + PreRun: func(cmd *cobra.Command, args []string) { + if account == "" { + account = config.Config.DefaultAccount + } + if account == "" { + common.Must(cmd.Help()) + log.Fatal("No account specified. Please specify with --account or set a default") + } + if len(args) == 0 { + common.Must(cmd.Help()) + log.Fatal("must specify a phone number or UUID to reset session with") + } + }, + Run: func(_ *cobra.Command, args []string) { + go common.Signald.Listen(nil) + + req := v1.ResetSessionRequest{Account: account} + + if strings.HasPrefix(args[0], "+") { + req.Address = &v1.JsonAddress{Number: args[0]} + } else { + req.Address = &v1.JsonAddress{UUID: args[0]} + } + + resp, err := req.Submit(common.Signald) + if err != nil { + log.Fatal("error sending request to signald: ", err) + } + + switch common.OutputFormat { + case common.OutputFormatJSON: + err := json.NewEncoder(os.Stdout).Encode(resp) + if err != nil { + log.Fatal(err, "error encoding response to stdout") + } + case common.OutputFormatYAML: + err := yaml.NewEncoder(os.Stdout).Encode(resp) + if err != nil { + log.Fatal(err, "error encoding response to stdout") + } + case common.OutputFormatCSV, common.OutputFormatTable, common.OutputFormatDefault: + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"Number", "UUID", "Duration", "Send Error"}) + for _, result := range resp.Results { + if result.Success != nil { + t.AppendRow(table.Row{ + result.Address.Number, + result.Address.UUID, + fmt.Sprintf("%dms", result.Success.Duration), + "", + }) + } else { + var sendError string + if result.IdentityFailure != "" { + sendError = fmt.Sprintf("identity failure: %s\n", result.IdentityFailure) + } + if result.NetworkFailure { + sendError = "network failure" + } + if result.UnregisteredFailure { + sendError = "user not registered" + } + t.AppendRow(table.Row{result.Address.Number, result.Address.UUID, "", sendError}) + } + } + + if common.OutputFormat == common.OutputFormatCSV { + t.RenderCSV() + } else { + common.StylizeTable(t) + t.Render() + } + default: + log.Fatal("Unsupported output format") + } + }, + } +) + +func init() { + ResetCmd.Flags().StringVarP(&account, "account", "a", "", "local account to use") +} From cf0e4a8b85d69956641d5c0aa6eafe95a7b598a3 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 23 Jul 2021 16:54:53 -0700 Subject: [PATCH 08/73] add key trust-all long overdue --- cmd/signaldctl/cmd/key/list/list-keys.go | 12 +- cmd/signaldctl/cmd/key/root.go | 2 + cmd/signaldctl/cmd/key/trust_all/trust-all.go | 148 ++++++++++++++++++ cmd/signaldctl/common/signald.go | 1 + 4 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 cmd/signaldctl/cmd/key/trust_all/trust-all.go diff --git a/cmd/signaldctl/cmd/key/list/list-keys.go b/cmd/signaldctl/cmd/key/list/list-keys.go index 30f5104..b20ca26 100644 --- a/cmd/signaldctl/cmd/key/list/list-keys.go +++ b/cmd/signaldctl/cmd/key/list/list-keys.go @@ -67,19 +67,19 @@ func getOne() { req := v1.GetIdentitiesRequest{Account: account, Address: address} resp, err := req.Submit(common.Signald) if err != nil { - log.Fatal(err, "error communicating with signald") + log.Fatal(err) } switch common.OutputFormat { case common.OutputFormatJSON: err := json.NewEncoder(os.Stdout).Encode(resp) if err != nil { - log.Fatal(err, "error encoding response to stdout") + log.Fatal("error encoding response to stdout:", err) } case common.OutputFormatYAML: err := yaml.NewEncoder(os.Stdout).Encode(resp) if err != nil { - log.Fatal(err, "error encoding response to stdout") + log.Fatal("error encoding response to stdout:", err) } case common.OutputFormatCSV, common.OutputFormatTable, common.OutputFormatDefault: t := table.NewWriter() @@ -103,19 +103,19 @@ func getAll() { req := v1.GetAllIdentities{Account: account} resp, err := req.Submit(common.Signald) if err != nil { - log.Fatal(err, "error communicating with signald") + log.Fatal(err) } switch common.OutputFormat { case common.OutputFormatJSON: err := json.NewEncoder(os.Stdout).Encode(resp) if err != nil { - log.Fatal(err, "error encoding response to stdout") + log.Fatal("error encoding response to stdout:", err) } case common.OutputFormatYAML: err := yaml.NewEncoder(os.Stdout).Encode(resp) if err != nil { - log.Fatal(err, "error encoding response to stdout") + log.Fatal("error encoding response to stdout:", err) } case common.OutputFormatCSV, common.OutputFormatTable, common.OutputFormatDefault: t := table.NewWriter() diff --git a/cmd/signaldctl/cmd/key/root.go b/cmd/signaldctl/cmd/key/root.go index f18109a..287b664 100644 --- a/cmd/signaldctl/cmd/key/root.go +++ b/cmd/signaldctl/cmd/key/root.go @@ -21,6 +21,7 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/key/list" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/key/qr" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/key/trust" + "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/key/trust_all" ) var KeyCmd = &cobra.Command{ @@ -32,4 +33,5 @@ func init() { KeyCmd.AddCommand(list.ListKeysCmd) KeyCmd.AddCommand(qr.QRKeyCmd) KeyCmd.AddCommand(trust.TrustKeyCmd) + KeyCmd.AddCommand(trust_all.TrustAllCmd) } diff --git a/cmd/signaldctl/cmd/key/trust_all/trust-all.go b/cmd/signaldctl/cmd/key/trust_all/trust-all.go new file mode 100644 index 0000000..7619f01 --- /dev/null +++ b/cmd/signaldctl/cmd/key/trust_all/trust-all.go @@ -0,0 +1,148 @@ +// Copyright © 2021 Finn Herzfeld +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package trust_all + +import ( + "encoding/json" + "log" + "os" + + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" + + "gitlab.com/signald/signald-go/cmd/signaldctl/common" + "gitlab.com/signald/signald-go/cmd/signaldctl/config" + "gitlab.com/signald/signald-go/signald/client-protocol/v1" +) + +var ( + account string + addresses []v1.JsonAddress + TrustAllCmd = &cobra.Command{ + Use: "trust-all []", + Short: "mark all keys as trusted, optionally limiting by phone number or UUID", + PreRun: func(cmd *cobra.Command, args []string) { + if account == "" { + account = config.Config.DefaultAccount + } + if account == "" { + common.Must(cmd.Help()) + log.Fatal("No account specified. Please specify with --account or set a default") + } + for _, address := range args { + addresses = append(addresses, common.StringToAddress(address)) + } + }, + Run: func(_ *cobra.Command, _ []string) { + go common.Signald.Listen(nil) + changed := []v1.IdentityKeyList{} + if len(addresses) > 0 { + for _, address := range addresses { + identitiesReq := v1.GetIdentitiesRequest{Account: account, Address: &address} + identities, err := identitiesReq.Submit(common.Signald) + if err != nil { + log.Fatal(err) + } + + changedIdentities := []*v1.IdentityKey{} + + for _, identityKey := range identities.Identities { + if identityKey.TrustLevel == "UNTRUSTED" { + trustReq := v1.TrustRequest{Account: account, Address: &address, SafetyNumber: identityKey.SafetyNumber} + err = trustReq.Submit(common.Signald) + if err != nil { + log.Fatal(err) + } + changedIdentities = append(changedIdentities, identityKey) + } + } + if len(changedIdentities) > 0 { + changed = append(changed, v1.IdentityKeyList{ + Address: &address, + Identities: changedIdentities, + }) + } + } + } else { + identitiesReq := v1.GetAllIdentities{Account: account} + resp, err := identitiesReq.Submit(common.Signald) + if err != nil { + log.Fatal(err) + } + for _, user := range resp.IdentityKeys { + changedIdentities := []*v1.IdentityKey{} + for _, identityKey := range user.Identities { + if identityKey.TrustLevel == "UNTRUSTED" { + trustReq := v1.TrustRequest{ + Account: account, + Address: user.Address, + SafetyNumber: identityKey.SafetyNumber, + TrustLevel: "TRUST_UNVERIFIED", + } + err = trustReq.Submit(common.Signald) + if err != nil { + log.Fatal(err) + } + changedIdentities = append(changedIdentities, identityKey) + } + } + if len(changedIdentities) > 0 { + changed = append(changed, v1.IdentityKeyList{ + Address: user.Address, + Identities: changedIdentities, + }) + } + } + } + switch common.OutputFormat { + case common.OutputFormatJSON: + err := json.NewEncoder(os.Stdout).Encode(changed) + if err != nil { + log.Fatal("error encoding response to stdout:", err) + } + case common.OutputFormatYAML: + err := yaml.NewEncoder(os.Stdout).Encode(changed) + if err != nil { + log.Fatal("error encoding response to stdout:", err) + } + case common.OutputFormatCSV, common.OutputFormatTable: + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"Number", "UUID", "Safety Number"}) + for _, user := range changed { + for _, identity := range user.Identities { + t.AppendRow(table.Row{user.Address.Number, user.Address.UUID, identity.SafetyNumber}) + } + } + if common.OutputFormat == common.OutputFormatCSV { + t.RenderCSV() + } else { + common.StylizeTable(t) + t.Render() + } + case common.OutputFormatQuiet, common.OutputFormatDefault: + return + default: + log.Fatal("Unsupported output format") + } + }, + } +) + +func init() { + TrustAllCmd.Flags().StringVarP(&account, "account", "a", "", "the signald account to use") +} diff --git a/cmd/signaldctl/common/signald.go b/cmd/signaldctl/common/signald.go index f47b075..8ffc145 100644 --- a/cmd/signaldctl/common/signald.go +++ b/cmd/signaldctl/common/signald.go @@ -19,6 +19,7 @@ const ( OutputFormatQR = "qr" OutputFormatMarkdown = "md" OutputFormatMan = "man" + OutputFormatQuiet = "quiet" AnnotationNoSocketConnection = "no-socket" ) From c3b79ebe98d13e1665a7c655a6013d1657c53339 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 23 Jul 2021 17:36:10 -0700 Subject: [PATCH 09/73] trust-all sets trust level to TRUSTED_UNVERIFIED --- cmd/signaldctl/cmd/key/trust_all/trust-all.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/signaldctl/cmd/key/trust_all/trust-all.go b/cmd/signaldctl/cmd/key/trust_all/trust-all.go index 7619f01..8c6e025 100644 --- a/cmd/signaldctl/cmd/key/trust_all/trust-all.go +++ b/cmd/signaldctl/cmd/key/trust_all/trust-all.go @@ -91,7 +91,7 @@ var ( Account: account, Address: user.Address, SafetyNumber: identityKey.SafetyNumber, - TrustLevel: "TRUST_UNVERIFIED", + TrustLevel: "TRUSTED_UNVERIFIED", } err = trustReq.Submit(common.Signald) if err != nil { From 0d80576e8ad5d3c1eaf1cec2e28d231ec080c144 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 23 Jul 2021 18:38:05 -0700 Subject: [PATCH 10/73] Update some links --- .gitlab-ci.yml | 2 +- README.md | 2 +- cmd/signaldctl/README.md | 4 ++-- cmd/signaldctl/cmd/key/trust_all/trust-all.go | 7 ++++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6b36cf0..f81a94c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -138,7 +138,7 @@ publish deb: - main - tags -docs.signald.org: +signald.org: stage: publish needs: ["build:x86"] trigger: signald/signald.org diff --git a/README.md b/README.md index 6fe8c06..72ad137 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # signald-go -*a golang library for communicating with [signald](https://gitlab.org/signald/signald)* +*a golang library for communicating with [signald](https://signald.org)* for signaldctl, see [signaldctl README](cmd/signaldctl/README.md) \ No newline at end of file diff --git a/cmd/signaldctl/README.md b/cmd/signaldctl/README.md index 9a4ac8e..24a7475 100644 --- a/cmd/signaldctl/README.md +++ b/cmd/signaldctl/README.md @@ -3,7 +3,7 @@ # Install -If you have the [signald debian repo](https://docs.signald.org/articles/install/debian/) installed: +If you have the [signald debian repo](https://signald.org/articles/install/debian/) installed: ``` sudo apt install signaldctl @@ -23,4 +23,4 @@ go get gitlab.com/signald/signald-go/cmd/signaldctl # Use -[see in progress documentation site](https://signaldctl.signald.org/) \ No newline at end of file +[see in progress documentation site](https://signald.org/signaldctl/) \ No newline at end of file diff --git a/cmd/signaldctl/cmd/key/trust_all/trust-all.go b/cmd/signaldctl/cmd/key/trust_all/trust-all.go index 8c6e025..3eb10a2 100644 --- a/cmd/signaldctl/cmd/key/trust_all/trust-all.go +++ b/cmd/signaldctl/cmd/key/trust_all/trust-all.go @@ -62,7 +62,12 @@ var ( for _, identityKey := range identities.Identities { if identityKey.TrustLevel == "UNTRUSTED" { - trustReq := v1.TrustRequest{Account: account, Address: &address, SafetyNumber: identityKey.SafetyNumber} + trustReq := v1.TrustRequest{ + Account: account, + Address: &address, + SafetyNumber: identityKey.SafetyNumber, + TrustLevel: "TRUSTED_UNVERIFIED", + } err = trustReq.Submit(common.Signald) if err != nil { log.Fatal(err) From 2c74bb163ca21e9cac573ce2bb417c52faff16b2 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 30 Jul 2021 11:36:26 -0700 Subject: [PATCH 11/73] fix up CI to run in 3rd party MR pipelines --- .gitlab-ci.yml | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f81a94c..5e00288 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,6 +15,8 @@ lint: - go mod tidy - diff --color=always go.mod "${CI_PROJECT_DIR}/go.mod" - diff --color=always go.sum "${CI_PROJECT_DIR}/go.sum" + rules: + - when: on_success .build: stage: build @@ -63,9 +65,18 @@ lint: - "*.deb" - "*.1" expire_in: 1 month + rules: + - if: '$CI_PROJECT_NAMESPACE == "signald" && $CI_COMMIT_REF_PROTECTED == "true"' + when: on_success + - when: manual + allow_failure: true build:x86: extends: .build + rules: + - when: on_success + rules: + - when: on_success build:aarch64: tags: [arm-builder] @@ -101,11 +112,16 @@ build:cross-compile: - amd64 - GOOS: darwin GOARCH: amd64 + rules: + - when: on_success + build:x86:deb: extends: .build-deb before_script: - "sed -i 's/^Architecture:.*/Architecture: amd64/g' debian/control" + rules: + - when: on_success build:aarch64:deb: extends: .build-deb @@ -137,10 +153,17 @@ publish deb: only: - main - tags + rules: + - if: '$CI_PROJECT_NAMESPACE == "signald" && $CI_COMMIT_REF_PROTECTED == "true"' + when: on_success + - when: manual + allow_failure: true + signald.org: stage: publish needs: ["build:x86"] trigger: signald/signald.org - only: - - main \ No newline at end of file + rules: + - if: '$CI_PROJECT_NAMESPACE == "signald" && $CI_COMMIT_REF_PROTECTED == "true"' + when: on_success \ No newline at end of file From a5b4d01e10f1b14e100292d401e188215de45038 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 30 Jul 2021 11:40:20 -0700 Subject: [PATCH 12/73] fix another minor CI thing --- .gitlab-ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5e00288..0b35d73 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -150,9 +150,6 @@ publish deb: - aptly publish repo -config=.aptly.conf -batch -gpg-key="${SIGNING_KEY}" -distribution="${DISTRIBUTION}" "signald" "s3:updates.signald.org:" variables: DISTRIBUTION: unstable - only: - - main - - tags rules: - if: '$CI_PROJECT_NAMESPACE == "signald" && $CI_COMMIT_REF_PROTECTED == "true"' when: on_success From 86f23e72c97db4eb2ea349a58698b89423e557f3 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 30 Jul 2021 11:52:57 -0700 Subject: [PATCH 13/73] build x86 deb doesnt work in MR pipelines either --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0b35d73..7c73a43 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -120,8 +120,6 @@ build:x86:deb: extends: .build-deb before_script: - "sed -i 's/^Architecture:.*/Architecture: amd64/g' debian/control" - rules: - - when: on_success build:aarch64:deb: extends: .build-deb From f443b0dd48ea438c17b2789d7abde6c0f18ac536 Mon Sep 17 00:00:00 2001 From: Sebastian Haas Date: Fri, 30 Jul 2021 19:56:38 +0200 Subject: [PATCH 14/73] Fix index out of range during shell completion validate number of parameters to prevent panic see https://github.com/spf13/cobra/blob/master/shell_completions.md --- cmd/signaldctl/cmd/completion.go | 49 ++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/cmd/signaldctl/cmd/completion.go b/cmd/signaldctl/cmd/completion.go index d4d284b..b162cbd 100644 --- a/cmd/signaldctl/cmd/completion.go +++ b/cmd/signaldctl/cmd/completion.go @@ -30,50 +30,55 @@ var completionCmd = &cobra.Command{ Bash: -$ source <(signaldctl completion bash) + $ source <(signaldctl completion bash) -# To load completions for each session, execute once: -Linux: - $ signaldctl completion bash > /etc/bash_completion.d/signaldctl -MacOS: - $ signaldctl completion bash > /usr/local/etc/bash_completion.d/signaldctl + # To load completions for each session, execute once: + # Linux: + $ signaldctl completion bash > /etc/bash_completion.d/signaldctl + # macOS: + $ signaldctl completion bash > /usr/local/etc/bash_completion.d/signaldctl Zsh: -# If shell completion is not already enabled in your environment you will need -# to enable it. You can execute the following once: + # If shell completion is not already enabled in your environment, + # you will need to enable it. You can execute the following once: -$ echo "autoload -U compinit; compinit" >> ~/.zshrc + $ echo "autoload -U compinit; compinit" >> ~/.zshrc -# To load completions for each session, execute once: -$ signaldctl completion zsh > "${fpath[1]}/_signaldctl" + # To load completions for each session, execute once: + $ signaldctl completion zsh > "${fpath[1]}/_signaldctl" -# You will need to start a new shell for this setup to take effect. + # You will need to start a new shell for this setup to take effect. -Fish: +fish: -$ signaldctl completion fish | source + $ signaldctl completion fish | source -# To load completions for each session, execute once: -$ signaldctl completion fish > ~/.config/fish/completions/signaldctl.fish + # To load completions for each session, execute once: + $ signaldctl completion fish > ~/.config/fish/completions/signaldctl.fish -Powershell: +PowerShell: -PS> signaldctl completion powershell | Out-String | Invoke-Expression + PS> signaldctl completion powershell | Out-String | Invoke-Expression -# To load completions for every new session, run: -PS> signaldctl completion powershell > signaldctl.ps1 -# and source this file from your powershell profile. + # To load completions for every new session, run: + PS> signaldctl completion powershell > signaldctl.ps1 + # and source this file from your PowerShell profile. `, DisableFlagsInUseLine: true, Annotations: map[string]string{common.AnnotationNoSocketConnection: "true"}, - ValidArgs: []string{"bash", "zsh"}, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.ExactValidArgs(1), Run: func(cmd *cobra.Command, args []string) { switch args[0] { case "bash": common.Must(cmd.Root().GenBashCompletion(os.Stdout)) case "zsh": common.Must(cmd.Root().GenZshCompletion(os.Stdout)) + case "fish": + common.Must(cmd.Root().GenFishCompletion(os.Stdout, true)) + case "powershell": + common.Must(cmd.Root().GenPowerShellCompletion(os.Stdout)) } }, } From 8567e37af954a9dd933108e3d6a10a58e41e3cff Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 30 Jul 2021 15:54:38 -0700 Subject: [PATCH 15/73] Install dependencies from backports when building signaldctl debian packages --- .gitlab-ci.yml | 7 +- go.mod | 4 +- go.sum | 201 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 178 insertions(+), 34 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7c73a43..493a1c5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,8 +39,9 @@ lint: stage: build image: debian:latest script: + - echo deb http://deb.debian.org/debian buster-backports main > /etc/apt/sources.list.d/backports.list - apt-get update - - apt-get install -y git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev + - apt-get install -y -t buster-backports git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev - apt-get install -y ./*.deb && rm -vf *.deb - (git checkout "${CI_COMMIT_BRANCH}" && git pull)|| true - go run ./cmd/signaldctl doc -o man @@ -75,8 +76,6 @@ build:x86: extends: .build rules: - when: on_success - rules: - - when: on_success build:aarch64: tags: [arm-builder] @@ -120,6 +119,8 @@ build:x86:deb: extends: .build-deb before_script: - "sed -i 's/^Architecture:.*/Architecture: amd64/g' debian/control" + rules: + - when: on_success build:aarch64:deb: extends: .build-deb diff --git a/go.mod b/go.mod index 1819461..6cc870b 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,10 @@ require ( github.com/mattn/go-runewidth v0.0.10 // indirect github.com/mdp/qrterminal v1.0.1 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/spf13/cobra v1.0.0 + github.com/spf13/cobra v1.1.2 github.com/stretchr/testify v1.6.1 // indirect golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect - gopkg.in/yaml.v2 v2.3.0 + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect ) diff --git a/go.sum b/go.sum index b5b15ab..b61d002 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,33 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= @@ -22,68 +38,110 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jedib0t/go-pretty/v6 v6.1.0 h1:NVS2PT3ZvzMb47DzS50cmsK6xkf8SSyLfroSSIG20JI= github.com/jedib0t/go-pretty/v6 v6.1.0/go.mod h1:+nE9fyyHGil+PuISTCrp7avEdo6bqoMwqZnuiK2r2a0= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c= github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -96,82 +154,167 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/cobra v1.1.2 h1:frHO75w/dH7kEc+e2KYZZKY4+PLrp39OqI77oB8m0KQ= +github.com/spf13/cobra v1.1.2/go.mod h1:ZjwqWkCg0LnXvLRIfTLdB4Y/MCO3gMHHJ2KFxQZy4xE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= From 0a0bcd5e93d1a4c72c4325888cc27854f38f5df7 Mon Sep 17 00:00:00 2001 From: Finn Date: Sat, 7 Aug 2021 12:46:18 -0700 Subject: [PATCH 16/73] Try to fix the debian version tags --- .gitlab-ci.yml | 13 +++++++------ Makefile | 2 +- version.sh | 2 ++ 3 files changed, 10 insertions(+), 7 deletions(-) create mode 100755 version.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 493a1c5..cd1ab35 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,8 +25,8 @@ lint: - mkdir -p /go/src/gitlab.com/signald/signald-go - cp -r * /go/src/gitlab.com/signald/signald-go script: - - (git checkout "${CI_COMMIT_BRANCH}" && git pull)|| true - - VERSION="$(git describe --abbrev=0 HEAD)-$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)-$(git rev-parse --short=8 HEAD)" + - (git checkout "${CI_COMMIT_BRANCH}" && git pull) || true + - VERSION="$(./version.sh)" - echo "building ${VERSION}" - cd /go/src/gitlab.com/signald/signald-go - go build -o "${CI_PROJECT_DIR}/signaldctl" --ldflags "-X gitlab.com/signald/signald-go/cmd/signaldctl/common.Version=${VERSION} -X gitlab.com/signald/signald-go/cmd/signaldctl/common.Branch=${CI_COMMIT_BRANCH} -X gitlab.com/signald/signald-go/cmd/signaldctl/common.Commit=${CI_COMMIT_SHA}" ./cmd/signaldctl @@ -43,11 +43,11 @@ lint: - apt-get update - apt-get install -y -t buster-backports git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev - apt-get install -y ./*.deb && rm -vf *.deb - - (git checkout "${CI_COMMIT_BRANCH}" && git pull)|| true + - (git checkout "${CI_COMMIT_BRANCH}" && git pull) || true - go run ./cmd/signaldctl doc -o man - go run ./cmd/signaldctl completion bash > debian/package.bash-completion - ls *.1 > debian/manpages - - gbp dch --ignore-branch --debian-tag="%(version)s" --git-author --new-version="$(git describe --abbrev=0 HEAD)+git$(date +%Y-%m-%d)r$(git rev-parse --short=8 HEAD).$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)" + - gbp dch --ignore-branch --debian-tag="%(version)s" --git-author --new-version="$(./version.sh)" - dpkg-buildpackage -us -uc -b - mv ../*.deb . needs: @@ -94,7 +94,7 @@ build:cross-compile: - cp -r * /go/src/gitlab.com/signald/signald-go script: - (git checkout "${CI_COMMIT_BRANCH}" && git pull)|| true - - VERSION="$(git describe --abbrev=0 HEAD)-$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)-$(git rev-parse --short=8 HEAD)" + - VERSION="$(./version.sh)" - echo "building ${VERSION}" - cd /go/src/gitlab.com/signald/signald-go - go build -o "${CI_PROJECT_DIR}/signaldctl-${GOOS}-${GOARCH}" --ldflags "-X gitlab.com/signald/signald-go/cmd/signaldctl/common.Version=${VERSION} -X gitlab.com/signald/signald-go/cmd/signaldctl/common.Branch=${CI_COMMIT_BRANCH} -X gitlab.com/signald/signald-go/cmd/signaldctl/common.Commit=${CI_COMMIT_SHA}" ./cmd/signaldctl @@ -162,4 +162,5 @@ signald.org: trigger: signald/signald.org rules: - if: '$CI_PROJECT_NAMESPACE == "signald" && $CI_COMMIT_REF_PROTECTED == "true"' - when: on_success \ No newline at end of file + when: on_success + \ No newline at end of file diff --git a/Makefile b/Makefile index 84f33d5..824a46c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ signaldctl: signald/client-protocol cmd/signaldctl - go build -ldflags '-X gitlab.com/signald/signald-go/cmd/signaldctl/common.Version=$(shell git describe) -X gitlab.com/signald/signald-go/cmd/signaldctl/common.Branch=$(shell git rev-parse --abbrev-ref HEAD) -X gitlab.com/signald/signald-go/cmd/signaldctl/common.Commit=$(shell git rev-parse HEAD)' -o signaldctl ./cmd/signaldctl + go build -ldflags '-X gitlab.com/signald/signald-go/cmd/signaldctl/common.Version=$(./version.sh) -X gitlab.com/signald/signald-go/cmd/signaldctl/common.Branch=$(shell git rev-parse --abbrev-ref HEAD) -X gitlab.com/signald/signald-go/cmd/signaldctl/common.Commit=$(shell git rev-parse HEAD)' -o signaldctl ./cmd/signaldctl protocol.json: signald --dump-protocol | jq . > protocol.json diff --git a/version.sh b/version.sh new file mode 100755 index 0000000..ff69408 --- /dev/null +++ b/version.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo $(git describe --abbrev=0 HEAD)-$(git rev-list $(git describe --abbrev=0 HEAD)..HEAD --count)-$(git rev-parse --short=8 HEAD) \ No newline at end of file From 5b98b6b13635533987d453caf71bff30331d6f8e Mon Sep 17 00:00:00 2001 From: Finn Date: Sat, 7 Aug 2021 13:16:49 -0700 Subject: [PATCH 17/73] Refactor deb build and remove git pull before build --- .gitlab-ci.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cd1ab35..196dafd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,7 +25,6 @@ lint: - mkdir -p /go/src/gitlab.com/signald/signald-go - cp -r * /go/src/gitlab.com/signald/signald-go script: - - (git checkout "${CI_COMMIT_BRANCH}" && git pull) || true - VERSION="$(./version.sh)" - echo "building ${VERSION}" - cd /go/src/gitlab.com/signald/signald-go @@ -43,7 +42,7 @@ lint: - apt-get update - apt-get install -y -t buster-backports git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev - apt-get install -y ./*.deb && rm -vf *.deb - - (git checkout "${CI_COMMIT_BRANCH}" && git pull) || true + - 'sed -i "s/^Architecture:.*/Architecture: ${ARCH}/g" debian/control' - go run ./cmd/signaldctl doc -o man - go run ./cmd/signaldctl completion bash > debian/package.bash-completion - ls *.1 > debian/manpages @@ -93,7 +92,6 @@ build:cross-compile: - mkdir -p /go/src/gitlab.com/signald/signald-go - cp -r * /go/src/gitlab.com/signald/signald-go script: - - (git checkout "${CI_COMMIT_BRANCH}" && git pull)|| true - VERSION="$(./version.sh)" - echo "building ${VERSION}" - cd /go/src/gitlab.com/signald/signald-go @@ -117,23 +115,23 @@ build:cross-compile: build:x86:deb: extends: .build-deb - before_script: - - "sed -i 's/^Architecture:.*/Architecture: amd64/g' debian/control" rules: - when: on_success + variables: + ARCH: amd64 build:aarch64:deb: extends: .build-deb tags: [arm-builder] - before_script: - - "sed -i 's/^Architecture:.*/Architecture: arm64/g' debian/control" + variables: + ARCH: arm64 build:armhf:deb: extends: .build-deb image: arm32v7/debian:latest tags: [arm-builder] - before_script: - - "sed -i 's/^Architecture:.*/Architecture: armhf/g' debian/control" + variables: + ARCH: armhf publish deb: image: registry.gitlab.com/signald/infrastructure/signald-builder-x86:d5e68709 @@ -162,5 +160,4 @@ signald.org: trigger: signald/signald.org rules: - if: '$CI_PROJECT_NAMESPACE == "signald" && $CI_COMMIT_REF_PROTECTED == "true"' - when: on_success - \ No newline at end of file + when: on_success \ No newline at end of file From 474897ea5330e5ce4dcfe1d75304317e93f56090 Mon Sep 17 00:00:00 2001 From: Finn Date: Sat, 7 Aug 2021 13:43:56 -0700 Subject: [PATCH 18/73] Fix version compiled into debian signaldctl --- debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index 5295052..9dae672 100755 --- a/debian/rules +++ b/debian/rules @@ -3,7 +3,7 @@ export DH_GOLANG_EXCLUDES := tools/ PKG := gitlab.com/signald/signald-go -GO_LDFLAGS += -X $(PKG)/cmd/signaldctl/common.Version=$(DEB_VERSION) +GO_LDFLAGS += -X $(PKG)/cmd/signaldctl/common.Version=$(./version.sh) GO_LDFLAGS += -X $(PKG)/cmd/signaldctl/common.Branch=$(shell debian/get-branch.sh) GO_LDFLAGS += -X $(PKG)/cmd/signaldctl/common.Commit=$(CI_BUILD_REF) From 1cdfd16f11f3ba7c3d95a77cf483f5e7bd044988 Mon Sep 17 00:00:00 2001 From: Finn Date: Wed, 25 Aug 2021 17:00:23 -0700 Subject: [PATCH 19/73] force debian builds to buster --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 196dafd..57c97cb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,7 +36,7 @@ lint: .build-deb: stage: build - image: debian:latest + image: debian:buster script: - echo deb http://deb.debian.org/debian buster-backports main > /etc/apt/sources.list.d/backports.list - apt-get update @@ -128,7 +128,7 @@ build:aarch64:deb: build:armhf:deb: extends: .build-deb - image: arm32v7/debian:latest + image: arm32v7/debian:buster tags: [arm-builder] variables: ARCH: armhf From ec3d46fcf36bb1fb27246d1305b6c5a03e80bb60 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Thu, 26 Aug 2021 21:10:02 +0000 Subject: [PATCH 20/73] Add account set-profile command --- cmd/signaldctl/cmd/account/root.go | 2 + .../cmd/account/setprofile/set-profile.go | 64 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 cmd/signaldctl/cmd/account/setprofile/set-profile.go diff --git a/cmd/signaldctl/cmd/account/root.go b/cmd/signaldctl/cmd/account/root.go index adf9cc1..ac812c0 100644 --- a/cmd/signaldctl/cmd/account/root.go +++ b/cmd/signaldctl/cmd/account/root.go @@ -22,6 +22,7 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/link" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/list" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/register" + "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/setprofile" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/verify" ) @@ -36,4 +37,5 @@ func init() { AccountCmd.AddCommand(list.ListAccountCmd) AccountCmd.AddCommand(register.RegisterAccountCmd) AccountCmd.AddCommand(verify.VerifyAccountCmd) + AccountCmd.AddCommand(setprofile.SetProfileCmd) } diff --git a/cmd/signaldctl/cmd/account/setprofile/set-profile.go b/cmd/signaldctl/cmd/account/setprofile/set-profile.go new file mode 100644 index 0000000..a1c646a --- /dev/null +++ b/cmd/signaldctl/cmd/account/setprofile/set-profile.go @@ -0,0 +1,64 @@ +// Copyright © 2021 The signald-go Contributors. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package setprofile + +import ( + "log" + + "github.com/spf13/cobra" + + "gitlab.com/signald/signald-go/cmd/signaldctl/common" + "gitlab.com/signald/signald-go/cmd/signaldctl/config" + "gitlab.com/signald/signald-go/signald/client-protocol/v1" +) + +var ( + account string + + SetProfileCmd = &cobra.Command{ + Use: "set-profile name", + Short: "updates the profile data with a new name", + PreRun: func(cmd *cobra.Command, args []string) { + if account == "" { + account = config.Config.DefaultAccount + } + if account == "" { + common.Must(cmd.Help()) + log.Fatal("No account specified. Please specify with --account or set a default") + } + if len(args) != 1 { + common.Must(cmd.Help()) + log.Fatal("must specify a name") + } + }, + Run: func(_ *cobra.Command, args []string) { + go common.Signald.Listen(nil) + req := v1.SetProfile{ + Account: account, + Name: args[0], + } + err := req.Submit(common.Signald) + if err != nil { + log.Fatal("error from signald: ", err) + } + log.Println("profile set") + }, + } +) + +func init() { + SetProfileCmd.Flags().StringVarP(&account, "account", "a", "", "the signald account to use") +} From 096152394a2c4bf71fad980a24cdf949792d840a Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 27 Aug 2021 02:54:09 -0700 Subject: [PATCH 21/73] Update protocol and add support for remote-config to signaldctl --- .../cmd/account/remoteconfig/show.go | 82 +++++++++++ cmd/signaldctl/cmd/account/root.go | 2 + protocol.json | 137 +++++++++++++++--- signald/client-protocol/v1/requests.go | 68 ++++++++- signald/client-protocol/v1/structs.go | 71 ++++++--- 5 files changed, 320 insertions(+), 40 deletions(-) create mode 100644 cmd/signaldctl/cmd/account/remoteconfig/show.go diff --git a/cmd/signaldctl/cmd/account/remoteconfig/show.go b/cmd/signaldctl/cmd/account/remoteconfig/show.go new file mode 100644 index 0000000..c569dac --- /dev/null +++ b/cmd/signaldctl/cmd/account/remoteconfig/show.go @@ -0,0 +1,82 @@ +// Copyright © 2021 Finn Herzfeld +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +package remoteconfig + +import ( + "encoding/json" + "log" + "os" + + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" + + "gitlab.com/signald/signald-go/cmd/signaldctl/common" + "gitlab.com/signald/signald-go/cmd/signaldctl/config" + v1 "gitlab.com/signald/signald-go/signald/client-protocol/v1" +) + +var ( + account string + RemoteConfigCmd = &cobra.Command{ + Use: "remote-config", + Short: "return a list of accounts", + PreRun: func(cmd *cobra.Command, args []string) { + if account == "" { + account = config.Config.DefaultAccount + } + }, + Run: func(_ *cobra.Command, _ []string) { + go common.Signald.Listen(nil) + req := &v1.RemoteConfigRequest{Account: account} + remoteconfig, err := req.Submit(common.Signald) + if err != nil { + log.Fatal(err) + } + + switch common.OutputFormat { + case common.OutputFormatJSON: + err := json.NewEncoder(os.Stdout).Encode(remoteconfig.Config) + if err != nil { + log.Fatal("error encoding response to stdout:", err) + } + case common.OutputFormatYAML: + err := yaml.NewEncoder(os.Stdout).Encode(remoteconfig.Config) + if err != nil { + log.Fatal("error encoding response to stdout:", err) + } + case common.OutputFormatCSV, common.OutputFormatTable, common.OutputFormatDefault: + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"Key", "Value"}) + for _, config := range remoteconfig.Config { + t.AppendRow(table.Row{config.Name, config.Value}) + } + if common.OutputFormat == common.OutputFormatCSV { + t.RenderCSV() + } else { + common.StylizeTable(t) + t.Render() + } + default: + log.Fatal("Unsupported output format") + } + }, + } +) + +func init() { + RemoteConfigCmd.Flags().StringVarP(&account, "account", "a", "", "the signald account to use") +} diff --git a/cmd/signaldctl/cmd/account/root.go b/cmd/signaldctl/cmd/account/root.go index ac812c0..2888868 100644 --- a/cmd/signaldctl/cmd/account/root.go +++ b/cmd/signaldctl/cmd/account/root.go @@ -22,6 +22,7 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/link" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/list" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/register" + "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/remoteconfig" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/setprofile" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/verify" ) @@ -38,4 +39,5 @@ func init() { AccountCmd.AddCommand(register.RegisterAccountCmd) AccountCmd.AddCommand(verify.VerifyAccountCmd) AccountCmd.AddCommand(setprofile.SetProfileCmd) + AccountCmd.AddCommand(remoteconfig.RemoteConfigCmd) } diff --git a/protocol.json b/protocol.json index 5a84d62..f416485 100644 --- a/protocol.json +++ b/protocol.json @@ -2,9 +2,9 @@ "doc_version": "v1", "version": { "name": "signald", - "version": "0.13.1+git2021-07-09rabe585d6.42", + "version": "0.14.1+git2021-08-27r6dd96a17.28", "branch": "main", - "commit": "abe585d68fdb00b440c1b0c517e1ece2bc095ba3" + "commit": "6dd96a17c921766b0d284165e571476744ac2829" }, "info": "This document describes objects that may be used when communicating with signald.", "types": { @@ -256,7 +256,7 @@ }, "version": { "type": "String", - "example": "\"0.13.1+git2021-07-09rabe585d6.42\"" + "example": "\"0.14.1+git2021-08-27r6dd96a17.28\"" }, "branch": { "type": "String", @@ -264,7 +264,7 @@ }, "commit": { "type": "String", - "example": "\"abe585d68fdb00b440c1b0c517e1ece2bc095ba3\"" + "example": "\"6dd96a17c921766b0d284165e571476744ac2829\"" } } }, @@ -439,19 +439,26 @@ "type": "String", "example": "\"Parkdale Run Club\"" }, + "description": { + "type": "String", + "example": "\"A club for running in Parkdale\"" + }, "memberCount": { "type": "int", "example": "3" }, "addFromInviteLink": { - "type": "int" + "type": "int", + "doc": "The access level required in order to join the group from the invite link, as an AccessControl.AccessRequired enum from the upstream Signal groups.proto file. This is UNSATISFIABLE (4) when the group link is disabled; ADMINISTRATOR (3) when the group link is enabled, but an administrator must approve new members; and ANY (1) when the group link is enabled and no approval is required. See theGroupAccessControl structure and the upstream enum ordinals." }, "revision": { "type": "int", + "doc": "The Group V2 revision. This is incremented by clients whenever they update group information, and it is often used by clients to determine if the local group state is out-of-date with the server's revision.", "example": "5" }, "pendingAdminApproval": { - "type": "boolean" + "type": "boolean", + "doc": "Whether the account is waiting for admin approval in order to be added to the group." } } }, @@ -490,6 +497,11 @@ "type": "String", "example": "\"Parkdale Run Club\"" }, + "description": { + "type": "String", + "doc": "A new group description. Set to empty string to remove an existing description.", + "example": "\"A club for running in Parkdale\"" + }, "avatar": { "type": "String", "example": "\"/tmp/image.jpg\"" @@ -522,7 +534,7 @@ "doc": "regenerate the group link password, invalidating the old one" } }, - "doc": "modify a group. Note that only one modification action may be preformed at once" + "doc": "modify a group. Note that only one modification action may be performed at once" }, "GroupInfo": { "fields": { @@ -553,14 +565,20 @@ }, "avatarFile": { "type": "String", - "doc": "Path to new profile avatar file, if the avatar should be updated", + "doc": "Path to new profile avatar file. If unset or null, unset the profile avatar", "example": "\"/tmp/image.jpg\"" }, "about": { - "type": "String" + "type": "String", + "doc": "an optional about string. If unset, null or an empty string will unset profile about field" }, "emoji": { - "type": "String" + "type": "String", + "doc": "an optional single emoji character. If unset, null or an empty string will unset profile emoji" + }, + "mobilecoin_address": { + "type": "String", + "doc": "an optional *base64-encoded* MobileCoin address to set in the profile. Note that this is not the traditional MobileCoin address encoding, which is custom. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. If unset, null or an empty string, will empty the profile payment address" } } }, @@ -680,6 +698,10 @@ }, "expiration_time": { "type": "int" + }, + "mobilecoin_address": { + "type": "String", + "doc": "*base64-encoded* mobilecoin address. Note that this is not the traditional MobileCoin address encoding. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. If unset, null or an empty string, will empty the profile payment address" } }, "doc": "Information about a Signal user" @@ -1221,6 +1243,51 @@ } } }, + "SendPaymentRequest": { + "fields": { + "account": { + "type": "String", + "doc": "the account to use", + "example": "\"+12024561414\"", + "required": true + }, + "address": { + "type": "JsonAddress", + "version": "v1", + "doc": "the address to send the payment message to", + "required": true + }, + "payment": { + "type": "Payment", + "version": "v1", + "required": true + }, + "when": { + "type": "Long" + } + }, + "doc": "send a mobilecoin payment" + }, + "RemoteConfigRequest": { + "fields": { + "account": { + "type": "String", + "doc": "The account to use to retrieve the remote config", + "example": "\"+12024561414\"", + "required": true + } + }, + "doc": "Retrieves the remote config (feature flags) from the server." + }, + "RemoteConfigList": { + "fields": { + "config": { + "list": true, + "type": "RemoteConfig", + "version": "v1" + } + } + }, "JsonDataMessage": { "fields": { "timestamp": { @@ -1667,13 +1734,6 @@ }, "doc": "a Signal server" }, - "RemoteDelete": { - "fields": { - "target_sent_timestamp": { - "type": "long" - } - } - }, "Payment": { "fields": { "receipt": { @@ -1687,6 +1747,28 @@ }, "doc": "details about a MobileCoin payment" }, + "RemoteConfig": { + "fields": { + "name": { + "type": "String", + "doc": "The name of this remote config entry. These names may be prefixed with the platform type (\"android.\", \"ios.\", \"desktop.\", etc.) Typically, clients only handle the relevant configs for its platform, hardcoding the names it cares about handling and ignoring the rest.", + "example": "desktop.mediaQuality.levels" + }, + "value": { + "type": "String", + "doc": "The value for this remote config entry. Even though this is a string, it could be a boolean as a string, an integer/long value, a comma-delimited list, etc. Clients usually consume this by hardcoding the feature flagsit should track in the app and assuming that the server will send the type that the client expects. If an unexpected type occurs, it falls back to a default value.", + "example": "1:2,61:2,81:2,82:2,65:2,31:2,47:2,41:2,32:2,385:2,971:2,974:2,49:2,33:2,*:1" + } + }, + "doc": "A remote config (feature flag) entry." + }, + "RemoteDelete": { + "fields": { + "target_sent_timestamp": { + "type": "long" + } + } + }, "JsonSentTranscriptMessage": { "fields": { "destination": { @@ -1846,7 +1928,14 @@ } }, "ServerCDN": { - "fields": {} + "fields": { + "number": { + "type": "int" + }, + "url": { + "type": "String" + } + } } }, "v0": { @@ -2826,7 +2915,7 @@ "update_group": { "request": "UpdateGroupRequest", "response": "GroupInfo", - "doc": "modify a group. Note that only one modification action may be preformed at once" + "doc": "modify a group. Note that only one modification action may be performed at once" }, "set_profile": { "request": "SetProfile" @@ -2963,6 +3052,16 @@ }, "delete_server": { "request": "RemoveServerRequest" + }, + "send_payment": { + "request": "SendPaymentRequest", + "response": "SendResponse", + "doc": "send a mobilecoin payment" + }, + "get_remote_config": { + "request": "RemoteConfigRequest", + "response": "RemoteConfigList", + "doc": "Retrieves the remote config (feature flags) from the server." } } } diff --git a/signald/client-protocol/v1/requests.go b/signald/client-protocol/v1/requests.go index 38fb1c8..e8de4d3 100644 --- a/signald/client-protocol/v1/requests.go +++ b/signald/client-protocol/v1/requests.go @@ -449,6 +449,39 @@ func (r *GetProfileRequest) Submit(conn *signald.Signald) (response Profile, err } +// Submit: Retrieves the remote config (feature flags) from the server. +func (r *RemoteConfigRequest) Submit(conn *signald.Signald) (response RemoteConfigList, err error) { + r.Version = "v1" + r.Type = "get_remote_config" + if r.ID == "" { + r.ID = signald.GenerateID() + } + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + return + } + + err = json.Unmarshal(rawResponse.Data, &response) + if err != nil { + rawResponseJson, _ := rawResponse.Data.MarshalJSON() + log.Println("signald-go: error unmarshalling response from signald of type", rawResponse.Type, string(rawResponseJson)) + return + } + + return response, nil + +} + func (r *GetServersRequest) Submit(conn *signald.Signald) (response ServerList, err error) { r.Version = "v1" r.Type = "get_servers" @@ -950,6 +983,39 @@ func (r *SendRequest) Submit(conn *signald.Signald) (response SendResponse, err } +// Submit: send a mobilecoin payment +func (r *SendPaymentRequest) Submit(conn *signald.Signald) (response SendResponse, err error) { + r.Version = "v1" + r.Type = "send_payment" + if r.ID == "" { + r.ID = signald.GenerateID() + } + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + return + } + + err = json.Unmarshal(rawResponse.Data, &response) + if err != nil { + rawResponseJson, _ := rawResponse.Data.MarshalJSON() + log.Println("signald-go: error unmarshalling response from signald of type", rawResponse.Type, string(rawResponseJson)) + return + } + + return response, nil + +} + // Submit: set this device's name. This will show up on the mobile device on the same account under func (r *SetDeviceNameRequest) Submit(conn *signald.Signald) (err error) { r.Version = "v1" @@ -1171,7 +1237,7 @@ func (r *UpdateContactRequest) Submit(conn *signald.Signald) (response Profile, } -// Submit: modify a group. Note that only one modification action may be preformed at once +// Submit: modify a group. Note that only one modification action may be performed at once func (r *UpdateGroupRequest) Submit(conn *signald.Signald) (response GroupInfo, err error) { r.Version = "v1" r.Type = "update_group" diff --git a/signald/client-protocol/v1/structs.go b/signald/client-protocol/v1/structs.go index 7cd5f78..726126c 100644 --- a/signald/client-protocol/v1/structs.go +++ b/signald/client-protocol/v1/structs.go @@ -290,11 +290,12 @@ type JsonGroupInfo struct { } type JsonGroupJoinInfo struct { - AddFromInviteLink int32 `json:"addFromInviteLink,omitempty" yaml:"addFromInviteLink,omitempty"` + AddFromInviteLink int32 `json:"addFromInviteLink,omitempty" yaml:"addFromInviteLink,omitempty"` // The access level required in order to join the group from the invite link, as an AccessControl.AccessRequired enum from the upstream Signal groups.proto file. This is UNSATISFIABLE (4) when the group link is disabled; ADMINISTRATOR (3) when the group link is enabled, but an administrator must approve new members; and ANY (1) when the group link is enabled and no approval is required. See theGroupAccessControl structure and the upstream enum ordinals. + Description string `json:"description,omitempty" yaml:"description,omitempty"` GroupID string `json:"groupID,omitempty" yaml:"groupID,omitempty"` MemberCount int32 `json:"memberCount,omitempty" yaml:"memberCount,omitempty"` - PendingAdminApproval bool `json:"pendingAdminApproval,omitempty" yaml:"pendingAdminApproval,omitempty"` - Revision int32 `json:"revision,omitempty" yaml:"revision,omitempty"` + PendingAdminApproval bool `json:"pendingAdminApproval,omitempty" yaml:"pendingAdminApproval,omitempty"` // Whether the account is waiting for admin approval in order to be added to the group. + Revision int32 `json:"revision,omitempty" yaml:"revision,omitempty"` // The Group V2 revision. This is incremented by clients whenever they update group information, and it is often used by clients to determine if the local group state is out-of-date with the server's revision. Title string `json:"title,omitempty" yaml:"title,omitempty"` } @@ -480,16 +481,17 @@ type Payment struct { // Profile: Information about a Signal user type Profile struct { - About string `json:"about,omitempty" yaml:"about,omitempty"` - Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` - Avatar string `json:"avatar,omitempty" yaml:"avatar,omitempty"` // path to avatar on local disk - Capabilities *Capabilities `json:"capabilities,omitempty" yaml:"capabilities,omitempty"` - Color string `json:"color,omitempty" yaml:"color,omitempty"` // color of the chat with this user - Emoji string `json:"emoji,omitempty" yaml:"emoji,omitempty"` - ExpirationTime int32 `json:"expiration_time,omitempty" yaml:"expiration_time,omitempty"` - InboxPosition int32 `json:"inbox_position,omitempty" yaml:"inbox_position,omitempty"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` // The user's name from local contact names if available, or if not in contact list their Signal profile name - ProfileName string `json:"profile_name,omitempty" yaml:"profile_name,omitempty"` // The user's Signal profile name + About string `json:"about,omitempty" yaml:"about,omitempty"` + Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` + Avatar string `json:"avatar,omitempty" yaml:"avatar,omitempty"` // path to avatar on local disk + Capabilities *Capabilities `json:"capabilities,omitempty" yaml:"capabilities,omitempty"` + Color string `json:"color,omitempty" yaml:"color,omitempty"` // color of the chat with this user + Emoji string `json:"emoji,omitempty" yaml:"emoji,omitempty"` + ExpirationTime int32 `json:"expiration_time,omitempty" yaml:"expiration_time,omitempty"` + InboxPosition int32 `json:"inbox_position,omitempty" yaml:"inbox_position,omitempty"` + MobilecoinAddress string `json:"mobilecoin_address,omitempty" yaml:"mobilecoin_address,omitempty"` // *base64-encoded* mobilecoin address. Note that this is not the traditional MobileCoin address encoding. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. If unset, null or an empty string, will empty the profile payment address + Name string `json:"name,omitempty" yaml:"name,omitempty"` // The user's name from local contact names if available, or if not in contact list their Signal profile name + ProfileName string `json:"profile_name,omitempty" yaml:"profile_name,omitempty"` // The user's Signal profile name } type ProfileList struct { @@ -521,6 +523,22 @@ type RegisterRequest struct { Voice bool `json:"voice,omitempty" yaml:"voice,omitempty"` // set to true to request a voice call instead of an SMS for verification } +// RemoteConfig: A remote config (feature flag) entry. +type RemoteConfig struct { + Name string `json:"name,omitempty" yaml:"name,omitempty"` // The name of this remote config entry. These names may be prefixed with the platform type ("android.", "ios.", "desktop.", etc.) Typically, clients only handle the relevant configs for its platform, hardcoding the names it cares about handling and ignoring the rest. + Value string `json:"value,omitempty" yaml:"value,omitempty"` // The value for this remote config entry. Even though this is a string, it could be a boolean as a string, an integer/long value, a comma-delimited list, etc. Clients usually consume this by hardcoding the feature flagsit should track in the app and assuming that the server will send the type that the client expects. If an unexpected type occurs, it falls back to a default value. +} + +type RemoteConfigList struct { + Config []*RemoteConfig `json:"config,omitempty" yaml:"config,omitempty"` +} + +// RemoteConfigRequest: Retrieves the remote config (feature flags) from the server. +type RemoteConfigRequest struct { + Request + Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to use to retrieve the remote config +} + type RemoteDelete struct { TargetSentTimestamp int64 `json:"target_sent_timestamp,omitempty" yaml:"target_sent_timestamp,omitempty"` } @@ -571,6 +589,15 @@ type ResolveAddressRequest struct { Partial *JsonAddress `json:"partial,omitempty" yaml:"partial,omitempty"` // The partial address, missing fields } +// SendPaymentRequest: send a mobilecoin payment +type SendPaymentRequest struct { + Request + Account string `json:"account,omitempty" yaml:"account,omitempty"` // the account to use + Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` // the address to send the payment message to + Payment *Payment `json:"payment,omitempty" yaml:"payment,omitempty"` + When int64 `json:"when,omitempty" yaml:"when,omitempty"` +} + type SendRequest struct { Request Attachments []*v0.JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` @@ -603,6 +630,8 @@ type Server struct { } type ServerCDN struct { + Number int32 `json:"number,omitempty" yaml:"number,omitempty"` + Url string `json:"url,omitempty" yaml:"url,omitempty"` } type ServerList struct { @@ -627,11 +656,12 @@ type SetExpirationRequest struct { type SetProfile struct { Request - About string `json:"about,omitempty" yaml:"about,omitempty"` - Account string `json:"account,omitempty" yaml:"account,omitempty"` // The phone number of the account to use - AvatarFile string `json:"avatarFile,omitempty" yaml:"avatarFile,omitempty"` // Path to new profile avatar file, if the avatar should be updated - Emoji string `json:"emoji,omitempty" yaml:"emoji,omitempty"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` // New profile name. Set to empty string for no profile name + About string `json:"about,omitempty" yaml:"about,omitempty"` // an optional about string. If unset, null or an empty string will unset profile about field + Account string `json:"account,omitempty" yaml:"account,omitempty"` // The phone number of the account to use + AvatarFile string `json:"avatarFile,omitempty" yaml:"avatarFile,omitempty"` // Path to new profile avatar file. If unset or null, unset the profile avatar + Emoji string `json:"emoji,omitempty" yaml:"emoji,omitempty"` // an optional single emoji character. If unset, null or an empty string will unset profile emoji + MobilecoinAddress string `json:"mobilecoin_address,omitempty" yaml:"mobilecoin_address,omitempty"` // an optional *base64-encoded* MobileCoin address to set in the profile. Note that this is not the traditional MobileCoin address encoding, which is custom. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. If unset, null or an empty string, will empty the profile payment address + Name string `json:"name,omitempty" yaml:"name,omitempty"` // New profile name. Set to empty string for no profile name } // SubscribeRequest: receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages. @@ -682,13 +712,14 @@ type UpdateContactRequest struct { Name string `json:"name,omitempty" yaml:"name,omitempty"` } -// UpdateGroupRequest: modify a group. Note that only one modification action may be preformed at once +// UpdateGroupRequest: modify a group. Note that only one modification action may be performed at once type UpdateGroupRequest struct { Request Account string `json:"account,omitempty" yaml:"account,omitempty"` // The identifier of the account to interact with AddMembers []*JsonAddress `json:"addMembers,omitempty" yaml:"addMembers,omitempty"` Avatar string `json:"avatar,omitempty" yaml:"avatar,omitempty"` - GroupID string `json:"groupID,omitempty" yaml:"groupID,omitempty"` // the ID of the group to update + Description string `json:"description,omitempty" yaml:"description,omitempty"` // A new group description. Set to empty string to remove an existing description. + GroupID string `json:"groupID,omitempty" yaml:"groupID,omitempty"` // the ID of the group to update RemoveMembers []*JsonAddress `json:"removeMembers,omitempty" yaml:"removeMembers,omitempty"` ResetLink bool `json:"resetLink,omitempty" yaml:"resetLink,omitempty"` // regenerate the group link password, invalidating the old one Title string `json:"title,omitempty" yaml:"title,omitempty"` From 2fa9670efec9762d9553eef78f7fc870c92499d4 Mon Sep 17 00:00:00 2001 From: Finn Date: Wed, 1 Sep 2021 20:24:56 -0700 Subject: [PATCH 22/73] improve support for handling UUIDs --- .../cmd/group/addmember/add-member.go | 5 +++- .../cmd/group/create/create-group.go | 5 +++- .../cmd/group/removemember/remove-member.go | 5 +++- .../group/update/role/update-group-role.go | 6 ++++- cmd/signaldctl/cmd/key/list/list-keys.go | 5 +++- cmd/signaldctl/cmd/key/qr/qr-key.go | 6 ++++- cmd/signaldctl/cmd/key/trust/trust-key.go | 6 ++++- cmd/signaldctl/cmd/key/trust_all/trust-all.go | 6 ++++- .../cmd/message/react/react-to-message.go | 5 +++- .../cmd/message/send/send-message.go | 22 ++++++++++------- cmd/signaldctl/common/signald.go | 24 ++++++++++++++++--- go.mod | 1 + go.sum | 2 ++ 13 files changed, 77 insertions(+), 21 deletions(-) diff --git a/cmd/signaldctl/cmd/group/addmember/add-member.go b/cmd/signaldctl/cmd/group/addmember/add-member.go index 73464bc..54bc20f 100644 --- a/cmd/signaldctl/cmd/group/addmember/add-member.go +++ b/cmd/signaldctl/cmd/group/addmember/add-member.go @@ -52,7 +52,10 @@ var ( } group = args[0] for _, member := range args[1:] { - address := common.StringToAddress(member) + address, err := common.StringToAddress(member) + if err != nil { + log.Fatal(err) + } members = append(members, &address) } }, diff --git a/cmd/signaldctl/cmd/group/create/create-group.go b/cmd/signaldctl/cmd/group/create/create-group.go index 8fdefb5..ca47d72 100644 --- a/cmd/signaldctl/cmd/group/create/create-group.go +++ b/cmd/signaldctl/cmd/group/create/create-group.go @@ -58,7 +58,10 @@ var ( } for _, member := range args[1:] { - address := common.StringToAddress(member) + address, err := common.StringToAddress(member) + if err != nil { + log.Fatal(err) + } req.Members = append(req.Members, &address) } diff --git a/cmd/signaldctl/cmd/group/removemember/remove-member.go b/cmd/signaldctl/cmd/group/removemember/remove-member.go index 49a2a6c..436496c 100644 --- a/cmd/signaldctl/cmd/group/removemember/remove-member.go +++ b/cmd/signaldctl/cmd/group/removemember/remove-member.go @@ -52,7 +52,10 @@ var ( } group = args[0] for _, member := range args[1:] { - address := common.StringToAddress(member) + address, err := common.StringToAddress(member) + if err != nil { + log.Fatal(err) + } members = append(members, &address) } }, diff --git a/cmd/signaldctl/cmd/group/update/role/update-group-role.go b/cmd/signaldctl/cmd/group/update/role/update-group-role.go index 8568b0d..5c69ab9 100644 --- a/cmd/signaldctl/cmd/group/update/role/update-group-role.go +++ b/cmd/signaldctl/cmd/group/update/role/update-group-role.go @@ -52,7 +52,11 @@ var ( log.Fatal("not enough arguments provided") } group = args[0] - address = common.StringToAddress(args[1]) + var err error + address, err = common.StringToAddress(args[1]) + if err != nil { + log.Fatal(err) + } switch strings.ToLower(args[2]) { case "default": role = "DEFAULT" diff --git a/cmd/signaldctl/cmd/key/list/list-keys.go b/cmd/signaldctl/cmd/key/list/list-keys.go index b20ca26..20ee54d 100644 --- a/cmd/signaldctl/cmd/key/list/list-keys.go +++ b/cmd/signaldctl/cmd/key/list/list-keys.go @@ -44,7 +44,10 @@ var ( log.Fatal("No account specified. Please specify with --account or set a default") } if len(args) >= 1 { - a := common.StringToAddress(args[0]) + a, err := common.StringToAddress(args[0]) + if err != nil { + log.Fatal(err) + } address = &a } }, diff --git a/cmd/signaldctl/cmd/key/qr/qr-key.go b/cmd/signaldctl/cmd/key/qr/qr-key.go index 205db36..8ad927d 100644 --- a/cmd/signaldctl/cmd/key/qr/qr-key.go +++ b/cmd/signaldctl/cmd/key/qr/qr-key.go @@ -50,7 +50,11 @@ var ( common.Must(cmd.Help()) log.Fatal("please specify an address") } - address = common.StringToAddress(args[0]) + var err error + address, err = common.StringToAddress(args[0]) + if err != nil { + log.Fatal(err) + } if len(args) > 1 { safetyNumber = args[1] } diff --git a/cmd/signaldctl/cmd/key/trust/trust-key.go b/cmd/signaldctl/cmd/key/trust/trust-key.go index 6dd0195..da2224c 100644 --- a/cmd/signaldctl/cmd/key/trust/trust-key.go +++ b/cmd/signaldctl/cmd/key/trust/trust-key.go @@ -46,7 +46,11 @@ var ( common.Must(cmd.Help()) log.Fatal("please specify an address and a safety number") } - address = common.StringToAddress(args[0]) + var err error + address, err = common.StringToAddress(args[0]) + if err != nil { + log.Fatal(err) + } safetyNumber = args[1] if len(args) > 2 { level = strings.ToUpper(args[2]) diff --git a/cmd/signaldctl/cmd/key/trust_all/trust-all.go b/cmd/signaldctl/cmd/key/trust_all/trust-all.go index 3eb10a2..e474b2c 100644 --- a/cmd/signaldctl/cmd/key/trust_all/trust-all.go +++ b/cmd/signaldctl/cmd/key/trust_all/trust-all.go @@ -44,7 +44,11 @@ var ( log.Fatal("No account specified. Please specify with --account or set a default") } for _, address := range args { - addresses = append(addresses, common.StringToAddress(address)) + address, err := common.StringToAddress(address) + if err != nil { + log.Fatal(err) + } + addresses = append(addresses, address) } }, Run: func(_ *cobra.Command, _ []string) { diff --git a/cmd/signaldctl/cmd/message/react/react-to-message.go b/cmd/signaldctl/cmd/message/react/react-to-message.go index be66ebc..d91c2f3 100644 --- a/cmd/signaldctl/cmd/message/react/react-to-message.go +++ b/cmd/signaldctl/cmd/message/react/react-to-message.go @@ -53,8 +53,11 @@ var ( if len(args) != 3 { common.Must(cmd.Help()) } - author = common.StringToAddress(args[0]) var err error + author, err = common.StringToAddress(args[0]) + if err != nil { + log.Fatal(err) + } timestamp, err = strconv.ParseInt(args[1], 10, 64) if err != nil { log.Fatal("Unable to parse timestamp", args[1], ":", err.Error()) diff --git a/cmd/signaldctl/cmd/message/send/send-message.go b/cmd/signaldctl/cmd/message/send/send-message.go index b0cd39d..f8bf02c 100644 --- a/cmd/signaldctl/cmd/message/send/send-message.go +++ b/cmd/signaldctl/cmd/message/send/send-message.go @@ -35,6 +35,8 @@ import ( var ( account string + toAddress *v1.JsonAddress + toGroup string attachments []string SendMessageCmd = &cobra.Command{ @@ -52,20 +54,22 @@ var ( common.Must(cmd.Help()) log.Fatal("must specify both destination (either group id or phone number) and message") } + to, err := common.StringToAddress(args[0]) + if err != nil { + toGroup = args[0] + } else { + toAddress = &to + } }, Run: func(_ *cobra.Command, args []string) { go common.Signald.Listen(nil) req := v1.SendRequest{ - Username: account, - MessageBody: strings.Join(args[1:], " "), - Attachments: []*v0.JsonAttachment{}, - } - - if strings.HasPrefix(args[0], "+") { - req.RecipientAddress = &v1.JsonAddress{Number: args[0]} - } else { - req.RecipientGroupID = args[0] + Username: account, + MessageBody: strings.Join(args[1:], " "), + Attachments: []*v0.JsonAttachment{}, + RecipientAddress: toAddress, + RecipientGroupID: toGroup, } for _, attachment := range attachments { diff --git a/cmd/signaldctl/common/signald.go b/cmd/signaldctl/common/signald.go index 8ffc145..3db14e8 100644 --- a/cmd/signaldctl/common/signald.go +++ b/cmd/signaldctl/common/signald.go @@ -1,9 +1,11 @@ package common import ( + "fmt" "log" "strings" + "github.com/google/uuid" "github.com/jedib0t/go-pretty/v6/table" "gitlab.com/signald/signald-go/signald" @@ -30,6 +32,18 @@ var ( OutputFormat string ) +type InvalidAddressError struct { + invalidAddress string +} + +func NewInvalidAddress(i string) InvalidAddressError { + return InvalidAddressError{invalidAddress: i} +} + +func (i InvalidAddressError) Error() string { + return fmt.Sprintf("invalid address: %s", i.invalidAddress) +} + func Must(err error) { if err != nil { log.Fatal(err) @@ -40,9 +54,13 @@ func StylizeTable(t table.Writer) { t.SetStyle(table.StyleLight) } -func StringToAddress(address string) v1.JsonAddress { +func StringToAddress(address string) (v1.JsonAddress, error) { if strings.HasPrefix(address, "+") { - return v1.JsonAddress{Number: address} + return v1.JsonAddress{Number: address}, nil } - return v1.JsonAddress{UUID: address} + + if _, err := uuid.Parse(address); err == nil { + return v1.JsonAddress{UUID: address}, nil + } + return v1.JsonAddress{}, NewInvalidAddress(address) } diff --git a/go.mod b/go.mod index 6cc870b..a08f00b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module gitlab.com/signald/signald-go go 1.14 require ( + github.com/google/uuid v1.3.0 github.com/jedib0t/go-pretty/v6 v6.1.0 github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.10 // indirect diff --git a/go.sum b/go.sum index b61d002..d22c029 100644 --- a/go.sum +++ b/go.sum @@ -65,6 +65,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= From 3cb60fd14faf6e6a822da026a0437881c76c6839 Mon Sep 17 00:00:00 2001 From: Finn Date: Wed, 1 Sep 2021 20:42:59 -0700 Subject: [PATCH 23/73] fix new uuid library dependency --- .gitlab-ci.yml | 2 +- debian/control | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 57c97cb..ef46119 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,7 +40,7 @@ lint: script: - echo deb http://deb.debian.org/debian buster-backports main > /etc/apt/sources.list.d/backports.list - apt-get update - - apt-get install -y -t buster-backports git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev + - apt-get install -y -t buster-backports git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev golang-github-google-uuid-dev - apt-get install -y ./*.deb && rm -vf *.deb - 'sed -i "s/^Architecture:.*/Architecture: ${ARCH}/g" debian/control' - go run ./cmd/signaldctl doc -o man diff --git a/debian/control b/debian/control index 86071c4..989ff78 100644 --- a/debian/control +++ b/debian/control @@ -8,7 +8,8 @@ Build-Depends: debhelper (>= 11), dh-golang, golang-any, golang-github-spf13-cobra-dev, - golang-github-spf13-viper-dev + golang-github-spf13-viper-dev, + golang-github-google-uuid-dev Standards-Version: 4.2.1 Homepage: https://signald.org Vcs-Browser: https://gitlab.com/signald/signald-go From 9a5253232520303c6af9b7de77142e36a880ac1a Mon Sep 17 00:00:00 2001 From: Finn Date: Tue, 14 Sep 2021 19:01:38 -0700 Subject: [PATCH 24/73] add group join subcommand --- cmd/signaldctl/cmd/group/join/join.go | 111 ++++++++++++++++++++++++++ cmd/signaldctl/cmd/group/root.go | 2 + 2 files changed, 113 insertions(+) create mode 100644 cmd/signaldctl/cmd/group/join/join.go diff --git a/cmd/signaldctl/cmd/group/join/join.go b/cmd/signaldctl/cmd/group/join/join.go new file mode 100644 index 0000000..c528244 --- /dev/null +++ b/cmd/signaldctl/cmd/group/join/join.go @@ -0,0 +1,111 @@ +// Copyright © 2021 Finn Herzfeld +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package join + +import ( + "encoding/json" + "log" + "os" + + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" + + "gitlab.com/signald/signald-go/cmd/signaldctl/common" + "gitlab.com/signald/signald-go/cmd/signaldctl/config" + "gitlab.com/signald/signald-go/signald/client-protocol/v1" +) + +var ( + account string + url string + + JoinGroupCmd = &cobra.Command{ + Use: "join ", + Short: "join a group by URL", + PreRun: func(cmd *cobra.Command, args []string) { + if account == "" { + account = config.Config.DefaultAccount + } + if account == "" { + common.Must(cmd.Help()) + log.Fatal("No account specified. Please specify with --account or set a default") + } + if len(args) < 1 { + common.Must(cmd.Help()) + log.Fatal("must specify group url") + } + url = args[0] + }, + Run: func(_ *cobra.Command, args []string) { + go common.Signald.Listen(nil) + req := v1.JoinGroupRequest{ + Account: account, + Uri: url, + } + + resp, err := req.Submit(common.Signald) + if err != nil { + log.Fatal(err, "error communicating with signald") + } + + if resp.PendingAdminApproval { + log.Println("requested to join pending admin approval") + } else { + log.Println("joined group") + } + + switch common.OutputFormat { + case common.OutputFormatJSON: + err := json.NewEncoder(os.Stdout).Encode(resp) + if err != nil { + log.Fatal(err, "error encoding response to stdout") + } + case common.OutputFormatYAML: + err := yaml.NewEncoder(os.Stdout).Encode(resp) + if err != nil { + log.Fatal(err, "error encoding response to stdout") + } + case common.OutputFormatCSV, common.OutputFormatTable: + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendRows([]table.Row{ + {"Group ID", resp.GroupID}, + {"Revision", resp.Revision}, + {"Title", resp.Title}, + {"Description", resp.Description}, + {"Members", resp.MemberCount}, + {"Awaiting approval", resp.PendingAdminApproval}, + }) + + if common.OutputFormat == common.OutputFormatCSV { + t.RenderCSV() + } else { + common.StylizeTable(t) + t.Render() + } + case common.OutputFormatQuiet, common.OutputFormatDefault: + return + default: + log.Fatal("Unsupported output format") + } + }, + } +) + +func init() { + JoinGroupCmd.Flags().StringVarP(&account, "account", "a", "", "the signald account to use") +} diff --git a/cmd/signaldctl/cmd/group/root.go b/cmd/signaldctl/cmd/group/root.go index 7e23c97..3f7d0ea 100644 --- a/cmd/signaldctl/cmd/group/root.go +++ b/cmd/signaldctl/cmd/group/root.go @@ -21,6 +21,7 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/accept" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/addmember" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/create" + "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/join" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/leave" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/list" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/removemember" @@ -37,6 +38,7 @@ func init() { GroupCmd.AddCommand(accept.AcceptGroupInvitationCmd) GroupCmd.AddCommand(addmember.AddGroupMembersCmd) GroupCmd.AddCommand(create.CreateGroupCmd) + GroupCmd.AddCommand(join.JoinGroupCmd) GroupCmd.AddCommand(leave.LeaveGroupCmd) GroupCmd.AddCommand(list.ListGroupCmd) GroupCmd.AddCommand(removemember.RemoveMemberCmd) From 0f8b17383f00da4b33fda28090659fb4de66e207 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 24 Sep 2021 02:24:04 -0700 Subject: [PATCH 25/73] add a subcommand to get the current protocol document from a running signald instance --- cmd/signaldctl/cmd/protocol.go | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 cmd/signaldctl/cmd/protocol.go diff --git a/cmd/signaldctl/cmd/protocol.go b/cmd/signaldctl/cmd/protocol.go new file mode 100644 index 0000000..5a0db72 --- /dev/null +++ b/cmd/signaldctl/cmd/protocol.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "fmt" + "log" + "os" + + "github.com/spf13/cobra" + "gitlab.com/signald/signald-go/cmd/signaldctl/common" + "gitlab.com/signald/signald-go/signald" +) + +var protocolCmd = &cobra.Command{ + Use: "protocol", + Short: "return a machine-readable description of the signald client protocol. Details at https://signald.org/articles/socket-protocol/", + RunE: func(cmd *cobra.Command, args []string) error { + go common.Signald.Listen(nil) + + req := map[string]string{"version": "v1", "type": "protocol", "id": signald.GenerateID()} + err := common.Signald.RawRequest(req) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return err + } + + responseChannel := common.Signald.GetResponseListener(req["id"]) + defer common.Signald.CloseResponseListener(req["id"]) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + return fmt.Errorf("signald error: %s", string(rawResponse.Error)) + } + + _, err = os.Stdout.Write(rawResponse.Data) + return err + }, +} + +func init() { + RootCmd.AddCommand(protocolCmd) +} From bfcb423cdf6d4d59a1882924e727f9f9ef64d5b7 Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 30 Sep 2021 00:39:54 -0700 Subject: [PATCH 26/73] add support for errors in the protocol --- protocol.json | 1352 ++++++++++++++++++++++-- signald/client-protocol/protocol.go | 13 + signald/client-protocol/v0/structs.go | 8 +- signald/client-protocol/v1/errors.go | 462 ++++++++ signald/client-protocol/v1/requests.go | 124 ++- signald/client-protocol/v1/structs.go | 104 +- signald/signald.go | 36 +- tools/generator/errors.go.tmpl | 37 + tools/generator/main.go | 72 +- tools/generator/requests.go.tmpl | 9 +- 10 files changed, 2007 insertions(+), 210 deletions(-) create mode 100644 signald/client-protocol/protocol.go create mode 100644 signald/client-protocol/v1/errors.go create mode 100644 tools/generator/errors.go.tmpl diff --git a/protocol.json b/protocol.json index f416485..1905083 100644 --- a/protocol.json +++ b/protocol.json @@ -2,9 +2,9 @@ "doc_version": "v1", "version": { "name": "signald", - "version": "0.14.1+git2021-08-27r6dd96a17.28", + "version": "0.14.1-48-7d927883", "branch": "main", - "commit": "6dd96a17c921766b0d284165e571476744ac2829" + "commit": "7d92788343f34c08634abfeda06045ae13e18670" }, "info": "This document describes objects that may be used when communicating with signald.", "types": { @@ -203,6 +203,11 @@ "list": true, "type": "JsonMention", "version": "v1" + }, + "previews": { + "list": true, + "type": "JsonPreview", + "version": "v1" } } }, @@ -219,6 +224,84 @@ } } }, + "NoSuchAccountError": { + "fields": { + "account": { + "type": "String" + }, + "message": { + "type": "String" + } + }, + "error": true + }, + "ServerNotFoundError": { + "fields": { + "uuid": { + "type": "String" + }, + "message": { + "type": "String" + } + }, + "error": true + }, + "InvalidProxyError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "NoSendPermissionError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "InvalidAttachmentError": { + "fields": { + "filename": { + "type": "String" + }, + "message": { + "type": "String" + } + }, + "error": true + }, + "InternalError": { + "fields": { + "exceptions": { + "list": true, + "type": "String" + }, + "message": { + "type": "String" + } + }, + "doc": "an internal error in signald has occured.", + "error": true + }, + "UnknownGroupError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "InvalidRecipientError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "ReactRequest": { "fields": { "username": { @@ -256,7 +339,7 @@ }, "version": { "type": "String", - "example": "\"0.14.1+git2021-08-27r6dd96a17.28\"" + "example": "\"0.14.1-48-7d927883\"" }, "branch": { "type": "String", @@ -264,7 +347,7 @@ }, "commit": { "type": "String", - "example": "\"6dd96a17c921766b0d284165e571476744ac2829\"" + "example": "\"7d92788343f34c08634abfeda06045ae13e18670\"" } } }, @@ -345,10 +428,22 @@ "type": "GroupMember", "version": "v1", "doc": "detailed pending member list" + }, + "announcements": { + "type": "String", + "doc": "indicates if the group is an announcements group. Only admins are allowed to send messages to announcements groups. Options are UNKNOWN, ENABLED or DISABLED" } }, "doc": "Information about a Signal group" }, + "OwnProfileKeyDoesNotExistError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "ApproveMembershipRequest": { "fields": { "account": { @@ -372,6 +467,14 @@ }, "doc": "approve a request to join a group" }, + "GroupVerificationError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "GetGroupRequest": { "fields": { "account": { @@ -390,7 +493,15 @@ "doc": "the latest known revision, default value (-1) forces fetch from server" } }, - "doc": "Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupException' is returned." + "doc": "Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupError' is returned." + }, + "InvalidGroupStateError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true }, "GetLinkedDevicesRequest": { "fields": { @@ -462,6 +573,30 @@ } } }, + "InvalidRequestError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "InvalidInviteURIError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "GroupNotActiveError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "RemoveLinkedDeviceRequest": { "fields": { "account": { @@ -532,6 +667,10 @@ "resetLink": { "type": "boolean", "doc": "regenerate the group link password, invalidating the old one" + }, + "announcements": { + "type": "String", + "doc": "ENABLED to only allow admins to post messages, DISABLED to allow anyone to post" } }, "doc": "modify a group. Note that only one modification action may be performed at once" @@ -582,6 +721,14 @@ } } }, + "InvalidBase64Error": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "ResolveAddressRequest": { "fields": { "account": { @@ -597,7 +744,7 @@ "required": true } }, - "doc": "Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns" + "doc": "Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns." }, "JsonAddress": { "fields": { @@ -641,6 +788,21 @@ } } }, + "UntrustedIdentityError": { + "fields": { + "identifier": { + "type": "String" + }, + "message": { + "type": "String" + }, + "identity_key": { + "type": "IdentityKey", + "version": "v1" + } + }, + "error": true + }, "GetProfileRequest": { "fields": { "account": { @@ -706,6 +868,14 @@ }, "doc": "Information about a Signal user" }, + "ProfileUnavailableError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "ListGroupsRequest": { "fields": { "account": { @@ -783,6 +953,14 @@ } } }, + "NoKnownUUIDError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "LeaveGroupRequest": { "fields": { "account": { @@ -836,6 +1014,10 @@ "version": "v1", "doc": "The address of this account" }, + "pending": { + "type": "Boolean", + "doc": "indicates the account has not completed registration" + }, "device_id": { "type": "int", "doc": "The Signal device ID. Official Signal mobile clients (iPhone and Android) have device ID = 1, while linked devices such as Signal Desktop or Signal iPad have higher device IDs." @@ -847,6 +1029,25 @@ }, "doc": "A local account in signald" }, + "NoSuchSessionError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "UserAlreadyExistsError": { + "fields": { + "uuid": { + "type": "UUID" + }, + "message": { + "type": "String" + } + }, + "error": true + }, "AddLinkedDeviceRequest": { "fields": { "account": { @@ -887,6 +1088,17 @@ }, "doc": "begin the account registration process by requesting a phone number verification code. when the code is received, submit it with a verify request" }, + "CaptchaRequiredError": { + "fields": { + "more": { + "type": "String" + }, + "message": { + "type": "String" + } + }, + "error": true + }, "VerifyRequest": { "fields": { "account": { @@ -904,6 +1116,33 @@ }, "doc": "verify an account's phone number with a code after registering, completing the account creation process" }, + "AccountHasNoKeysError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "AccountAlreadyVerifiedError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "AccountLockedError": { + "fields": { + "more": { + "type": "String" + }, + "message": { + "type": "String" + } + }, + "error": true + }, "GetIdentitiesRequest": { "fields": { "account": { @@ -966,6 +1205,30 @@ }, "doc": "Trust another user's safety number using either the QR code data or the safety number text" }, + "FingerprintVersionMismatchError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "UnknownIdentityKeyError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "InvalidFingerprintError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "DeleteAccountRequest": { "fields": { "account": { @@ -1008,6 +1271,14 @@ }, "doc": "send a typing started or stopped message" }, + "InvalidGroupError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "ResetSessionRequest": { "fields": { "account": { @@ -1085,6 +1356,14 @@ }, "doc": "Get information about a group from a signal.group link" }, + "GroupLinkNotActiveError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "UpdateContactRequest": { "fields": { "account": { @@ -1288,6 +1567,29 @@ } } }, + "RefuseMembershipRequest": { + "fields": { + "account": { + "type": "String", + "doc": "The account to interact with", + "example": "\"+12024561414\"", + "required": true + }, + "members": { + "list": true, + "type": "JsonAddress", + "version": "v1", + "doc": "list of requesting members to refuse", + "required": true + }, + "group_id": { + "type": "String", + "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", + "required": true + } + }, + "doc": "deny a request to join a group" + }, "JsonDataMessage": { "fields": { "timestamp": { @@ -1298,7 +1600,7 @@ "attachments": { "list": true, "type": "JsonAttachment", - "version": "v0", + "version": "v1", "doc": "files attached to the incoming message" }, "body": { @@ -1340,7 +1642,7 @@ "previews": { "list": true, "type": "JsonPreview", - "version": "v0", + "version": "v1", "doc": "if the incoming message has a link preview, information about that preview will be here" }, "sticker": { @@ -1387,14 +1689,14 @@ }, "contacts": { "type": "JsonAttachment", - "version": "v0" + "version": "v1" }, "contactsComplete": { "type": "boolean" }, "groups": { "type": "JsonAttachment", - "version": "v0" + "version": "v1" }, "blockedList": { "type": "JsonBlockedListMessage", @@ -1544,6 +1846,28 @@ } } }, + "JsonPreview": { + "fields": { + "url": { + "type": "String" + }, + "title": { + "type": "String" + }, + "description": { + "type": "String" + }, + "date": { + "type": "long" + }, + "attachment": { + "type": "JsonAttachment", + "version": "v1", + "doc": "an optional image file attached to the preview" + } + }, + "doc": "metadata about one of the links in a message" + }, "JsonSendMessageResult": { "fields": { "address": { @@ -1551,8 +1875,8 @@ "version": "v1" }, "success": { - "type": "Success", - "version": "v0" + "type": "SendSuccess", + "version": "v1" }, "networkFailure": { "type": "boolean", @@ -1662,19 +1986,6 @@ }, "doc": "information about a legacy group" }, - "Capabilities": { - "fields": { - "gv2": { - "type": "boolean" - }, - "storage": { - "type": "boolean" - }, - "gv1-migration": { - "type": "boolean" - } - } - }, "IdentityKey": { "fields": { "added": { @@ -1695,6 +2006,19 @@ } } }, + "Capabilities": { + "fields": { + "gv2": { + "type": "boolean" + }, + "storage": { + "type": "boolean" + }, + "gv1-migration": { + "type": "boolean" + } + } + }, "Server": { "fields": { "uuid": { @@ -1705,7 +2029,8 @@ "type": "String" }, "ca": { - "type": "String" + "type": "String", + "doc": "base64 encoded trust store, password must be 'whisper'" }, "service_url": { "type": "String" @@ -1729,7 +2054,25 @@ "doc": "base64 encoded ZKGROUP_SERVER_PUBLIC_PARAMS value" }, "unidentified_sender_root": { + "type": "String", + "doc": "base64 encoded" + }, + "key_backup_service_name": { "type": "String" + }, + "key_backup_service_id": { + "type": "String", + "doc": "base64 encoded" + }, + "key_backup_mrenclave": { + "type": "String" + }, + "cds_mrenclave": { + "type": "String" + }, + "ias_ca": { + "type": "String", + "doc": "base64 encoded trust store, password must be 'whisper'" } }, "doc": "a Signal server" @@ -1762,6 +2105,53 @@ }, "doc": "A remote config (feature flag) entry." }, + "JsonAttachment": { + "fields": { + "contentType": { + "type": "String" + }, + "id": { + "type": "String" + }, + "size": { + "type": "int" + }, + "storedFilename": { + "type": "String", + "doc": "when receiving, the path that file has been downloaded to" + }, + "filename": { + "type": "String", + "doc": "when sending, the path to the local file to upload" + }, + "customFilename": { + "type": "String", + "doc": "the original name of the file" + }, + "caption": { + "type": "String" + }, + "width": { + "type": "int" + }, + "height": { + "type": "int" + }, + "voiceNote": { + "type": "boolean" + }, + "key": { + "type": "String" + }, + "digest": { + "type": "String" + }, + "blurhash": { + "type": "String" + } + }, + "doc": "represents a file attached to a message. When seding, only `filename` is required." + }, "RemoteDelete": { "fields": { "target_sent_timestamp": { @@ -1927,6 +2317,23 @@ } } }, + "SendSuccess": { + "fields": { + "unidentified": { + "type": "boolean" + }, + "needsSync": { + "type": "boolean" + }, + "duration": { + "type": "long" + }, + "devices": { + "list": true, + "type": "Integer" + } + } + }, "ServerCDN": { "fields": { "number": { @@ -1971,7 +2378,8 @@ "type": "String" }, "relay": { - "type": "String" + "type": "String", + "doc": "this field is no longer available and will never be populated" }, "timestamp": { "type": "long", @@ -2340,22 +2748,6 @@ } } }, - "JsonPreview": { - "fields": { - "url": { - "type": "String" - }, - "title": { - "type": "String" - }, - "attachment": { - "type": "JsonAttachment", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, "JsonSticker": { "fields": { "packID": { @@ -2603,6 +2995,22 @@ "deprecated": true, "removal_date": 1641027661 }, + "JsonPreview": { + "fields": { + "url": { + "type": "String" + }, + "title": { + "type": "String" + }, + "attachment": { + "type": "JsonAttachment", + "version": "v0" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, "JsonReaction": { "fields": { "emoji": { @@ -2777,19 +3185,6 @@ "deprecated": true, "removal_date": 1641027661 }, - "Success": { - "fields": { - "unidentified": { - "type": "boolean" - }, - "needsSync": { - "type": "boolean" - }, - "duration": { - "type": "long" - } - } - }, "Name": { "fields": { "display": { @@ -2872,12 +3267,61 @@ "v1": { "send": { "request": "SendRequest", - "response": "SendResponse" + "response": "SendResponse", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "NoSendPermissionError" + }, + { + "name": "InvalidAttachmentError" + }, + { + "name": "InternalError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "InvalidRecipientError" + } + ] }, "react": { "request": "ReactRequest", "response": "SendResponse", - "doc": "react to a previous message" + "doc": "react to a previous message", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "NoSendPermissionError" + }, + { + "name": "InternalError" + }, + { + "name": "InvalidRecipientError" + }, + { + "name": "UnknownGroupError" + } + ] }, "version": { "request": "VersionRequest", @@ -2886,182 +3330,894 @@ "accept_invitation": { "request": "AcceptInvitationRequest", "response": "JsonGroupV2Info", - "doc": "Accept a v2 group invitation. Note that you must have a profile name set to join groups." + "doc": "Accept a v2 group invitation. Note that you must have a profile name set to join groups.", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "OwnProfileKeyDoesNotExistError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "InternalError" + } + ] }, "approve_membership": { "request": "ApproveMembershipRequest", "response": "JsonGroupV2Info", - "doc": "approve a request to join a group" + "doc": "approve a request to join a group", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "InternalError" + }, + { + "name": "GroupVerificationError" + } + ] }, "get_group": { "request": "GetGroupRequest", "response": "JsonGroupV2Info", - "doc": "Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupException' is returned." + "doc": "Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupError' is returned.", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "InternalError" + }, + { + "name": "GroupVerificationError" + }, + { + "name": "InvalidGroupStateError" + } + ] }, "get_linked_devices": { "request": "GetLinkedDevicesRequest", "response": "LinkedDevices", - "doc": "list all linked devices on a Signal account" + "doc": "list all linked devices on a Signal account", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + } + ] }, "join_group": { "request": "JoinGroupRequest", "response": "JsonGroupJoinInfo", - "doc": "Join a group using the a signal.group URL. Note that you must have a profile name set to join groups." + "doc": "Join a group using the a signal.group URL. Note that you must have a profile name set to join groups.", + "errors": [ + { + "name": "InvalidRequestError" + }, + { + "name": "InvalidInviteURIError" + }, + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "OwnProfileKeyDoesNotExistError" + }, + { + "name": "GroupVerificationError" + }, + { + "name": "GroupNotActiveError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "InvalidGroupStateError" + } + ] }, "remove_linked_device": { "request": "RemoveLinkedDeviceRequest", - "doc": "Remove a linked device from the Signal account. Only allowed when the local device id is 1" + "doc": "Remove a linked device from the Signal account. Only allowed when the local device id is 1", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + } + ] }, "update_group": { "request": "UpdateGroupRequest", "response": "GroupInfo", - "doc": "modify a group. Note that only one modification action may be performed at once" + "doc": "modify a group. Note that only one modification action may be performed at once", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "GroupVerificationError" + }, + { + "name": "InvalidRequestError" + } + ] }, "set_profile": { - "request": "SetProfile" + "request": "SetProfile", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "InvalidBase64Error" + } + ] }, "resolve_address": { "request": "ResolveAddressRequest", "response": "JsonAddress", - "doc": "Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns" + "doc": "Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns.", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "NoSuchAccountError" + } + ] }, "mark_read": { - "request": "MarkReadRequest" + "request": "MarkReadRequest", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "InternalError" + }, + { + "name": "UntrustedIdentityError" + } + ] }, "get_profile": { "request": "GetProfileRequest", "response": "Profile", - "doc": "Get all information available about a user" + "doc": "Get all information available about a user", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "ProfileUnavailableError" + } + ] }, "list_groups": { "request": "ListGroupsRequest", - "response": "GroupList" + "response": "GroupList", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + } + ] }, "list_contacts": { "request": "ListContactsRequest", - "response": "ProfileList" + "response": "ProfileList", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + } + ] }, "create_group": { "request": "CreateGroupRequest", - "response": "JsonGroupV2Info" + "response": "JsonGroupV2Info", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "OwnProfileKeyDoesNotExistError" + }, + { + "name": "NoKnownUUIDError" + }, + { + "name": "InvalidRequestError" + }, + { + "name": "GroupVerificationError" + }, + { + "name": "InvalidGroupStateError" + }, + { + "name": "UnknownGroupError" + } + ] }, "leave_group": { "request": "LeaveGroupRequest", - "response": "GroupInfo" + "response": "GroupInfo", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "InternalError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "GroupVerificationError" + } + ] }, "generate_linking_uri": { "request": "GenerateLinkingURIRequest", "response": "LinkingURI", - "doc": "Generate a linking URI. Typically this is QR encoded and scanned by the primary device. Submit the returned session_id with a finish_link request." + "doc": "Generate a linking URI. Typically this is QR encoded and scanned by the primary device. Submit the returned session_id with a finish_link request.", + "errors": [ + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + } + ] }, "finish_link": { "request": "FinishLinkRequest", "response": "Account", - "doc": "After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device." + "doc": "After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device.", + "errors": [ + { + "name": "NoSuchSessionError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "InternalError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "UserAlreadyExistsError" + } + ] }, "add_device": { "request": "AddLinkedDeviceRequest", - "doc": "Link a new device to a local Signal account" + "doc": "Link a new device to a local Signal account", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "InvalidRequestError", + "doc": "caused by syntax errors with the provided linking URI" + }, + { + "name": "InternalError" + } + ] }, "register": { "request": "RegisterRequest", "response": "Account", - "doc": "begin the account registration process by requesting a phone number verification code. when the code is received, submit it with a verify request" + "doc": "begin the account registration process by requesting a phone number verification code. when the code is received, submit it with a verify request", + "errors": [ + { + "name": "CaptchaRequiredError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + } + ] }, "verify": { "request": "VerifyRequest", "response": "Account", - "doc": "verify an account's phone number with a code after registering, completing the account creation process" + "doc": "verify an account's phone number with a code after registering, completing the account creation process", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "AccountHasNoKeysError" + }, + { + "name": "AccountAlreadyVerifiedError" + }, + { + "name": "AccountLockedError" + }, + { + "name": "NoSuchAccountError" + } + ] }, "get_identities": { "request": "GetIdentitiesRequest", "response": "IdentityKeyList", - "doc": "Get information about a known keys for a particular address" + "doc": "Get information about a known keys for a particular address", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + } + ] }, "trust": { "request": "TrustRequest", - "doc": "Trust another user's safety number using either the QR code data or the safety number text" + "doc": "Trust another user's safety number using either the QR code data or the safety number text", + "errors": [ + { + "name": "InvalidRequestError" + }, + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "FingerprintVersionMismatchError" + }, + { + "name": "InvalidBase64Error" + }, + { + "name": "UnknownIdentityKeyError" + }, + { + "name": "InvalidFingerprintError" + } + ] }, "delete_account": { "request": "DeleteAccountRequest", - "doc": "delete all account data signald has on disk, and optionally delete the account from the server as well. Note that this is not \"unlink\" and will delete the entire account, even from a linked device." + "doc": "delete all account data signald has on disk, and optionally delete the account from the server as well. Note that this is not \"unlink\" and will delete the entire account, even from a linked device.", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + } + ] }, "typing": { "request": "TypingRequest", - "doc": "send a typing started or stopped message" + "doc": "send a typing started or stopped message", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "InvalidRecipientError" + }, + { + "name": "InvalidGroupError" + }, + { + "name": "UntrustedIdentityError" + }, + { + "name": "UnknownGroupError" + } + ] }, "reset_session": { "request": "ResetSessionRequest", "response": "SendResponse", - "doc": "reset a session with a particular user" + "doc": "reset a session with a particular user", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "InvalidRecipientError" + }, + { + "name": "NoSendPermissionError" + }, + { + "name": "UnknownGroupError" + } + ] }, "request_sync": { "request": "RequestSyncRequest", - "doc": "Request other devices on the account send us their group list, syncable config and contact list." + "doc": "Request other devices on the account send us their group list, syncable config and contact list.", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "UntrustedIdentityError" + } + ] }, "list_accounts": { "request": "ListAccountsRequest", "response": "AccountList", - "doc": "return all local accounts" + "doc": "return all local accounts", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "InternalError" + } + ] }, "group_link_info": { "request": "GroupLinkInfoRequest", "response": "JsonGroupJoinInfo", - "doc": "Get information about a group from a signal.group link" + "doc": "Get information about a group from a signal.group link", + "errors": [ + { + "name": "GroupLinkNotActiveError" + }, + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "InvalidRequestError" + }, + { + "name": "GroupVerificationError" + } + ] }, "update_contact": { "request": "UpdateContactRequest", "response": "Profile", - "doc": "update information about a local contact" + "doc": "update information about a local contact", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "InternalError" + } + ] }, "set_expiration": { "request": "SetExpirationRequest", "response": "SendResponse", - "doc": "Set the message expiration timer for a thread. Expiration must be specified in seconds, set to 0 to disable timer" + "doc": "Set the message expiration timer for a thread. Expiration must be specified in seconds, set to 0 to disable timer", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "GroupVerificationError" + } + ] }, "set_device_name": { "request": "SetDeviceNameRequest", - "doc": "set this device's name. This will show up on the mobile device on the same account under " + "doc": "set this device's name. This will show up on the mobile device on the same account under ", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + } + ] }, "get_all_identities": { "request": "GetAllIdentities", "response": "AllIdentityKeyList", - "doc": "get all known identity keys" + "doc": "get all known identity keys", + "errors": [ + { + "name": "InvalidProxyError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InternalError" + } + ] }, "subscribe": { "request": "SubscribeRequest", - "doc": "receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages." + "doc": "receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages.", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "InternalError" + } + ] }, "unsubscribe": { "request": "UnsubscribeRequest", - "doc": "See subscribe for more info" + "doc": "See subscribe for more info", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "InternalError" + } + ] }, "remote_delete": { "request": "RemoteDeleteRequest", "response": "SendResponse", - "doc": "delete a message previously sent" + "doc": "delete a message previously sent", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "InvalidRecipientError" + }, + { + "name": "NoSendPermissionError" + }, + { + "name": "UnknownGroupError" + } + ] }, "add_server": { "request": "AddServerRequest", "response": "String", - "doc": "add a new server to connect to. Returns the new server's UUID." + "doc": "add a new server to connect to. Returns the new server's UUID.", + "errors": [ + { + "name": "InvalidProxyError" + }, + { + "name": "InternalError" + } + ] }, "get_servers": { "request": "GetServersRequest", - "response": "ServerList" + "response": "ServerList", + "errors": [ + { + "name": "InternalError" + } + ] }, "delete_server": { - "request": "RemoveServerRequest" + "request": "RemoveServerRequest", + "errors": [ + { + "name": "InternalError" + } + ] }, "send_payment": { "request": "SendPaymentRequest", "response": "SendResponse", - "doc": "send a mobilecoin payment" + "doc": "send a mobilecoin payment", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "InvalidBase64Error" + }, + { + "name": "InvalidRecipientError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "NoSendPermissionError" + } + ] }, "get_remote_config": { "request": "RemoteConfigRequest", "response": "RemoteConfigList", - "doc": "Retrieves the remote config (feature flags) from the server." + "doc": "Retrieves the remote config (feature flags) from the server.", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + } + ] + }, + "refuse_membership": { + "request": "RefuseMembershipRequest", + "response": "JsonGroupV2Info", + "doc": "deny a request to join a group", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "GroupVerificationError" + }, + { + "name": "InternalError" + } + ] } } } diff --git a/signald/client-protocol/protocol.go b/signald/client-protocol/protocol.go new file mode 100644 index 0000000..d354c53 --- /dev/null +++ b/signald/client-protocol/protocol.go @@ -0,0 +1,13 @@ +package client_protocol + +import ( + "encoding/json" +) + +type BasicResponse struct { + ID string + Type string + ErrorType string + Error json.RawMessage + Data json.RawMessage +} diff --git a/signald/client-protocol/v0/structs.go b/signald/client-protocol/v0/structs.go index 481fc76..92de9ec 100644 --- a/signald/client-protocol/v0/structs.go +++ b/signald/client-protocol/v0/structs.go @@ -158,7 +158,7 @@ type JsonMessageEnvelope struct { HasLegacyMessage bool `json:"hasLegacyMessage,omitempty" yaml:"hasLegacyMessage,omitempty"` IsUnidentifiedSender bool `json:"isUnidentifiedSender,omitempty" yaml:"isUnidentifiedSender,omitempty"` Receipt *JsonReceiptMessage `json:"receipt,omitempty" yaml:"receipt,omitempty"` - Relay string `json:"relay,omitempty" yaml:"relay,omitempty"` + Relay string `json:"relay,omitempty" yaml:"relay,omitempty"` // this field is no longer available and will never be populated ServerDeliveredTimestamp int64 `json:"serverDeliveredTimestamp,omitempty" yaml:"serverDeliveredTimestamp,omitempty"` ServerTimestamp int64 `json:"serverTimestamp,omitempty" yaml:"serverTimestamp,omitempty"` Source *JsonAddress `json:"source,omitempty" yaml:"source,omitempty"` @@ -307,11 +307,5 @@ type SharedContact struct { Phone *Optional `json:"phone,omitempty" yaml:"phone,omitempty"` } -type Success struct { - Duration int64 `json:"duration,omitempty" yaml:"duration,omitempty"` - NeedsSync bool `json:"needsSync,omitempty" yaml:"needsSync,omitempty"` - Unidentified bool `json:"unidentified,omitempty" yaml:"unidentified,omitempty"` -} - type Type struct { } diff --git a/signald/client-protocol/v1/errors.go b/signald/client-protocol/v1/errors.go new file mode 100644 index 0000000..9778bd6 --- /dev/null +++ b/signald/client-protocol/v1/errors.go @@ -0,0 +1,462 @@ +package v1 + +import ( + "encoding/json" + "fmt" + + client_protocol "gitlab.com/signald/signald-go/signald/client-protocol" +) + +// DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo + +func mkerr(response client_protocol.BasicResponse) error { + switch response.ErrorType { + case "AccountAlreadyVerifiedError": + result := AccountAlreadyVerifiedError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "AccountHasNoKeysError": + result := AccountHasNoKeysError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "AccountLockedError": + result := AccountLockedError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "CaptchaRequiredError": + result := CaptchaRequiredError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "FingerprintVersionMismatchError": + result := FingerprintVersionMismatchError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "GroupLinkNotActiveError": + result := GroupLinkNotActiveError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "GroupNotActiveError": + result := GroupNotActiveError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "GroupVerificationError": + result := GroupVerificationError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "InternalError": + result := InternalError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "InvalidAttachmentError": + result := InvalidAttachmentError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "InvalidBase64Error": + result := InvalidBase64Error{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "InvalidFingerprintError": + result := InvalidFingerprintError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "InvalidGroupError": + result := InvalidGroupError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "InvalidGroupStateError": + result := InvalidGroupStateError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "InvalidInviteURIError": + result := InvalidInviteURIError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "InvalidProxyError": + result := InvalidProxyError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "InvalidRecipientError": + result := InvalidRecipientError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "InvalidRequestError": + result := InvalidRequestError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "NoKnownUUIDError": + result := NoKnownUUIDError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "NoSendPermissionError": + result := NoSendPermissionError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "NoSuchAccountError": + result := NoSuchAccountError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "NoSuchSessionError": + result := NoSuchSessionError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "OwnProfileKeyDoesNotExistError": + result := OwnProfileKeyDoesNotExistError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "ProfileUnavailableError": + result := ProfileUnavailableError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "ServerNotFoundError": + result := ServerNotFoundError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "UnknownGroupError": + result := UnknownGroupError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "UnknownIdentityKeyError": + result := UnknownIdentityKeyError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "UntrustedIdentityError": + result := UntrustedIdentityError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + case "UserAlreadyExistsError": + result := UserAlreadyExistsError{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result + default: + return fmt.Errorf("unexpected response type from signald: %s: %s", response.ErrorType, string(response.Error)) + } +} + +type AccountAlreadyVerifiedError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e AccountAlreadyVerifiedError) Error() string { + return e.Message +} + +type AccountHasNoKeysError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e AccountHasNoKeysError) Error() string { + return e.Message +} + +type AccountLockedError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` + More string `json:"more,omitempty" yaml:"more,omitempty"` +} + +func (e AccountLockedError) Error() string { + return e.Message +} + +type CaptchaRequiredError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` + More string `json:"more,omitempty" yaml:"more,omitempty"` +} + +func (e CaptchaRequiredError) Error() string { + return e.Message +} + +type FingerprintVersionMismatchError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e FingerprintVersionMismatchError) Error() string { + return e.Message +} + +type GroupLinkNotActiveError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e GroupLinkNotActiveError) Error() string { + return e.Message +} + +type GroupNotActiveError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e GroupNotActiveError) Error() string { + return e.Message +} + +type GroupVerificationError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e GroupVerificationError) Error() string { + return e.Message +} + +// InternalError: an internal error in signald has occured. +type InternalError struct { + Exceptions []string `json:"exceptions,omitempty" yaml:"exceptions,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e InternalError) Error() string { + return e.Message +} + +type InvalidAttachmentError struct { + Filename string `json:"filename,omitempty" yaml:"filename,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e InvalidAttachmentError) Error() string { + return e.Message +} + +type InvalidBase64Error struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e InvalidBase64Error) Error() string { + return e.Message +} + +type InvalidFingerprintError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e InvalidFingerprintError) Error() string { + return e.Message +} + +type InvalidGroupError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e InvalidGroupError) Error() string { + return e.Message +} + +type InvalidGroupStateError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e InvalidGroupStateError) Error() string { + return e.Message +} + +type InvalidInviteURIError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e InvalidInviteURIError) Error() string { + return e.Message +} + +type InvalidProxyError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e InvalidProxyError) Error() string { + return e.Message +} + +type InvalidRecipientError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e InvalidRecipientError) Error() string { + return e.Message +} + +type InvalidRequestError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e InvalidRequestError) Error() string { + return e.Message +} + +type NoKnownUUIDError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e NoKnownUUIDError) Error() string { + return e.Message +} + +type NoSendPermissionError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e NoSendPermissionError) Error() string { + return e.Message +} + +type NoSuchAccountError struct { + Account string `json:"account,omitempty" yaml:"account,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e NoSuchAccountError) Error() string { + return e.Message +} + +type NoSuchSessionError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e NoSuchSessionError) Error() string { + return e.Message +} + +type OwnProfileKeyDoesNotExistError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e OwnProfileKeyDoesNotExistError) Error() string { + return e.Message +} + +type ProfileUnavailableError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e ProfileUnavailableError) Error() string { + return e.Message +} + +type ServerNotFoundError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` + UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` +} + +func (e ServerNotFoundError) Error() string { + return e.Message +} + +type UnknownGroupError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e UnknownGroupError) Error() string { + return e.Message +} + +type UnknownIdentityKeyError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e UnknownIdentityKeyError) Error() string { + return e.Message +} + +type UntrustedIdentityError struct { + Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` + IdentityKey **IdentityKey `json:"identity_key,omitempty" yaml:"identity_key,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e UntrustedIdentityError) Error() string { + return e.Message +} + +type UserAlreadyExistsError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` + UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` +} + +func (e UserAlreadyExistsError) Error() string { + return e.Message +} diff --git a/signald/client-protocol/v1/requests.go b/signald/client-protocol/v1/requests.go index e8de4d3..8d6f5b8 100644 --- a/signald/client-protocol/v1/requests.go +++ b/signald/client-protocol/v1/requests.go @@ -4,7 +4,6 @@ package v1 import ( "encoding/json" - "fmt" "log" "gitlab.com/signald/signald-go/signald" @@ -28,7 +27,7 @@ func (r *AcceptInvitationRequest) Submit(conn *signald.Signald) (response JsonGr rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -61,7 +60,7 @@ func (r *AddLinkedDeviceRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -87,7 +86,7 @@ func (r *AddServerRequest) Submit(conn *signald.Signald) (response string, err e rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -120,7 +119,7 @@ func (r *ApproveMembershipRequest) Submit(conn *signald.Signald) (response JsonG rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -152,7 +151,7 @@ func (r *CreateGroupRequest) Submit(conn *signald.Signald) (response JsonGroupV2 rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -185,7 +184,7 @@ func (r *DeleteAccountRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -210,7 +209,7 @@ func (r *RemoveServerRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -236,7 +235,7 @@ func (r *FinishLinkRequest) Submit(conn *signald.Signald) (response Account, err rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -269,7 +268,7 @@ func (r *GenerateLinkingURIRequest) Submit(conn *signald.Signald) (response Link rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -302,7 +301,7 @@ func (r *GetAllIdentities) Submit(conn *signald.Signald) (response AllIdentityKe rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -317,7 +316,7 @@ func (r *GetAllIdentities) Submit(conn *signald.Signald) (response AllIdentityKe } -// Submit: Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupException' is returned. +// Submit: Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupError' is returned. func (r *GetGroupRequest) Submit(conn *signald.Signald) (response JsonGroupV2Info, err error) { r.Version = "v1" r.Type = "get_group" @@ -335,7 +334,7 @@ func (r *GetGroupRequest) Submit(conn *signald.Signald) (response JsonGroupV2Inf rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -368,7 +367,7 @@ func (r *GetIdentitiesRequest) Submit(conn *signald.Signald) (response IdentityK rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -401,7 +400,7 @@ func (r *GetLinkedDevicesRequest) Submit(conn *signald.Signald) (response Linked rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -434,7 +433,7 @@ func (r *GetProfileRequest) Submit(conn *signald.Signald) (response Profile, err rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -467,7 +466,7 @@ func (r *RemoteConfigRequest) Submit(conn *signald.Signald) (response RemoteConf rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -499,7 +498,7 @@ func (r *GetServersRequest) Submit(conn *signald.Signald) (response ServerList, rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -532,7 +531,7 @@ func (r *GroupLinkInfoRequest) Submit(conn *signald.Signald) (response JsonGroup rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -565,7 +564,7 @@ func (r *JoinGroupRequest) Submit(conn *signald.Signald) (response JsonGroupJoin rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -597,7 +596,7 @@ func (r *LeaveGroupRequest) Submit(conn *signald.Signald) (response GroupInfo, e rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -630,7 +629,7 @@ func (r *ListAccountsRequest) Submit(conn *signald.Signald) (response AccountLis rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -662,7 +661,7 @@ func (r *ListContactsRequest) Submit(conn *signald.Signald) (response ProfileLis rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -694,7 +693,7 @@ func (r *ListGroupsRequest) Submit(conn *signald.Signald) (response GroupList, e rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -726,7 +725,7 @@ func (r *MarkReadRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -752,7 +751,40 @@ func (r *ReactRequest) Submit(conn *signald.Signald) (response SendResponse, err rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) + return + } + + err = json.Unmarshal(rawResponse.Data, &response) + if err != nil { + rawResponseJson, _ := rawResponse.Data.MarshalJSON() + log.Println("signald-go: error unmarshalling response from signald of type", rawResponse.Type, string(rawResponseJson)) + return + } + + return response, nil + +} + +// Submit: deny a request to join a group +func (r *RefuseMembershipRequest) Submit(conn *signald.Signald) (response JsonGroupV2Info, err error) { + r.Version = "v1" + r.Type = "refuse_membership" + if r.ID == "" { + r.ID = signald.GenerateID() + } + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = mkerr(rawResponse) return } @@ -785,7 +817,7 @@ func (r *RegisterRequest) Submit(conn *signald.Signald) (response Account, err e rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -818,7 +850,7 @@ func (r *RemoteDeleteRequest) Submit(conn *signald.Signald) (response SendRespon rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -851,7 +883,7 @@ func (r *RemoveLinkedDeviceRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -877,7 +909,7 @@ func (r *RequestSyncRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -903,7 +935,7 @@ func (r *ResetSessionRequest) Submit(conn *signald.Signald) (response SendRespon rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -918,7 +950,7 @@ func (r *ResetSessionRequest) Submit(conn *signald.Signald) (response SendRespon } -// Submit: Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns +// Submit: Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns. func (r *ResolveAddressRequest) Submit(conn *signald.Signald) (response JsonAddress, err error) { r.Version = "v1" r.Type = "resolve_address" @@ -936,7 +968,7 @@ func (r *ResolveAddressRequest) Submit(conn *signald.Signald) (response JsonAddr rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -968,7 +1000,7 @@ func (r *SendRequest) Submit(conn *signald.Signald) (response SendResponse, err rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1001,7 +1033,7 @@ func (r *SendPaymentRequest) Submit(conn *signald.Signald) (response SendRespons rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1034,7 +1066,7 @@ func (r *SetDeviceNameRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1060,7 +1092,7 @@ func (r *SetExpirationRequest) Submit(conn *signald.Signald) (response SendRespo rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1092,7 +1124,7 @@ func (r *SetProfile) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1118,7 +1150,7 @@ func (r *SubscribeRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1144,7 +1176,7 @@ func (r *TrustRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1170,7 +1202,7 @@ func (r *TypingRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1196,7 +1228,7 @@ func (r *UnsubscribeRequest) Submit(conn *signald.Signald) (err error) { rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1222,7 +1254,7 @@ func (r *UpdateContactRequest) Submit(conn *signald.Signald) (response Profile, rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1255,7 +1287,7 @@ func (r *UpdateGroupRequest) Submit(conn *signald.Signald) (response GroupInfo, rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1288,7 +1320,7 @@ func (r *VerifyRequest) Submit(conn *signald.Signald) (response Account, err err rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } @@ -1320,7 +1352,7 @@ func (r *VersionRequest) Submit(conn *signald.Signald) (response JsonVersionMess rawResponse := <-responseChannel if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + err = mkerr(rawResponse) return } diff --git a/signald/client-protocol/v1/structs.go b/signald/client-protocol/v1/structs.go index 726126c..dd8a1c2 100644 --- a/signald/client-protocol/v1/structs.go +++ b/signald/client-protocol/v1/structs.go @@ -24,6 +24,7 @@ type Account struct { AccountId string `json:"account_id,omitempty" yaml:"account_id,omitempty"` // The primary identifier on the account, included with all requests to signald for this account. Previously called 'username' Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` // The address of this account DeviceId int32 `json:"device_id,omitempty" yaml:"device_id,omitempty"` // The Signal device ID. Official Signal mobile clients (iPhone and Android) have device ID = 1, while linked devices such as Signal Desktop or Signal iPad have higher device IDs. + Pending bool `json:"pending,omitempty" yaml:"pending,omitempty"` // indicates the account has not completed registration } type AccountList struct { @@ -132,7 +133,7 @@ type GetAllIdentities struct { Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with } -// GetGroupRequest: Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupException' is returned. +// GetGroupRequest: Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupError' is returned. type GetGroupRequest struct { Request Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with @@ -254,30 +255,47 @@ type JsonAddress struct { UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` // A UUID, the unique identifier for a particular Signal account. } +// JsonAttachment: represents a file attached to a message. When seding, only `filename` is required. +type JsonAttachment struct { + Blurhash string `json:"blurhash,omitempty" yaml:"blurhash,omitempty"` + Caption string `json:"caption,omitempty" yaml:"caption,omitempty"` + ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"` + CustomFilename string `json:"customFilename,omitempty" yaml:"customFilename,omitempty"` // the original name of the file + Digest string `json:"digest,omitempty" yaml:"digest,omitempty"` + Filename string `json:"filename,omitempty" yaml:"filename,omitempty"` // when sending, the path to the local file to upload + Height int32 `json:"height,omitempty" yaml:"height,omitempty"` + ID string `json:"id,omitempty" yaml:"id,omitempty"` + Key string `json:"key,omitempty" yaml:"key,omitempty"` + Size int32 `json:"size,omitempty" yaml:"size,omitempty"` + StoredFilename string `json:"storedFilename,omitempty" yaml:"storedFilename,omitempty"` // when receiving, the path that file has been downloaded to + VoiceNote bool `json:"voiceNote,omitempty" yaml:"voiceNote,omitempty"` + Width int32 `json:"width,omitempty" yaml:"width,omitempty"` +} + type JsonBlockedListMessage struct { Addresses []*JsonAddress `json:"addresses,omitempty" yaml:"addresses,omitempty"` GroupIds []string `json:"groupIds,omitempty" yaml:"groupIds,omitempty"` } type JsonDataMessage struct { - Attachments []*v0.JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` // files attached to the incoming message - Body string `json:"body,omitempty" yaml:"body,omitempty"` // the text body of the incoming message. - Contacts []*v0.SharedContact `json:"contacts,omitempty" yaml:"contacts,omitempty"` // if the incoming message has a shared contact, the contact's information will be here - EndSession bool `json:"endSession,omitempty" yaml:"endSession,omitempty"` - ExpiresInSeconds int32 `json:"expiresInSeconds,omitempty" yaml:"expiresInSeconds,omitempty"` // the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds - Group *JsonGroupInfo `json:"group,omitempty" yaml:"group,omitempty"` // if the incoming message was sent to a v1 group, information about that group will be here - GroupV2 *JsonGroupV2Info `json:"groupV2,omitempty" yaml:"groupV2,omitempty"` // if the incoming message was sent to a v2 group, basic identifying information about that group will be here. If group information changes, JsonGroupV2Info.revision is incremented. If the group revision is higher than previously seen, a client can retrieve the group information by calling get_group. - GroupCallUpdate string `json:"group_call_update,omitempty" yaml:"group_call_update,omitempty"` // the eraId string from a group call message update - Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` // list of mentions in the message - Payment *Payment `json:"payment,omitempty" yaml:"payment,omitempty"` // details about the MobileCoin payment attached to the message, if present - Previews []*v0.JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"` // if the incoming message has a link preview, information about that preview will be here - ProfileKeyUpdate bool `json:"profileKeyUpdate,omitempty" yaml:"profileKeyUpdate,omitempty"` - Quote *JsonQuote `json:"quote,omitempty" yaml:"quote,omitempty"` // if the incoming message is a quote or reply to another message, this will contain information about that message - Reaction *JsonReaction `json:"reaction,omitempty" yaml:"reaction,omitempty"` // if the message adds or removes a reaction to another message, this will indicate what change is being made - RemoteDelete *RemoteDelete `json:"remoteDelete,omitempty" yaml:"remoteDelete,omitempty"` // if the inbound message is deleting a previously sent message, indicates which message should be deleted - Sticker *v0.JsonSticker `json:"sticker,omitempty" yaml:"sticker,omitempty"` // if the incoming message is a sticker, information about the sicker will be here - Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` // the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes. - ViewOnce bool `json:"viewOnce,omitempty" yaml:"viewOnce,omitempty"` // indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image. + Attachments []*JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` // files attached to the incoming message + Body string `json:"body,omitempty" yaml:"body,omitempty"` // the text body of the incoming message. + Contacts []*v0.SharedContact `json:"contacts,omitempty" yaml:"contacts,omitempty"` // if the incoming message has a shared contact, the contact's information will be here + EndSession bool `json:"endSession,omitempty" yaml:"endSession,omitempty"` + ExpiresInSeconds int32 `json:"expiresInSeconds,omitempty" yaml:"expiresInSeconds,omitempty"` // the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds + Group *JsonGroupInfo `json:"group,omitempty" yaml:"group,omitempty"` // if the incoming message was sent to a v1 group, information about that group will be here + GroupV2 *JsonGroupV2Info `json:"groupV2,omitempty" yaml:"groupV2,omitempty"` // if the incoming message was sent to a v2 group, basic identifying information about that group will be here. If group information changes, JsonGroupV2Info.revision is incremented. If the group revision is higher than previously seen, a client can retrieve the group information by calling get_group. + GroupCallUpdate string `json:"group_call_update,omitempty" yaml:"group_call_update,omitempty"` // the eraId string from a group call message update + Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` // list of mentions in the message + Payment *Payment `json:"payment,omitempty" yaml:"payment,omitempty"` // details about the MobileCoin payment attached to the message, if present + Previews []*JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"` // if the incoming message has a link preview, information about that preview will be here + ProfileKeyUpdate bool `json:"profileKeyUpdate,omitempty" yaml:"profileKeyUpdate,omitempty"` + Quote *JsonQuote `json:"quote,omitempty" yaml:"quote,omitempty"` // if the incoming message is a quote or reply to another message, this will contain information about that message + Reaction *JsonReaction `json:"reaction,omitempty" yaml:"reaction,omitempty"` // if the message adds or removes a reaction to another message, this will indicate what change is being made + RemoteDelete *RemoteDelete `json:"remoteDelete,omitempty" yaml:"remoteDelete,omitempty"` // if the inbound message is deleting a previously sent message, indicates which message should be deleted + Sticker *v0.JsonSticker `json:"sticker,omitempty" yaml:"sticker,omitempty"` // if the incoming message is a sticker, information about the sicker will be here + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` // the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes. + ViewOnce bool `json:"viewOnce,omitempty" yaml:"viewOnce,omitempty"` // indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image. } // JsonGroupInfo: information about a legacy group @@ -302,6 +320,7 @@ type JsonGroupJoinInfo struct { // JsonGroupV2Info: Information about a Signal group type JsonGroupV2Info struct { AccessControl *GroupAccessControl `json:"accessControl,omitempty" yaml:"accessControl,omitempty"` // current access control settings for this group + Announcements string `json:"announcements,omitempty" yaml:"announcements,omitempty"` // indicates if the group is an announcements group. Only admins are allowed to send messages to announcements groups. Options are UNKNOWN, ENABLED or DISABLED Avatar string `json:"avatar,omitempty" yaml:"avatar,omitempty"` // path to the group's avatar on local disk, if available Description string `json:"description,omitempty" yaml:"description,omitempty"` ID string `json:"id,omitempty" yaml:"id,omitempty"` @@ -349,6 +368,15 @@ type JsonMessageRequestResponseMessage struct { Type string `json:"type,omitempty" yaml:"type,omitempty"` } +// JsonPreview: metadata about one of the links in a message +type JsonPreview struct { + Attachment *JsonAttachment `json:"attachment,omitempty" yaml:"attachment,omitempty"` // an optional image file attached to the preview + Date int64 `json:"date,omitempty" yaml:"date,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Url string `json:"url,omitempty" yaml:"url,omitempty"` +} + // JsonQuote: A quote is a reply to a previous message. ID is the sent time of the message being replied to type JsonQuote struct { Attachments []*v0.JsonQuotedAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` // list of files attached to the quoted message @@ -374,7 +402,7 @@ type JsonSendMessageResult struct { Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` IdentityFailure string `json:"identityFailure,omitempty" yaml:"identityFailure,omitempty"` NetworkFailure bool `json:"networkFailure,omitempty" yaml:"networkFailure,omitempty"` - Success *v0.Success `json:"success,omitempty" yaml:"success,omitempty"` + Success *SendSuccess `json:"success,omitempty" yaml:"success,omitempty"` UnregisteredFailure bool `json:"unregisteredFailure,omitempty" yaml:"unregisteredFailure,omitempty"` } @@ -390,10 +418,10 @@ type JsonSentTranscriptMessage struct { type JsonSyncMessage struct { BlockedList *JsonBlockedListMessage `json:"blockedList,omitempty" yaml:"blockedList,omitempty"` Configuration *v0.ConfigurationMessage `json:"configuration,omitempty" yaml:"configuration,omitempty"` - Contacts *v0.JsonAttachment `json:"contacts,omitempty" yaml:"contacts,omitempty"` + Contacts *JsonAttachment `json:"contacts,omitempty" yaml:"contacts,omitempty"` ContactsComplete bool `json:"contactsComplete,omitempty" yaml:"contactsComplete,omitempty"` FetchType string `json:"fetchType,omitempty" yaml:"fetchType,omitempty"` - Groups *v0.JsonAttachment `json:"groups,omitempty" yaml:"groups,omitempty"` + Groups *JsonAttachment `json:"groups,omitempty" yaml:"groups,omitempty"` MessageRequestResponse *JsonMessageRequestResponseMessage `json:"messageRequestResponse,omitempty" yaml:"messageRequestResponse,omitempty"` ReadMessages []*JsonReadMessage `json:"readMessages,omitempty" yaml:"readMessages,omitempty"` Request string `json:"request,omitempty" yaml:"request,omitempty"` @@ -514,6 +542,14 @@ type ReceiptMessage struct { When int64 `json:"when,omitempty" yaml:"when,omitempty"` } +// RefuseMembershipRequest: deny a request to join a group +type RefuseMembershipRequest struct { + Request + Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with + GroupId string `json:"group_id,omitempty" yaml:"group_id,omitempty"` + Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // list of requesting members to refuse +} + // RegisterRequest: begin the account registration process by requesting a phone number verification code. when the code is received, submit it with a verify request type RegisterRequest struct { Request @@ -582,7 +618,7 @@ type ResetSessionRequest struct { Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` } -// ResolveAddressRequest: Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns +// ResolveAddressRequest: Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns. type ResolveAddressRequest struct { Request Account string `json:"account,omitempty" yaml:"account,omitempty"` // The signal account to use @@ -603,6 +639,7 @@ type SendRequest struct { Attachments []*v0.JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` MessageBody string `json:"messageBody,omitempty" yaml:"messageBody,omitempty"` + Previews []*JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"` Quote *JsonQuote `json:"quote,omitempty" yaml:"quote,omitempty"` RecipientAddress *JsonAddress `json:"recipientAddress,omitempty" yaml:"recipientAddress,omitempty"` RecipientGroupID string `json:"recipientGroupId,omitempty" yaml:"recipientGroupId,omitempty"` @@ -615,18 +652,30 @@ type SendResponse struct { Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` } +type SendSuccess struct { + Devices []int32 `json:"devices,omitempty" yaml:"devices,omitempty"` + Duration int64 `json:"duration,omitempty" yaml:"duration,omitempty"` + NeedsSync bool `json:"needsSync,omitempty" yaml:"needsSync,omitempty"` + Unidentified bool `json:"unidentified,omitempty" yaml:"unidentified,omitempty"` +} + // Server: a Signal server type Server struct { - Ca string `json:"ca,omitempty" yaml:"ca,omitempty"` + Ca string `json:"ca,omitempty" yaml:"ca,omitempty"` // base64 encoded trust store, password must be 'whisper' CdnUrls []*ServerCDN `json:"cdn_urls,omitempty" yaml:"cdn_urls,omitempty"` + CdsMrenclave string `json:"cds_mrenclave,omitempty" yaml:"cds_mrenclave,omitempty"` ContactDiscoveryUrl string `json:"contact_discovery_url,omitempty" yaml:"contact_discovery_url,omitempty"` + IasCa string `json:"ias_ca,omitempty" yaml:"ias_ca,omitempty"` // base64 encoded trust store, password must be 'whisper' + KeyBackupMrenclave string `json:"key_backup_mrenclave,omitempty" yaml:"key_backup_mrenclave,omitempty"` + KeyBackupServiceId string `json:"key_backup_service_id,omitempty" yaml:"key_backup_service_id,omitempty"` // base64 encoded + KeyBackupServiceName string `json:"key_backup_service_name,omitempty" yaml:"key_backup_service_name,omitempty"` KeyBackupUrl string `json:"key_backup_url,omitempty" yaml:"key_backup_url,omitempty"` Proxy string `json:"proxy,omitempty" yaml:"proxy,omitempty"` ServiceUrl string `json:"service_url,omitempty" yaml:"service_url,omitempty"` StorageUrl string `json:"storage_url,omitempty" yaml:"storage_url,omitempty"` - UnidentifiedSenderRoot string `json:"unidentified_sender_root,omitempty" yaml:"unidentified_sender_root,omitempty"` - UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` // A unique identifier for the server, referenced when adding accounts. Must be a valid UUID. Will be generated if not specified when creating. - ZkParam string `json:"zk_param,omitempty" yaml:"zk_param,omitempty"` // base64 encoded ZKGROUP_SERVER_PUBLIC_PARAMS value + UnidentifiedSenderRoot string `json:"unidentified_sender_root,omitempty" yaml:"unidentified_sender_root,omitempty"` // base64 encoded + UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` // A unique identifier for the server, referenced when adding accounts. Must be a valid UUID. Will be generated if not specified when creating. + ZkParam string `json:"zk_param,omitempty" yaml:"zk_param,omitempty"` // base64 encoded ZKGROUP_SERVER_PUBLIC_PARAMS value } type ServerCDN struct { @@ -717,6 +766,7 @@ type UpdateGroupRequest struct { Request Account string `json:"account,omitempty" yaml:"account,omitempty"` // The identifier of the account to interact with AddMembers []*JsonAddress `json:"addMembers,omitempty" yaml:"addMembers,omitempty"` + Announcements string `json:"announcements,omitempty" yaml:"announcements,omitempty"` // ENABLED to only allow admins to post messages, DISABLED to allow anyone to post Avatar string `json:"avatar,omitempty" yaml:"avatar,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` // A new group description. Set to empty string to remove an existing description. GroupID string `json:"groupID,omitempty" yaml:"groupID,omitempty"` // the ID of the group to update diff --git a/signald/signald.go b/signald/signald.go index d215dde..c8a4897 100644 --- a/signald/signald.go +++ b/signald/signald.go @@ -17,8 +17,8 @@ package signald import ( "bytes" + "context" "encoding/json" - "fmt" "io" "log" "math/rand" @@ -28,6 +28,7 @@ import ( "strings" "time" + client_protocol "gitlab.com/signald/signald-go/signald/client-protocol" "gitlab.com/signald/signald-go/signald/client-protocol/v0" ) @@ -51,17 +52,10 @@ func init() { // Signald is a connection to a signald instance. type Signald struct { socket net.Conn - listeners map[string]chan BasicResponse + listeners map[string]chan client_protocol.BasicResponse SocketPath string } -type BasicResponse struct { - ID string - Type string - Error json.RawMessage - Data json.RawMessage -} - type UnexpectedError struct { Message string } @@ -91,10 +85,17 @@ func (s *Signald) Connect() error { } func (s *Signald) connect() error { - socket, err := net.Dial("unix", s.SocketPath) + var d net.Dialer + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + addr := net.UnixAddr{Name: s.SocketPath, Net: "unix"} + socket, err := d.DialContext(ctx, "unix", addr.String()) if err != nil { return err } + s.socket = socket return nil } @@ -146,13 +147,13 @@ func (s *Signald) RawRequest(request interface{}) error { return json.NewEncoder(s.socket).Encode(request) } -func (s *Signald) GetResponseListener(requestid string) chan BasicResponse { +func (s *Signald) GetResponseListener(requestid string) chan client_protocol.BasicResponse { if s.listeners == nil { - s.listeners = map[string]chan BasicResponse{} + s.listeners = map[string]chan client_protocol.BasicResponse{} } c, ok := s.listeners[requestid] if !ok { - c = make(chan BasicResponse) + c = make(chan client_protocol.BasicResponse) s.listeners[requestid] = c } return c @@ -167,7 +168,7 @@ func (s *Signald) CloseResponseListener(requestid string) { delete(s.listeners, requestid) } -func (s *Signald) readNext() (b BasicResponse, err error) { +func (s *Signald) readNext() (b client_protocol.BasicResponse, err error) { if debugSignaldIO { buffer := bytes.Buffer{} err = json.NewDecoder(io.TeeReader(s.socket, &buffer)).Decode(&b) @@ -181,10 +182,3 @@ func (s *Signald) readNext() (b BasicResponse, err error) { } return } - -func (b BasicResponse) GetError() error { - if b.Error == nil { - return nil - } - return fmt.Errorf("signald error: %s", string(b.Error)) -} diff --git a/tools/generator/errors.go.tmpl b/tools/generator/errors.go.tmpl new file mode 100644 index 0000000..69e2cdc --- /dev/null +++ b/tools/generator/errors.go.tmpl @@ -0,0 +1,37 @@ +package {{.Version}} + +import ( + "encoding/json" + "fmt" + + client_protocol "gitlab.com/signald/signald-go/signald/client-protocol" +) + +// DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo + +func mkerr(response client_protocol.BasicResponse) error { + switch response.ErrorType { {{ range $structName, $type := .Types }} + case "{{ $structName }}": + result := {{ $structName }}{} + err := json.Unmarshal(response.Error, result) + if err != nil { + return err + } + return result{{ end }} + default: + return fmt.Errorf("unexpected response type from signald: %s: %s", response.ErrorType, string(response.Error)) + } +} + +{{ range $structName, $type := .Types }}{{if ne $type.Doc ""}}// {{$structName}}: {{$type.Doc}}{{end}} +type {{ $structName }} struct { +{{if $type.Request}} Request{{end}} +{{ range $fieldName, $field := $type.Fields }}{{ $field.FieldName }} {{if $field.List}}[]{{end}}{{ $field.Type }} `json:"{{$fieldName}},omitempty" yaml:"{{$fieldName}},omitempty"`{{if ne $field.Doc ""}} // {{$field.Doc}}{{end}} +{{ end }} +} + +func (e {{ $structName }}) Error() string { + return e.Message +} + +{{ end }} diff --git a/tools/generator/main.go b/tools/generator/main.go index 67503e4..a68a78a 100644 --- a/tools/generator/main.go +++ b/tools/generator/main.go @@ -19,6 +19,7 @@ type Type struct { Fields map[string]*DataType Request bool `json:"-"` Doc string + Error bool } type DataType struct { @@ -76,10 +77,12 @@ func (d *DataType) fixForVersion(field, version string) { } d.Type = response } else { - if d.Version == version || d.Version == "" { - d.Type = fmt.Sprintf("*%s", d.Type) - } else { - d.Type = fmt.Sprintf("*%s.%s", d.Version, d.Type) + if d.Version != "" { + if d.Version == version { + d.Type = fmt.Sprintf("*%s", d.Type) + } else { + d.Type = fmt.Sprintf("*%s.%s", d.Version, d.Type) + } } } @@ -145,7 +148,10 @@ func main() { } for version, types := range response.Types { - inputs := StructsTemplateInput{Version: version} + inputs := StructsTemplateInput{ + Version: version, + Types: make(map[string]*Type), + } for typeName, t := range types { for fieldName, field := range t.Fields { types[typeName].Fields[fieldName].fixForVersion(fieldName, version) @@ -162,8 +168,10 @@ func main() { } } } + if !t.Error { + inputs.Types[typeName] = t + } } - inputs.Types = types outputDir := fmt.Sprintf("signald/client-protocol/%s", version) err = os.MkdirAll(outputDir, os.ModePerm) if err != nil { @@ -185,4 +193,56 @@ func main() { } fmt.Println(outputFilename) } + + // errors + for version, types := range response.Types { + inputs := StructsTemplateInput{ + Version: version, + Types: make(map[string]*Type), + } + for typeName, t := range types { + for fieldName, field := range t.Fields { + types[typeName].Fields[fieldName].fixForVersion(fieldName, version) + if field.Version != "" && field.Version != version { + found := false + for _, v := range inputs.ImportVersions { + if v == field.Version { + found = true + break + } + } + if !found { + inputs.ImportVersions = append(inputs.ImportVersions, field.Version) + } + } + } + if t.Error { + inputs.Types[typeName] = t + } + } + if len(inputs.Types) == 0 { + continue + } + outputDir := fmt.Sprintf("signald/client-protocol/%s", version) + err = os.MkdirAll(outputDir, os.ModePerm) + if err != nil { + log.Fatal("Error creating", outputDir, err) + } + outputFilename := fmt.Sprintf("%s/%s", outputDir, "errors.go") + log.Println("Opening", outputFilename) + f, err := os.Create(outputFilename) + if err != nil { + log.Fatal(err, "\nfailed to open output file ", outputFilename) + } + err = tmpl.ExecuteTemplate(f, "errors.go.tmpl", inputs) + if err != nil { + log.Fatal(err, "\nfailed to render template") + } + err = exec.Command("gofmt", "-w", outputFilename).Run() + if err != nil { + log.Fatal(err, " error running gofmt on ", outputFilename) + } + fmt.Println(outputFilename) + } + } diff --git a/tools/generator/requests.go.tmpl b/tools/generator/requests.go.tmpl index 7f2967f..d706259 100644 --- a/tools/generator/requests.go.tmpl +++ b/tools/generator/requests.go.tmpl @@ -4,7 +4,6 @@ package {{.Version}} import ({{if .Responses}} "encoding/json" - "fmt" "log"{{end}} "gitlab.com/signald/signald-go/signald" @@ -33,10 +32,10 @@ func {{$action.FnName}}(conn *signald.Signald) ({{if ne $action.Response ""}}res defer conn.CloseResponseListener(r.ID) rawResponse := <- responseChannel - if rawResponse.Error != nil { - err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) - return - } + if rawResponse.Error != nil { + err = mkerr(rawResponse) + return + } {{if ne $action.Response ""}} err = json.Unmarshal(rawResponse.Data, &response) From 25050d2f0eaf8723a020304e1a579fbf98de253d Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 30 Sep 2021 00:41:56 -0700 Subject: [PATCH 27/73] fix unmarshal argument pointer --- signald/client-protocol/v1/errors.go | 62 ++++++++++++++-------------- tools/generator/errors.go.tmpl | 6 +-- tools/generator/main.go | 8 +++- tools/generator/requests.go.tmpl | 2 +- tools/generator/structs.go.tmpl | 3 +- 5 files changed, 44 insertions(+), 37 deletions(-) diff --git a/signald/client-protocol/v1/errors.go b/signald/client-protocol/v1/errors.go index 9778bd6..0cf7335 100644 --- a/signald/client-protocol/v1/errors.go +++ b/signald/client-protocol/v1/errors.go @@ -1,5 +1,7 @@ package v1 +// DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo + import ( "encoding/json" "fmt" @@ -7,209 +9,207 @@ import ( client_protocol "gitlab.com/signald/signald-go/signald/client-protocol" ) -// DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo - func mkerr(response client_protocol.BasicResponse) error { switch response.ErrorType { case "AccountAlreadyVerifiedError": result := AccountAlreadyVerifiedError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "AccountHasNoKeysError": result := AccountHasNoKeysError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "AccountLockedError": result := AccountLockedError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "CaptchaRequiredError": result := CaptchaRequiredError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "FingerprintVersionMismatchError": result := FingerprintVersionMismatchError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "GroupLinkNotActiveError": result := GroupLinkNotActiveError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "GroupNotActiveError": result := GroupNotActiveError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "GroupVerificationError": result := GroupVerificationError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "InternalError": result := InternalError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "InvalidAttachmentError": result := InvalidAttachmentError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "InvalidBase64Error": result := InvalidBase64Error{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "InvalidFingerprintError": result := InvalidFingerprintError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "InvalidGroupError": result := InvalidGroupError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "InvalidGroupStateError": result := InvalidGroupStateError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "InvalidInviteURIError": result := InvalidInviteURIError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "InvalidProxyError": result := InvalidProxyError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "InvalidRecipientError": result := InvalidRecipientError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "InvalidRequestError": result := InvalidRequestError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "NoKnownUUIDError": result := NoKnownUUIDError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "NoSendPermissionError": result := NoSendPermissionError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "NoSuchAccountError": result := NoSuchAccountError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "NoSuchSessionError": result := NoSuchSessionError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "OwnProfileKeyDoesNotExistError": result := OwnProfileKeyDoesNotExistError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "ProfileUnavailableError": result := ProfileUnavailableError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "ServerNotFoundError": result := ServerNotFoundError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "UnknownGroupError": result := UnknownGroupError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "UnknownIdentityKeyError": result := UnknownIdentityKeyError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "UntrustedIdentityError": result := UntrustedIdentityError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } return result case "UserAlreadyExistsError": result := UserAlreadyExistsError{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } diff --git a/tools/generator/errors.go.tmpl b/tools/generator/errors.go.tmpl index 69e2cdc..2ccdaeb 100644 --- a/tools/generator/errors.go.tmpl +++ b/tools/generator/errors.go.tmpl @@ -1,5 +1,7 @@ package {{.Version}} +// {{ .Banner }} + import ( "encoding/json" "fmt" @@ -7,13 +9,11 @@ import ( client_protocol "gitlab.com/signald/signald-go/signald/client-protocol" ) -// DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo - func mkerr(response client_protocol.BasicResponse) error { switch response.ErrorType { {{ range $structName, $type := .Types }} case "{{ $structName }}": result := {{ $structName }}{} - err := json.Unmarshal(response.Error, result) + err := json.Unmarshal(response.Error, &result) if err != nil { return err } diff --git a/tools/generator/main.go b/tools/generator/main.go index a68a78a..cda73a1 100644 --- a/tools/generator/main.go +++ b/tools/generator/main.go @@ -41,12 +41,14 @@ type StructsTemplateInput struct { Types map[string]*Type Version string ImportVersions []string + Banner string } type ActionsTemplateInput struct { Actions map[string]*Action Version string Responses bool + Banner string } var typeMap = map[string]string{ @@ -69,6 +71,8 @@ var fieldNameMap = map[string]string{ "gv1-migration": "Gv1Migration", } +var banner = "DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo" + func (d *DataType) fixForVersion(field, version string) { response, ok := typeMap[d.Type] if ok { @@ -111,7 +115,7 @@ func main() { } for version, actions := range response.Actions { - inputs := ActionsTemplateInput{Version: version, Responses: false} + inputs := ActionsTemplateInput{Version: version, Responses: false, Banner: banner} for action, a := range actions { actions[action].FnName = strings.Title(action) if a.Request != "" { @@ -151,6 +155,7 @@ func main() { inputs := StructsTemplateInput{ Version: version, Types: make(map[string]*Type), + Banner: banner, } for typeName, t := range types { for fieldName, field := range t.Fields { @@ -199,6 +204,7 @@ func main() { inputs := StructsTemplateInput{ Version: version, Types: make(map[string]*Type), + Banner: banner, } for typeName, t := range types { for fieldName, field := range t.Fields { diff --git a/tools/generator/requests.go.tmpl b/tools/generator/requests.go.tmpl index d706259..fe08ac1 100644 --- a/tools/generator/requests.go.tmpl +++ b/tools/generator/requests.go.tmpl @@ -1,6 +1,6 @@ package {{.Version}} -// DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo +// {{ .Banner }} import ({{if .Responses}} "encoding/json" diff --git a/tools/generator/structs.go.tmpl b/tools/generator/structs.go.tmpl index e8091a2..ba5255a 100644 --- a/tools/generator/structs.go.tmpl +++ b/tools/generator/structs.go.tmpl @@ -1,6 +1,7 @@ package {{.Version}} -// DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo +// {{ .Banner }} + {{if gt (.ImportVersions | len) 0}} import ( {{range $version := .ImportVersions}} From a535a63ba0e0cf9089060855a954bc8c372deed9 Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 14 Oct 2021 01:52:39 -0700 Subject: [PATCH 28/73] add signaldctl raw subcommand --- cmd/signaldctl/cmd/raw.go | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 cmd/signaldctl/cmd/raw.go diff --git a/cmd/signaldctl/cmd/raw.go b/cmd/signaldctl/cmd/raw.go new file mode 100644 index 0000000..23cb9e0 --- /dev/null +++ b/cmd/signaldctl/cmd/raw.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/spf13/cobra" + "gitlab.com/signald/signald-go/cmd/signaldctl/common" + "gitlab.com/signald/signald-go/signald" +) + +var rawCmd = &cobra.Command{ + Use: "raw []", + Short: "make a request to signald", + Long: "make a raw request to signald, returning the result. If request data is not provided via command line argument labeled \"json\", it must be provided via stdin", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) < 3 { + return cmd.Help() + } + + var reqdata map[string]interface{} + if err := json.Unmarshal([]byte(args[2]), &reqdata); err != nil { + return err + } + + requestID := signald.GenerateID() + reqdata["id"] = requestID + reqdata["type"] = args[1] + reqdata["version"] = args[0] + + go common.Signald.Listen(nil) + + err := common.Signald.RawRequest(reqdata) + if err != nil { + log.Println("error submitting request to signald") + return err + } + + responseChannel := common.Signald.GetResponseListener(requestID) + defer common.Signald.CloseResponseListener(requestID) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + return fmt.Errorf("signald error: %s", string(rawResponse.Error)) + } + + _, err = os.Stdout.Write(rawResponse.Data) + return err + }, +} + +func init() { + RootCmd.AddCommand(rawCmd) +} From 28b7d16568ca457aa80fa329a00201afd1b66c3c Mon Sep 17 00:00:00 2001 From: Finn Date: Sun, 17 Oct 2021 15:01:20 -0700 Subject: [PATCH 29/73] Fix sending reactions previous implementation had serious limitations. I realize the docs are awkward, i'll look up how to use cobra one of these days --- .../cmd/message/react/react-to-message.go | 45 ++++++++++++------- cmd/signaldctl/common/signald.go | 15 +++++++ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/cmd/signaldctl/cmd/message/react/react-to-message.go b/cmd/signaldctl/cmd/message/react/react-to-message.go index d91c2f3..24ebb15 100644 --- a/cmd/signaldctl/cmd/message/react/react-to-message.go +++ b/cmd/signaldctl/cmd/message/react/react-to-message.go @@ -32,16 +32,25 @@ import ( ) var ( - account string - author v1.JsonAddress - timestamp int64 - emoji string - group string - remove bool + account string + threadAddress *v1.JsonAddress + group string + author v1.JsonAddress + timestamp int64 + emoji string + remove bool ReactMessageCmd = &cobra.Command{ - Use: "react ", + Use: "react thread author timestamp emoji", Short: "react to a message", + Long: `react to a message with a particular emoji + + arguments: + if the message being reacted to is in a 1-on-1 chat, the e164 or UUID of the person the chat is with. if the message is in a group, the group id. + the e164 or UUID of the author of the message being reacted to + the timestamp of the message to react to + the unicode emoji to send as the reaction + `, PreRun: func(cmd *cobra.Command, args []string) { if account == "" { account = config.Config.DefaultAccount @@ -50,19 +59,25 @@ var ( common.Must(cmd.Help()) log.Fatal("No account specified. Please specify with --account or set a default") } - if len(args) != 3 { + if len(args) != 4 { common.Must(cmd.Help()) + log.Fatal("not enough arguments") } + var err error - author, err = common.StringToAddress(args[0]) + threadAddress, group, err = common.StringToAddressOrGroup(args[0]) if err != nil { log.Fatal(err) } - timestamp, err = strconv.ParseInt(args[1], 10, 64) + author, err = common.StringToAddress(args[1]) if err != nil { - log.Fatal("Unable to parse timestamp", args[1], ":", err.Error()) + log.Fatal(err) } - emoji = args[2] + timestamp, err = strconv.ParseInt(args[2], 10, 64) + if err != nil { + log.Fatal("Unable to parse timestamp", args[2], ":", err.Error()) + } + emoji = args[3] }, Run: func(_ *cobra.Command, args []string) { go common.Signald.Listen(nil) @@ -77,10 +92,10 @@ var ( }, } - if group == "" { - req.RecipientAddress = &author + if threadAddress != nil { + req.RecipientAddress = threadAddress } else { - req.RecipientGroupID = group + req.RecipientAddress = &author } resp, err := req.Submit(common.Signald) diff --git a/cmd/signaldctl/common/signald.go b/cmd/signaldctl/common/signald.go index 3db14e8..de2cdc7 100644 --- a/cmd/signaldctl/common/signald.go +++ b/cmd/signaldctl/common/signald.go @@ -64,3 +64,18 @@ func StringToAddress(address string) (v1.JsonAddress, error) { } return v1.JsonAddress{}, NewInvalidAddress(address) } + +func StringToAddressOrGroup(identifier string) (*v1.JsonAddress, string, error) { + // if it starts with a +, assume it's an e164 + if strings.HasPrefix(identifier, "+") { + return &v1.JsonAddress{Number: identifier}, "", nil + } + + // if it parses as a UUID, assume it's a UUID + if _, err := uuid.Parse(identifier); err == nil { + return &v1.JsonAddress{UUID: identifier}, "", nil + } + + // if it doesn't start with a + and doesn't parse as a UUID, assume it's a group + return nil, identifier, nil +} From 924e9c3767d555e51922340e066c2d8da5751be0 Mon Sep 17 00:00:00 2001 From: Finn Date: Tue, 2 Nov 2021 21:26:30 -0700 Subject: [PATCH 30/73] bring protocol up to date --- protocol.json | 303 ++++++++++++++++++++----- signald/client-protocol/v1/errors.go | 69 +++++- signald/client-protocol/v1/requests.go | 25 ++ signald/client-protocol/v1/structs.go | 52 +++-- 4 files changed, 376 insertions(+), 73 deletions(-) diff --git a/protocol.json b/protocol.json index 1905083..68cb778 100644 --- a/protocol.json +++ b/protocol.json @@ -2,9 +2,9 @@ "doc_version": "v1", "version": { "name": "signald", - "version": "0.14.1-48-7d927883", + "version": "0.15.0-23-981b4409", "branch": "main", - "commit": "7d92788343f34c08634abfeda06045ae13e18670" + "commit": "981b44098da8ddd748832597d5f5bde019197902" }, "info": "This document describes objects that may be used when communicating with signald.", "types": { @@ -145,7 +145,20 @@ "type": "boolean" } }, - "doc": "indicates when the incoming connection to the signal server has started or stopped" + "doc": "prior attempt to indicate signald connectivity state. WebSocketConnectionState messages will be delivered at the same time as well as in other parts of the websocket lifecycle." + }, + "WebSocketConnectionState": { + "fields": { + "state": { + "type": "String", + "doc": "One of: DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, DISCONNECTING, AUTHENTICATION_FAILED, FAILED" + }, + "socket": { + "type": "String", + "doc": "One of: UNIDENTIFIED, IDENTIFIED" + } + }, + "doc": "indicates when the websocket connection state to the signal server has changed" }, "ClientMessageWrapper": { "fields": { @@ -164,9 +177,56 @@ "error": { "type": "Boolean", "doc": "true if the incoming message represents an error" + }, + "account": { + "type": "String", + "doc": "the account this message is from" } }, - "doc": "Wraps all incoming messages after a v1 subscribe request is issued" + "doc": "Wraps all incoming messages sent to the client after a v1 subscribe request is issued" + }, + "DuplicateMessageError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, + "ProtocolInvalidMessageError": { + "fields": { + "sender": { + "type": "String" + }, + "message": { + "type": "String" + }, + "sender_device": { + "type": "int" + }, + "content_hint": { + "type": "int" + }, + "group_id": { + "type": "String" + } + }, + "error": true + }, + "UntrustedIdentityError": { + "fields": { + "identifier": { + "type": "String" + }, + "message": { + "type": "String" + }, + "identity_key": { + "type": "IdentityKey", + "version": "v1" + } + }, + "error": true }, "SendRequest": { "fields": { @@ -208,6 +268,12 @@ "list": true, "type": "JsonPreview", "version": "v1" + }, + "members": { + "list": true, + "type": "JsonAddress", + "version": "v1", + "doc": "Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified" } } }, @@ -283,7 +349,15 @@ "type": "String" } }, - "doc": "an internal error in signald has occured.", + "doc": "an internal error in signald has occurred. typically these are things that \"should never happen\" such as issues saving to the local disk, but it is also the default error type and may catch some things that should have their own error type. If you find tht your code is depending on the exception list for any particular behavior, please file an issue so we can pull those errors out to a separate error type: https://gitlab.com/signald/signald/-/issues/new", + "error": true + }, + "InvalidRequestError": { + "fields": { + "message": { + "type": "String" + } + }, "error": true }, "UnknownGroupError": { @@ -294,6 +368,14 @@ }, "error": true }, + "RateLimitError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "InvalidRecipientError": { "fields": { "message": { @@ -324,6 +406,12 @@ }, "timestamp": { "type": "long" + }, + "members": { + "list": true, + "type": "JsonAddress", + "version": "v1", + "doc": "Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified" } }, "doc": "react to a previous message" @@ -339,7 +427,7 @@ }, "version": { "type": "String", - "example": "\"0.14.1-48-7d927883\"" + "example": "\"0.15.0-23-981b4409\"" }, "branch": { "type": "String", @@ -347,7 +435,7 @@ }, "commit": { "type": "String", - "example": "\"7d92788343f34c08634abfeda06045ae13e18670\"" + "example": "\"981b44098da8ddd748832597d5f5bde019197902\"" } } }, @@ -573,14 +661,6 @@ } } }, - "InvalidRequestError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, "InvalidInviteURIError": { "fields": { "message": { @@ -788,21 +868,6 @@ } } }, - "UntrustedIdentityError": { - "fields": { - "identifier": { - "type": "String" - }, - "message": { - "type": "String" - }, - "identity_key": { - "type": "IdentityKey", - "version": "v1" - } - }, - "error": true - }, "GetProfileRequest": { "fields": { "account": { @@ -1058,8 +1123,8 @@ }, "uri": { "type": "String", - "doc": "the tsdevice:/ uri provided (typically in qr code form) by the new device", - "example": "\"tsdevice:/?uuid=jAaZ5lxLfh7zVw5WELd6-Q&pub_key=BfFbjSwmAgpVJBXUdfmSgf61eX3a%2Bq9AoxAVpl1HUap9\"", + "doc": "the sgnl://linkdevice uri provided (typically in qr code form) by the new device", + "example": "\"sgnl://linkdevice?uuid=jAaZ5lxLfh7zVw5WELd6-Q&pub_key=BfFbjSwmAgpVJBXUdfmSgf61eX3a%2Bq9AoxAVpl1HUap9\"", "required": true } }, @@ -1489,6 +1554,12 @@ "timestamp": { "type": "long", "required": true + }, + "members": { + "list": true, + "type": "JsonAddress", + "version": "v1", + "doc": "Optionally set to a sub-set of group members. Ignored if group isn't specified" } }, "doc": "delete a message previously sent" @@ -1590,6 +1661,21 @@ }, "doc": "deny a request to join a group" }, + "SubmitChallengeRequest": { + "fields": { + "account": { + "type": "String", + "required": true + }, + "challenge": { + "type": "String", + "required": true + }, + "captcha_token": { + "type": "String" + } + } + }, "JsonDataMessage": { "fields": { "timestamp": { @@ -1795,6 +1881,26 @@ } } }, + "IdentityKey": { + "fields": { + "added": { + "type": "long", + "doc": "the first time this identity key was seen" + }, + "safety_number": { + "type": "String", + "example": "\"373453558586758076680580548714989751943247272727416091564451\"" + }, + "qr_code_data": { + "type": "String", + "doc": "base64-encoded QR code data" + }, + "trust_level": { + "type": "String", + "doc": "One of TRUSTED_UNVERIFIED, TRUSTED_VERIFIED or UNTRUSTED" + } + } + }, "JsonQuote": { "fields": { "id": { @@ -1888,6 +1994,10 @@ }, "identityFailure": { "type": "String" + }, + "proof_required_failure": { + "type": "ProofRequiredError", + "version": "v1" } } }, @@ -1986,26 +2096,6 @@ }, "doc": "information about a legacy group" }, - "IdentityKey": { - "fields": { - "added": { - "type": "long", - "doc": "the first time this identity key was seen" - }, - "safety_number": { - "type": "String", - "example": "\"373453558586758076680580548714989751943247272727416091564451\"" - }, - "qr_code_data": { - "type": "String", - "doc": "base64-encoded QR code data" - }, - "trust_level": { - "type": "String", - "doc": "One of TRUSTED_UNVERIFIED, TRUSTED_VERIFIED or UNTRUSTED" - } - } - }, "Capabilities": { "fields": { "gv2": { @@ -2334,6 +2424,26 @@ } } }, + "ProofRequiredError": { + "fields": { + "token": { + "type": "String" + }, + "options": { + "list": true, + "type": "String", + "doc": "possible list values are RECAPTCHA and PUSH_CHALLENGE" + }, + "message": { + "type": "String" + }, + "retry_after": { + "type": "long", + "doc": "value in seconds" + } + }, + "error": true + }, "ServerCDN": { "fields": { "number": { @@ -3287,9 +3397,15 @@ { "name": "InternalError" }, + { + "name": "InvalidRequestError" + }, { "name": "UnknownGroupError" }, + { + "name": "RateLimitError" + }, { "name": "InvalidRecipientError" } @@ -3320,6 +3436,12 @@ }, { "name": "UnknownGroupError" + }, + { + "name": "InvalidRequestError" + }, + { + "name": "RateLimitError" } ] }, @@ -3349,6 +3471,9 @@ }, { "name": "InternalError" + }, + { + "name": "InvalidRequestError" } ] }, @@ -3374,6 +3499,9 @@ }, { "name": "GroupVerificationError" + }, + { + "name": "InvalidRequestError" } ] }, @@ -3402,6 +3530,9 @@ }, { "name": "InvalidGroupStateError" + }, + { + "name": "InvalidRequestError" } ] }, @@ -3421,6 +3552,9 @@ }, { "name": "NoSuchAccountError" + }, + { + "name": "InvalidRequestError" } ] }, @@ -3479,6 +3613,9 @@ }, { "name": "NoSuchAccountError" + }, + { + "name": "InvalidRequestError" } ] }, @@ -3527,6 +3664,9 @@ }, { "name": "InvalidBase64Error" + }, + { + "name": "InvalidRequestError" } ] }, @@ -3600,6 +3740,9 @@ }, { "name": "NoSuchAccountError" + }, + { + "name": "InvalidRequestError" } ] }, @@ -3678,6 +3821,9 @@ }, { "name": "GroupVerificationError" + }, + { + "name": "InvalidRequestError" } ] }, @@ -3882,6 +4028,9 @@ }, { "name": "UnknownGroupError" + }, + { + "name": "InvalidRequestError" } ] }, @@ -3894,22 +4043,28 @@ "name": "InternalError" }, { - "name": "InvalidProxyError" + "name": "ServerNotFoundError" }, { - "name": "ServerNotFoundError" + "name": "InvalidProxyError" }, { "name": "NoSuchAccountError" }, { - "name": "InvalidRecipientError" + "name": "InvalidRequestError" }, { "name": "NoSendPermissionError" }, { "name": "UnknownGroupError" + }, + { + "name": "RateLimitError" + }, + { + "name": "InvalidRecipientError" } ] }, @@ -4016,6 +4171,9 @@ }, { "name": "GroupVerificationError" + }, + { + "name": "InvalidRequestError" } ] }, @@ -4111,6 +4269,12 @@ }, { "name": "UnknownGroupError" + }, + { + "name": "InvalidRequestError" + }, + { + "name": "RateLimitError" } ] }, @@ -4172,6 +4336,12 @@ }, { "name": "NoSendPermissionError" + }, + { + "name": "InvalidRequestError" + }, + { + "name": "RateLimitError" } ] }, @@ -4214,6 +4384,29 @@ { "name": "GroupVerificationError" }, + { + "name": "InternalError" + }, + { + "name": "InvalidRequestError" + } + ] + }, + "submit_challenge": { + "request": "SubmitChallengeRequest", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "InvalidRequestError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, { "name": "InternalError" } diff --git a/signald/client-protocol/v1/errors.go b/signald/client-protocol/v1/errors.go index 0cf7335..3081b52 100644 --- a/signald/client-protocol/v1/errors.go +++ b/signald/client-protocol/v1/errors.go @@ -39,6 +39,13 @@ func mkerr(response client_protocol.BasicResponse) error { return err } return result + case "DuplicateMessageError": + result := DuplicateMessageError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result case "FingerprintVersionMismatchError": result := FingerprintVersionMismatchError{} err := json.Unmarshal(response.Error, &result) @@ -179,6 +186,27 @@ func mkerr(response client_protocol.BasicResponse) error { return err } return result + case "ProofRequiredError": + result := ProofRequiredError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result + case "ProtocolInvalidMessageError": + result := ProtocolInvalidMessageError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result + case "RateLimitError": + result := RateLimitError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result case "ServerNotFoundError": result := ServerNotFoundError{} err := json.Unmarshal(response.Error, &result) @@ -253,6 +281,14 @@ func (e CaptchaRequiredError) Error() string { return e.Message } +type DuplicateMessageError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e DuplicateMessageError) Error() string { + return e.Message +} + type FingerprintVersionMismatchError struct { Message string `json:"message,omitempty" yaml:"message,omitempty"` } @@ -285,7 +321,7 @@ func (e GroupVerificationError) Error() string { return e.Message } -// InternalError: an internal error in signald has occured. +// InternalError: an internal error in signald has occurred. typically these are things that "should never happen" such as issues saving to the local disk, but it is also the default error type and may catch some things that should have their own error type. If you find tht your code is depending on the exception list for any particular behavior, please file an issue so we can pull those errors out to a separate error type: https://gitlab.com/signald/signald/-/issues/new type InternalError struct { Exceptions []string `json:"exceptions,omitempty" yaml:"exceptions,omitempty"` Message string `json:"message,omitempty" yaml:"message,omitempty"` @@ -417,6 +453,37 @@ func (e ProfileUnavailableError) Error() string { return e.Message } +type ProofRequiredError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` + Options []string `json:"options,omitempty" yaml:"options,omitempty"` // possible list values are RECAPTCHA and PUSH_CHALLENGE + RetryAfter int64 `json:"retry_after,omitempty" yaml:"retry_after,omitempty"` // value in seconds + Token string `json:"token,omitempty" yaml:"token,omitempty"` +} + +func (e ProofRequiredError) Error() string { + return e.Message +} + +type ProtocolInvalidMessageError struct { + ContentHint int32 `json:"content_hint,omitempty" yaml:"content_hint,omitempty"` + GroupId string `json:"group_id,omitempty" yaml:"group_id,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` + Sender string `json:"sender,omitempty" yaml:"sender,omitempty"` + SenderDevice int32 `json:"sender_device,omitempty" yaml:"sender_device,omitempty"` +} + +func (e ProtocolInvalidMessageError) Error() string { + return e.Message +} + +type RateLimitError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e RateLimitError) Error() string { + return e.Message +} + type ServerNotFoundError struct { Message string `json:"message,omitempty" yaml:"message,omitempty"` UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` diff --git a/signald/client-protocol/v1/requests.go b/signald/client-protocol/v1/requests.go index 8d6f5b8..5a38921 100644 --- a/signald/client-protocol/v1/requests.go +++ b/signald/client-protocol/v1/requests.go @@ -1132,6 +1132,31 @@ func (r *SetProfile) Submit(conn *signald.Signald) (err error) { } +func (r *SubmitChallengeRequest) Submit(conn *signald.Signald) (err error) { + r.Version = "v1" + r.Type = "submit_challenge" + if r.ID == "" { + r.ID = signald.GenerateID() + } + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = mkerr(rawResponse) + return + } + + return err + +} + // Submit: receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages. func (r *SubscribeRequest) Submit(conn *signald.Signald) (err error) { r.Version = "v1" diff --git a/signald/client-protocol/v1/structs.go b/signald/client-protocol/v1/structs.go index dd8a1c2..38d30ea 100644 --- a/signald/client-protocol/v1/structs.go +++ b/signald/client-protocol/v1/structs.go @@ -35,7 +35,7 @@ type AccountList struct { type AddLinkedDeviceRequest struct { Request Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with - Uri string `json:"uri,omitempty" yaml:"uri,omitempty"` // the tsdevice:/ uri provided (typically in qr code form) by the new device + Uri string `json:"uri,omitempty" yaml:"uri,omitempty"` // the sgnl://linkdevice uri provided (typically in qr code form) by the new device } // AddServerRequest: add a new server to connect to. Returns the new server's UUID. @@ -82,8 +82,9 @@ type Capabilities struct { Storage bool `json:"storage,omitempty" yaml:"storage,omitempty"` } -// ClientMessageWrapper: Wraps all incoming messages after a v1 subscribe request is issued +// ClientMessageWrapper: Wraps all incoming messages sent to the client after a v1 subscribe request is issued type ClientMessageWrapper struct { + Account string `json:"account,omitempty" yaml:"account,omitempty"` // the account this message is from Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // the incoming object. The structure will vary from message to message, see `type` and `version` fields Error bool `json:"error,omitempty" yaml:"error,omitempty"` // true if the incoming message represents an error Type string `json:"type,omitempty" yaml:"type,omitempty"` // the type of object to expect in the `data` field @@ -399,11 +400,12 @@ type JsonReadMessage struct { } type JsonSendMessageResult struct { - Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` - IdentityFailure string `json:"identityFailure,omitempty" yaml:"identityFailure,omitempty"` - NetworkFailure bool `json:"networkFailure,omitempty" yaml:"networkFailure,omitempty"` - Success *SendSuccess `json:"success,omitempty" yaml:"success,omitempty"` - UnregisteredFailure bool `json:"unregisteredFailure,omitempty" yaml:"unregisteredFailure,omitempty"` + Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` + IdentityFailure string `json:"identityFailure,omitempty" yaml:"identityFailure,omitempty"` + NetworkFailure bool `json:"networkFailure,omitempty" yaml:"networkFailure,omitempty"` + ProofRequiredFailure *ProofRequiredError `json:"proof_required_failure,omitempty" yaml:"proof_required_failure,omitempty"` + Success *SendSuccess `json:"success,omitempty" yaml:"success,omitempty"` + UnregisteredFailure bool `json:"unregisteredFailure,omitempty" yaml:"unregisteredFailure,omitempty"` } type JsonSentTranscriptMessage struct { @@ -481,7 +483,7 @@ type ListGroupsRequest struct { Account string `json:"account,omitempty" yaml:"account,omitempty"` } -// ListenerState: indicates when the incoming connection to the signal server has started or stopped +// ListenerState: prior attempt to indicate signald connectivity state. WebSocketConnectionState messages will be delivered at the same time as well as in other parts of the websocket lifecycle. type ListenerState struct { Connected bool `json:"connected,omitempty" yaml:"connected,omitempty"` } @@ -529,11 +531,12 @@ type ProfileList struct { // ReactRequest: react to a previous message type ReactRequest struct { Request - Reaction *JsonReaction `json:"reaction,omitempty" yaml:"reaction,omitempty"` - RecipientAddress *JsonAddress `json:"recipientAddress,omitempty" yaml:"recipientAddress,omitempty"` - RecipientGroupID string `json:"recipientGroupId,omitempty" yaml:"recipientGroupId,omitempty"` - Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` - Username string `json:"username,omitempty" yaml:"username,omitempty"` + Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified + Reaction *JsonReaction `json:"reaction,omitempty" yaml:"reaction,omitempty"` + RecipientAddress *JsonAddress `json:"recipientAddress,omitempty" yaml:"recipientAddress,omitempty"` + RecipientGroupID string `json:"recipientGroupId,omitempty" yaml:"recipientGroupId,omitempty"` + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` + Username string `json:"username,omitempty" yaml:"username,omitempty"` } type ReceiptMessage struct { @@ -582,10 +585,11 @@ type RemoteDelete struct { // RemoteDeleteRequest: delete a message previously sent type RemoteDeleteRequest struct { Request - Account string `json:"account,omitempty" yaml:"account,omitempty"` // the account to use - Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` // the address to send the delete message to. should match address the message to be deleted was sent to. required if group is not set. - Group string `json:"group,omitempty" yaml:"group,omitempty"` // the group to send the delete message to. should match group the message to be deleted was sent to. required if address is not set. - Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` + Account string `json:"account,omitempty" yaml:"account,omitempty"` // the account to use + Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` // the address to send the delete message to. should match address the message to be deleted was sent to. required if group is not set. + Group string `json:"group,omitempty" yaml:"group,omitempty"` // the group to send the delete message to. should match group the message to be deleted was sent to. required if address is not set. + Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // Optionally set to a sub-set of group members. Ignored if group isn't specified + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` } // RemoveLinkedDeviceRequest: Remove a linked device from the Signal account. Only allowed when the local device id is 1 @@ -637,6 +641,7 @@ type SendPaymentRequest struct { type SendRequest struct { Request Attachments []*v0.JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` + Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` MessageBody string `json:"messageBody,omitempty" yaml:"messageBody,omitempty"` Previews []*JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"` @@ -713,6 +718,13 @@ type SetProfile struct { Name string `json:"name,omitempty" yaml:"name,omitempty"` // New profile name. Set to empty string for no profile name } +type SubmitChallengeRequest struct { + Request + Account string `json:"account,omitempty" yaml:"account,omitempty"` + CaptchaToken string `json:"captcha_token,omitempty" yaml:"captcha_token,omitempty"` + Challenge string `json:"challenge,omitempty" yaml:"challenge,omitempty"` +} + // SubscribeRequest: receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages. type SubscribeRequest struct { Request @@ -788,3 +800,9 @@ type VerifyRequest struct { type VersionRequest struct { Request } + +// WebSocketConnectionState: indicates when the websocket connection state to the signal server has changed +type WebSocketConnectionState struct { + Socket string `json:"socket,omitempty" yaml:"socket,omitempty"` // One of: UNIDENTIFIED, IDENTIFIED + State string `json:"state,omitempty" yaml:"state,omitempty"` // One of: DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, DISCONNECTING, AUTHENTICATION_FAILED, FAILED +} From 149d44b70508f21cf1d3c418c9b8ecc4ec408846 Mon Sep 17 00:00:00 2001 From: Finn Date: Sun, 7 Nov 2021 10:18:27 -0800 Subject: [PATCH 31/73] Add an example to signaldctl raw fixes #4 --- cmd/signaldctl/cmd/raw.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/signaldctl/cmd/raw.go b/cmd/signaldctl/cmd/raw.go index 23cb9e0..db10514 100644 --- a/cmd/signaldctl/cmd/raw.go +++ b/cmd/signaldctl/cmd/raw.go @@ -12,9 +12,14 @@ import ( ) var rawCmd = &cobra.Command{ - Use: "raw []", + Use: "raw version type [json]", Short: "make a request to signald", - Long: "make a raw request to signald, returning the result. If request data is not provided via command line argument labeled \"json\", it must be provided via stdin", + Long: `make a raw request to signald, returning the result. If request data is not provided via command line argument labeled \"json\", it must be provided via stdin. + + Example usage: + + signaldctl raw v1 get_profile '{"account": "+12024561414", "address": {"number": "+12024561111"}}' + `, RunE: func(cmd *cobra.Command, args []string) error { if len(args) < 3 { return cmd.Help() From dd56e3d6f746692ea4ab18d42af29f0826cde3be Mon Sep 17 00:00:00 2001 From: Finn Date: Sun, 7 Nov 2021 10:22:25 -0800 Subject: [PATCH 32/73] Fix indentation on signaldctl raw docs closes signald/signald.org#4 --- cmd/signaldctl/cmd/raw.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/signaldctl/cmd/raw.go b/cmd/signaldctl/cmd/raw.go index db10514..0959207 100644 --- a/cmd/signaldctl/cmd/raw.go +++ b/cmd/signaldctl/cmd/raw.go @@ -16,10 +16,9 @@ var rawCmd = &cobra.Command{ Short: "make a request to signald", Long: `make a raw request to signald, returning the result. If request data is not provided via command line argument labeled \"json\", it must be provided via stdin. - Example usage: - - signaldctl raw v1 get_profile '{"account": "+12024561414", "address": {"number": "+12024561111"}}' - `, +Example usage: + + signaldctl raw v1 get_profile '{"account": "+12024561414", "address": {"number": "+12024561111"}}'`, RunE: func(cmd *cobra.Command, args []string) error { if len(args) < 3 { return cmd.Help() From e454d6324919f2025ff62f6789f76720ca9af254 Mon Sep 17 00:00:00 2001 From: finn Date: Mon, 17 Jan 2022 18:15:23 -0800 Subject: [PATCH 33/73] breaking change: Listen() now returns BasicResponse --- signald/client-protocol/protocol.go | 4 +++- signald/signald.go | 10 ++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/signald/client-protocol/protocol.go b/signald/client-protocol/protocol.go index d354c53..e625226 100644 --- a/signald/client-protocol/protocol.go +++ b/signald/client-protocol/protocol.go @@ -2,6 +2,7 @@ package client_protocol import ( "encoding/json" + ) type BasicResponse struct { @@ -10,4 +11,5 @@ type BasicResponse struct { ErrorType string Error json.RawMessage Data json.RawMessage -} + Account string +} \ No newline at end of file diff --git a/signald/signald.go b/signald/signald.go index c8a4897..5a0f9d8 100644 --- a/signald/signald.go +++ b/signald/signald.go @@ -29,7 +29,6 @@ import ( "time" client_protocol "gitlab.com/signald/signald-go/signald/client-protocol" - "gitlab.com/signald/signald-go/signald/client-protocol/v0" ) const ( @@ -101,7 +100,7 @@ func (s *Signald) connect() error { } // Listen listens for events from signald -func (s *Signald) Listen(c chan v0.LegacyResponse) { +func (s *Signald) Listen(c chan client_protocol.BasicResponse) { for { msg, err := s.readNext() if err == io.EOF { @@ -127,12 +126,7 @@ func (s *Signald) Listen(c chan v0.LegacyResponse) { } if c != nil { - legacyResponse := v0.LegacyResponse{ID: msg.ID, Type: msg.Type} - if err := json.Unmarshal(msg.Data, &legacyResponse.Data); err != nil { - log.Println("signald-go receive error: ", err) - } else { - c <- legacyResponse - } + c <- msg } } } From 219f1df6e5ec72ac26b348910fef2986dfeb31ec Mon Sep 17 00:00:00 2001 From: finn Date: Mon, 17 Jan 2022 18:16:56 -0800 Subject: [PATCH 34/73] gofmt no longer automatic on save... will investigate --- signald/client-protocol/protocol.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/signald/client-protocol/protocol.go b/signald/client-protocol/protocol.go index e625226..1403b18 100644 --- a/signald/client-protocol/protocol.go +++ b/signald/client-protocol/protocol.go @@ -2,7 +2,6 @@ package client_protocol import ( "encoding/json" - ) type BasicResponse struct { @@ -11,5 +10,5 @@ type BasicResponse struct { ErrorType string Error json.RawMessage Data json.RawMessage - Account string -} \ No newline at end of file + Account string +} From 946b27917dae0e06de3a81cdd295ae0d78985159 Mon Sep 17 00:00:00 2001 From: finn Date: Wed, 16 Feb 2022 14:52:03 -0500 Subject: [PATCH 35/73] bump protocol --- .../cmd/message/send/send-message.go | 5 +- protocol.json | 2473 ++++++++++------- signald/client-protocol/v1/errors.go | 67 +- signald/client-protocol/v1/requests.go | 65 +- signald/client-protocol/v1/structs.go | 154 +- 5 files changed, 1656 insertions(+), 1108 deletions(-) diff --git a/cmd/signaldctl/cmd/message/send/send-message.go b/cmd/signaldctl/cmd/message/send/send-message.go index f8bf02c..e4f705e 100644 --- a/cmd/signaldctl/cmd/message/send/send-message.go +++ b/cmd/signaldctl/cmd/message/send/send-message.go @@ -29,7 +29,6 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/common" "gitlab.com/signald/signald-go/cmd/signaldctl/config" - v0 "gitlab.com/signald/signald-go/signald/client-protocol/v0" "gitlab.com/signald/signald-go/signald/client-protocol/v1" ) @@ -67,7 +66,7 @@ var ( req := v1.SendRequest{ Username: account, MessageBody: strings.Join(args[1:], " "), - Attachments: []*v0.JsonAttachment{}, + Attachments: []*v1.JsonAttachment{}, RecipientAddress: toAddress, RecipientGroupID: toGroup, } @@ -78,7 +77,7 @@ var ( log.Fatal("error resolving attachment", err) } log.Println(path) - req.Attachments = append(req.Attachments, &v0.JsonAttachment{Filename: path}) + req.Attachments = append(req.Attachments, &v1.JsonAttachment{Filename: path}) } resp, err := req.Submit(common.Signald) diff --git a/protocol.json b/protocol.json index 68cb778..a0ded3f 100644 --- a/protocol.json +++ b/protocol.json @@ -2,13 +2,1013 @@ "doc_version": "v1", "version": { "name": "signald", - "version": "0.15.0-23-981b4409", + "version": "0.17.0", "branch": "main", - "commit": "981b44098da8ddd748832597d5f5bde019197902" + "commit": "3d482a6060b7235a18cdb8a4dc60aba61f99da74" }, "info": "This document describes objects that may be used when communicating with signald.", "types": { + "v0": { + "JsonAccountList": { + "fields": { + "accounts": { + "list": true, + "type": "JsonAccount", + "version": "v0" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonMessageEnvelope": { + "fields": { + "username": { + "type": "String", + "example": "\"+12024561414\"" + }, + "uuid": { + "type": "String", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"" + }, + "source": { + "type": "JsonAddress", + "version": "v0" + }, + "sourceDevice": { + "type": "int" + }, + "type": { + "type": "String" + }, + "relay": { + "type": "String", + "doc": "this field is no longer available and will never be populated" + }, + "timestamp": { + "type": "long", + "example": "1615576442475" + }, + "timestampISO": { + "type": "String" + }, + "serverTimestamp": { + "type": "long" + }, + "serverDeliveredTimestamp": { + "type": "long", + "example": "161557644247580" + }, + "hasLegacyMessage": { + "type": "boolean" + }, + "hasContent": { + "type": "boolean" + }, + "isUnidentifiedSender": { + "type": "boolean" + }, + "dataMessage": { + "type": "JsonDataMessage", + "version": "v0" + }, + "syncMessage": { + "type": "JsonSyncMessage", + "version": "v0" + }, + "callMessage": { + "type": "JsonCallMessage", + "version": "v0" + }, + "receipt": { + "type": "JsonReceiptMessage", + "version": "v0" + }, + "typing": { + "type": "JsonTypingMessage", + "version": "v0" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonAccount": { + "fields": { + "deviceId": { + "type": "int" + }, + "username": { + "type": "String" + }, + "filename": { + "type": "String" + }, + "uuid": { + "type": "String" + }, + "registered": { + "type": "boolean" + }, + "has_keys": { + "type": "boolean" + }, + "subscribed": { + "type": "boolean" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonAddress": { + "fields": { + "number": { + "type": "String" + }, + "uuid": { + "type": "UUID" + }, + "relay": { + "type": "String" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonDataMessage": { + "fields": { + "timestamp": { + "type": "long", + "doc": "the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes.", + "example": "1615576442475" + }, + "attachments": { + "list": true, + "type": "JsonAttachment", + "version": "v0", + "doc": "files attached to the incoming message" + }, + "body": { + "type": "String", + "doc": "the text body of the incoming message.", + "example": "\"hello\"" + }, + "group": { + "type": "JsonGroupInfo", + "version": "v0", + "doc": "if the incoming message was sent to a v1 group, information about that group will be here" + }, + "groupV2": { + "type": "JsonGroupV2Info", + "version": "v0", + "doc": "is the incoming message was sent to a v2 group, basic identifying information about that group will be here. For full information, use list_groups" + }, + "endSession": { + "type": "boolean" + }, + "expiresInSeconds": { + "type": "int", + "doc": "the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds" + }, + "profileKeyUpdate": { + "type": "boolean" + }, + "quote": { + "type": "JsonQuote", + "version": "v0", + "doc": "if the incoming message is a quote or reply to another message, this will contain information about that message" + }, + "contacts": { + "list": true, + "type": "SharedContact", + "version": "v0", + "doc": "if the incoming message has a shared contact, the contact's information will be here" + }, + "previews": { + "list": true, + "type": "JsonPreview", + "version": "v0", + "doc": "if the incoming message has a link preview, information about that preview will be here" + }, + "sticker": { + "type": "JsonSticker", + "version": "v0", + "doc": "if the incoming message is a sticker, information about the sicker will be here" + }, + "viewOnce": { + "type": "boolean", + "doc": "indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image." + }, + "reaction": { + "type": "JsonReaction", + "version": "v0", + "doc": "if the message adds or removes a reaction to another message, this will indicate what change is being made" + }, + "remoteDelete": { + "type": "RemoteDelete", + "version": "v0", + "doc": "if the inbound message is deleting a previously sent message, indicates which message should be deleted" + }, + "mentions": { + "list": true, + "type": "JsonMention", + "version": "v0", + "doc": "list of mentions in the message" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonSyncMessage": { + "fields": { + "sent": { + "type": "JsonSentTranscriptMessage", + "version": "v0" + }, + "contacts": { + "type": "JsonAttachment", + "version": "v0" + }, + "contactsComplete": { + "type": "boolean" + }, + "groups": { + "type": "JsonAttachment", + "version": "v0" + }, + "blockedList": { + "type": "JsonBlockedListMessage", + "version": "v0" + }, + "request": { + "type": "String" + }, + "readMessages": { + "list": true, + "type": "JsonReadMessage", + "version": "v0" + }, + "viewOnceOpen": { + "type": "JsonViewOnceOpenMessage", + "version": "v0" + }, + "verified": { + "type": "JsonVerifiedMessage", + "version": "v0" + }, + "configuration": { + "type": "ConfigurationMessage", + "version": "v0" + }, + "stickerPackOperations": { + "list": true, + "type": "JsonStickerPackOperationMessage", + "version": "v0" + }, + "fetchType": { + "type": "String" + }, + "messageRequestResponse": { + "type": "JsonMessageRequestResponseMessage", + "version": "v0" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonCallMessage": { + "fields": { + "offerMessage": { + "type": "OfferMessage", + "version": "v0" + }, + "answerMessage": { + "type": "AnswerMessage", + "version": "v0" + }, + "busyMessage": { + "type": "BusyMessage", + "version": "v0" + }, + "hangupMessage": { + "type": "HangupMessage", + "version": "v0" + }, + "iceUpdateMessages": { + "list": true, + "type": "IceUpdateMessage", + "version": "v0" + }, + "destinationDeviceId": { + "type": "int" + }, + "isMultiRing": { + "type": "boolean" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonReceiptMessage": { + "fields": { + "type": { + "type": "String" + }, + "timestamps": { + "list": true, + "type": "Long" + }, + "when": { + "type": "long" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonTypingMessage": { + "fields": { + "action": { + "type": "String" + }, + "timestamp": { + "type": "long" + }, + "groupId": { + "type": "String" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonAttachment": { + "fields": { + "contentType": { + "type": "String" + }, + "id": { + "type": "String" + }, + "size": { + "type": "int" + }, + "storedFilename": { + "type": "String" + }, + "filename": { + "type": "String" + }, + "customFilename": { + "type": "String" + }, + "caption": { + "type": "String" + }, + "width": { + "type": "int" + }, + "height": { + "type": "int" + }, + "voiceNote": { + "type": "boolean" + }, + "key": { + "type": "String" + }, + "digest": { + "type": "String" + }, + "blurhash": { + "type": "String" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonGroupInfo": { + "fields": { + "groupId": { + "type": "String" + }, + "members": { + "list": true, + "type": "JsonAddress", + "version": "v0" + }, + "name": { + "type": "String" + }, + "type": { + "type": "String" + }, + "avatarId": { + "type": "long" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonGroupV2Info": { + "fields": { + "id": { + "type": "String", + "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" + }, + "revision": { + "type": "int", + "example": "5" + }, + "title": { + "type": "String", + "example": "\"Parkdale Run Club\"" + }, + "description": { + "type": "String" + }, + "avatar": { + "type": "String", + "doc": "path to the group's avatar on local disk, if available", + "example": "\"/var/lib/signald/avatars/group-EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" + }, + "timer": { + "type": "int", + "example": "604800" + }, + "members": { + "list": true, + "type": "JsonAddress", + "version": "v0" + }, + "pendingMembers": { + "list": true, + "type": "JsonAddress", + "version": "v0" + }, + "requestingMembers": { + "list": true, + "type": "JsonAddress", + "version": "v0" + }, + "inviteLink": { + "type": "String", + "doc": "the signal.group link, if applicable" + }, + "accessControl": { + "type": "GroupAccessControl", + "version": "v0", + "doc": "current access control settings for this group" + }, + "memberDetail": { + "list": true, + "type": "GroupMember", + "version": "v0", + "doc": "detailed member list" + }, + "pendingMemberDetail": { + "list": true, + "type": "GroupMember", + "version": "v0", + "doc": "detailed pending member list" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonQuote": { + "fields": { + "id": { + "type": "long", + "doc": "the client timestamp of the message being quoted", + "example": "1615576442475" + }, + "author": { + "type": "JsonAddress", + "version": "v0", + "doc": "the author of the message being quoted" + }, + "text": { + "type": "String", + "doc": "the body of the message being quoted", + "example": "\"hey ? what's up?\"" + }, + "attachments": { + "list": true, + "type": "JsonQuotedAttachment", + "version": "v0", + "doc": "list of files attached to the quoted message" + }, + "mentions": { + "list": true, + "type": "JsonMention", + "version": "v0", + "doc": "list of mentions in the quoted message" + } + }, + "doc": "A quote is a reply to a previous message. ID is the sent time of the message being replied to", + "deprecated": true, + "removal_date": 1641027661 + }, + "SharedContact": { + "fields": { + "name": { + "type": "Name", + "version": "v0" + }, + "avatar": { + "type": "Optional", + "version": "v0" + }, + "phone": { + "type": "Optional", + "version": "v0" + }, + "email": { + "type": "Optional", + "version": "v0" + }, + "address": { + "type": "Optional", + "version": "v0" + }, + "organization": { + "type": "Optional", + "version": "v0" + } + } + }, + "JsonPreview": { + "fields": { + "url": { + "type": "String" + }, + "title": { + "type": "String" + }, + "attachment": { + "type": "JsonAttachment", + "version": "v0" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonSticker": { + "fields": { + "packID": { + "type": "String" + }, + "packKey": { + "type": "String" + }, + "stickerID": { + "type": "int" + }, + "attachment": { + "type": "JsonAttachment", + "version": "v0" + }, + "image": { + "type": "String" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonReaction": { + "fields": { + "emoji": { + "type": "String", + "doc": "the emoji to react with", + "example": "\"?\"" + }, + "remove": { + "type": "boolean", + "doc": "set to true to remove the reaction. requires emoji be set to previously reacted emoji" + }, + "targetAuthor": { + "type": "JsonAddress", + "version": "v0", + "doc": "the author of the message being reacted to" + }, + "targetSentTimestamp": { + "type": "long", + "doc": "the client timestamp of the message being reacted to", + "example": "1615576442475" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "RemoteDelete": { + "fields": { + "targetSentTimestamp": { + "type": "long" + } + } + }, + "JsonMention": { + "fields": { + "uuid": { + "type": "String", + "doc": "The UUID of the account being mentioned", + "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" + }, + "start": { + "type": "int", + "doc": "The number of characters in that the mention starts at. Note that due to a quirk of how signald encodes JSON, if this value is 0 (for example if the first character in the message is the mention) the field won't show up.", + "example": "4" + }, + "length": { + "type": "int", + "doc": "The length of the mention represented in the message. Seems to always be 1 but included here in case that changes.", + "example": "1" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonSentTranscriptMessage": { + "fields": { + "destination": { + "type": "JsonAddress", + "version": "v0" + }, + "timestamp": { + "type": "long", + "example": "1615576442475" + }, + "expirationStartTimestamp": { + "type": "long" + }, + "message": { + "type": "JsonDataMessage", + "version": "v0" + }, + "unidentifiedStatus": { + "type": "Map" + }, + "isRecipientUpdate": { + "type": "boolean" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonBlockedListMessage": { + "fields": { + "addresses": { + "list": true, + "type": "JsonAddress", + "version": "v0" + }, + "groupIds": { + "list": true, + "type": "String" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonReadMessage": { + "fields": { + "sender": { + "type": "JsonAddress", + "version": "v0" + }, + "timestamp": { + "type": "long", + "example": "1615576442475" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonViewOnceOpenMessage": { + "fields": { + "sender": { + "type": "JsonAddress", + "version": "v0" + }, + "timestamp": { + "type": "long", + "example": "1615576442475" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonVerifiedMessage": { + "fields": { + "destination": { + "type": "JsonAddress", + "version": "v0" + }, + "identityKey": { + "type": "String" + }, + "verified": { + "type": "String" + }, + "timestamp": { + "type": "long" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "ConfigurationMessage": { + "fields": { + "readReceipts": { + "type": "Optional", + "version": "v0" + }, + "unidentifiedDeliveryIndicators": { + "type": "Optional", + "version": "v0" + }, + "typingIndicators": { + "type": "Optional", + "version": "v0" + }, + "linkPreviews": { + "type": "Optional", + "version": "v0" + } + } + }, + "JsonStickerPackOperationMessage": { + "fields": { + "packID": { + "type": "String" + }, + "packKey": { + "type": "String" + }, + "type": { + "type": "String" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "JsonMessageRequestResponseMessage": { + "fields": { + "person": { + "type": "JsonAddress", + "version": "v0" + }, + "groupId": { + "type": "String" + }, + "type": { + "type": "String" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "OfferMessage": { + "fields": { + "id": { + "type": "long" + }, + "sdp": { + "type": "String" + }, + "type": { + "type": "Type", + "version": "v0" + }, + "opaque": { + "type": "String" + } + } + }, + "AnswerMessage": { + "fields": { + "id": { + "type": "long" + }, + "sdp": { + "type": "String" + }, + "opaque": { + "type": "String" + } + } + }, + "BusyMessage": { + "fields": { + "id": { + "type": "long" + } + } + }, + "HangupMessage": { + "fields": { + "id": { + "type": "long" + }, + "type": { + "type": "Type", + "version": "v0" + }, + "deviceId": { + "type": "int" + }, + "legacy": { + "type": "boolean" + } + } + }, + "IceUpdateMessage": { + "fields": { + "id": { + "type": "long" + }, + "opaque": { + "type": "String" + }, + "sdp": { + "type": "String" + } + } + }, + "JsonQuotedAttachment": { + "fields": { + "contentType": { + "type": "String" + }, + "fileName": { + "type": "String" + }, + "thumbnail": { + "type": "JsonAttachment", + "version": "v0" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "GroupAccessControl": { + "fields": { + "link": { + "type": "String", + "doc": "UNSATISFIABLE when the group link is disabled, ADMINISTRATOR when the group link is enabled but an administrator must approve new members, ANY when the group link is enabled and no approval is required", + "example": "\"ANY\"" + }, + "attributes": { + "type": "String", + "doc": "who can edit group info" + }, + "members": { + "type": "String", + "doc": "who can add members" + } + }, + "doc": "group access control settings. Options for each controlled action are: UNKNOWN, ANY, MEMBER, ADMINISTRATOR, UNSATISFIABLE and UNRECOGNIZED", + "deprecated": true, + "removal_date": 1641027661 + }, + "GroupMember": { + "fields": { + "uuid": { + "type": "String", + "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" + }, + "role": { + "type": "String", + "doc": "possible values are: UNKNOWN, DEFAULT, ADMINISTRATOR and UNRECOGNIZED", + "example": "\"DEFAULT\"" + }, + "joined_revision": { + "type": "int" + } + }, + "deprecated": true, + "removal_date": 1641027661 + }, + "Name": { + "fields": { + "display": { + "type": "Optional", + "version": "v0" + }, + "given": { + "type": "Optional", + "version": "v0" + }, + "family": { + "type": "Optional", + "version": "v0" + }, + "prefix": { + "type": "Optional", + "version": "v0" + }, + "suffix": { + "type": "Optional", + "version": "v0" + }, + "middle": { + "type": "Optional", + "version": "v0" + } + } + }, + "Optional": { + "fields": { + "present": { + "type": "boolean" + } + } + }, + "Type": { + "fields": {} + } + }, "v1": { + "ListenerState": { + "fields": { + "connected": { + "type": "boolean" + } + }, + "doc": "prior attempt to indicate signald connectivity state. WebSocketConnectionState messages will be delivered at the same time as well as in other parts of the websocket lifecycle." + }, + "IncomingMessage": { + "fields": { + "account": { + "type": "String", + "example": "\"+12024561414\"" + }, + "source": { + "type": "JsonAddress", + "version": "v1" + }, + "type": { + "type": "String" + }, + "timestamp": { + "type": "long", + "example": "1615576442475" + }, + "source_device": { + "type": "int" + }, + "server_receiver_timestamp": { + "type": "long", + "example": "1615576442475" + }, + "server_deliver_timestamp": { + "type": "long", + "example": "1615576442475" + }, + "has_legacy_message": { + "type": "boolean" + }, + "has_content": { + "type": "boolean" + }, + "unidentified_sender": { + "type": "boolean" + }, + "data_message": { + "type": "JsonDataMessage", + "version": "v1" + }, + "sync_message": { + "type": "JsonSyncMessage", + "version": "v1" + }, + "call_message": { + "type": "CallMessage", + "version": "v1" + }, + "receipt_message": { + "type": "ReceiptMessage", + "version": "v1" + }, + "typing_message": { + "type": "TypingMessage", + "version": "v1" + }, + "server_guid": { + "type": "String" + } + } + }, + "WebSocketConnectionState": { + "fields": { + "state": { + "type": "String", + "doc": "One of: DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, DISCONNECTING, AUTHENTICATION_FAILED, FAILED" + }, + "socket": { + "type": "String", + "doc": "One of: UNIDENTIFIED, IDENTIFIED" + } + }, + "doc": "indicates when the websocket connection state to the signal server has changed" + }, "JsonMessageEnvelope": { "fields": { "username": { @@ -77,89 +1077,6 @@ } } }, - "IncomingMessage": { - "fields": { - "account": { - "type": "String", - "example": "\"+12024561414\"" - }, - "source": { - "type": "JsonAddress", - "version": "v1" - }, - "type": { - "type": "String" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - }, - "source_device": { - "type": "int" - }, - "server_receiver_timestamp": { - "type": "long", - "example": "1615576442475" - }, - "server_deliver_timestamp": { - "type": "long", - "example": "1615576442475" - }, - "has_legacy_message": { - "type": "boolean" - }, - "has_content": { - "type": "boolean" - }, - "unidentified_sender": { - "type": "boolean" - }, - "data_message": { - "type": "JsonDataMessage", - "version": "v1" - }, - "sync_message": { - "type": "JsonSyncMessage", - "version": "v1" - }, - "call_message": { - "type": "CallMessage", - "version": "v1" - }, - "receipt_message": { - "type": "ReceiptMessage", - "version": "v1" - }, - "typing_message": { - "type": "TypingMessage", - "version": "v1" - }, - "server_guid": { - "type": "String" - } - } - }, - "ListenerState": { - "fields": { - "connected": { - "type": "boolean" - } - }, - "doc": "prior attempt to indicate signald connectivity state. WebSocketConnectionState messages will be delivered at the same time as well as in other parts of the websocket lifecycle." - }, - "WebSocketConnectionState": { - "fields": { - "state": { - "type": "String", - "doc": "One of: DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, DISCONNECTING, AUTHENTICATION_FAILED, FAILED" - }, - "socket": { - "type": "String", - "doc": "One of: UNIDENTIFIED, IDENTIFIED" - } - }, - "doc": "indicates when the websocket connection state to the signal server has changed" - }, "ClientMessageWrapper": { "fields": { "type": { @@ -185,10 +1102,17 @@ }, "doc": "Wraps all incoming messages sent to the client after a v1 subscribe request is issued" }, - "DuplicateMessageError": { + "UntrustedIdentityError": { "fields": { + "identifier": { + "type": "String" + }, "message": { "type": "String" + }, + "identity_key": { + "type": "IdentityKey", + "version": "v1" } }, "error": true @@ -198,6 +1122,9 @@ "sender": { "type": "String" }, + "timestamp": { + "type": "long" + }, "message": { "type": "String" }, @@ -213,17 +1140,13 @@ }, "error": true }, - "UntrustedIdentityError": { + "DuplicateMessageError": { "fields": { - "identifier": { - "type": "String" + "timestamp": { + "type": "long" }, "message": { "type": "String" - }, - "identity_key": { - "type": "IdentityKey", - "version": "v1" } }, "error": true @@ -250,7 +1173,7 @@ "attachments": { "list": true, "type": "JsonAttachment", - "version": "v0" + "version": "v1" }, "quote": { "type": "JsonQuote", @@ -384,6 +1307,17 @@ }, "error": true }, + "AttachmentTooLargeError": { + "fields": { + "filename": { + "type": "String" + }, + "message": { + "type": "String" + } + }, + "error": true + }, "ReactRequest": { "fields": { "username": { @@ -416,6 +1350,17 @@ }, "doc": "react to a previous message" }, + "UnregisteredUserError": { + "fields": { + "message": { + "type": "String" + }, + "e164_number": { + "type": "String" + } + }, + "error": true + }, "VersionRequest": { "fields": {} }, @@ -427,7 +1372,7 @@ }, "version": { "type": "String", - "example": "\"0.15.0-23-981b4409\"" + "example": "\"0.17.0\"" }, "branch": { "type": "String", @@ -435,7 +1380,7 @@ }, "commit": { "type": "String", - "example": "\"981b44098da8ddd748832597d5f5bde019197902\"" + "example": "\"3d482a6060b7235a18cdb8a4dc60aba61f99da74\"" } } }, @@ -520,6 +1465,10 @@ "announcements": { "type": "String", "doc": "indicates if the group is an announcements group. Only admins are allowed to send messages to announcements groups. Options are UNKNOWN, ENABLED or DISABLED" + }, + "removed": { + "type": "boolean", + "doc": "will be set to true for incoming messages to indicate the user has been removed from the group" } }, "doc": "Information about a Signal group" @@ -532,6 +1481,15 @@ }, "error": true }, + "AuthorizationFailedError": { + "fields": { + "message": { + "type": "String" + } + }, + "doc": "indicates the server rejected our credentials. Typically means the linked device was removed by the primary device, or that the account was re-registered", + "error": true + }, "ApproveMembershipRequest": { "fields": { "account": { @@ -581,7 +1539,7 @@ "doc": "the latest known revision, default value (-1) forces fetch from server" } }, - "doc": "Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupError' is returned." + "doc": "Query the server for the latest state of a known group. If the account is not a member of the group, an UnknownGroupError is returned." }, "InvalidGroupStateError": { "fields": { @@ -778,9 +1736,8 @@ }, "name": { "type": "String", - "doc": "New profile name. Set to empty string for no profile name", - "example": "\"signald user\"", - "required": true + "doc": "Change the profile name", + "example": "\"signald user\"" }, "avatarFile": { "type": "String", @@ -789,15 +1746,20 @@ }, "about": { "type": "String", - "doc": "an optional about string. If unset, null or an empty string will unset profile about field" + "doc": "Change the 'about' profile field" }, "emoji": { "type": "String", - "doc": "an optional single emoji character. If unset, null or an empty string will unset profile emoji" + "doc": "Change the profile emoji" }, "mobilecoin_address": { "type": "String", - "doc": "an optional *base64-encoded* MobileCoin address to set in the profile. Note that this is not the traditional MobileCoin address encoding, which is custom. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. If unset, null or an empty string, will empty the profile payment address" + "doc": "Change the profile payment address. Payment address must be a *base64-encoded* MobileCoin address. Note that this is not the traditional MobileCoin address encoding, which is custom. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side." + }, + "visible_badge_ids": { + "list": true, + "type": "String", + "doc": "configure visible badge IDs" } } }, @@ -929,6 +1891,11 @@ "mobilecoin_address": { "type": "String", "doc": "*base64-encoded* mobilecoin address. Note that this is not the traditional MobileCoin address encoding. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. If unset, null or an empty string, will empty the profile payment address" + }, + "visible_badge_ids": { + "list": true, + "type": "String", + "doc": "currently unclear how these work, as they are not available in the production Signal apps" } }, "doc": "Information about a Signal user" @@ -1070,7 +2037,7 @@ "type": "String" } }, - "doc": "After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device." + "doc": "After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device and the new account is setup. Note that the account setup process can sometimes take some time, if rapid userfeedback is required after scanning, use wait_for_scan first, then finish setup with finish_link." }, "Account": { "fields": { @@ -1113,6 +2080,14 @@ }, "error": true }, + "ScanTimeoutError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "AddLinkedDeviceRequest": { "fields": { "account": { @@ -1366,12 +2341,6 @@ }, "RequestSyncRequest": { "fields": { - "account": { - "type": "String", - "doc": "The account to use", - "example": "\"+12024561414\"", - "required": true - }, "groups": { "type": "boolean", "doc": "request group sync (default true)" @@ -1387,6 +2356,16 @@ "blocked": { "type": "boolean", "doc": "request block list sync (default true)" + }, + "keys": { + "type": "boolean", + "doc": "request storage service keys" + }, + "account": { + "type": "String", + "doc": "The account to use", + "example": "\"+12024561414\"", + "required": true } }, "doc": "Request other devices on the account send us their group list, syncable config and contact list." @@ -1489,7 +2468,7 @@ "doc": "The device name" } }, - "doc": "set this device's name. This will show up on the mobile device on the same account under " + "doc": "set this device's name. This will show up on the mobile device on the same account under settings -> linked devices" }, "GetAllIdentities": { "fields": { @@ -1676,6 +2655,39 @@ } } }, + "IsIdentifierRegisteredRequest": { + "fields": { + "account": { + "type": "String", + "doc": "The account to use to use", + "example": "\"+12024561414\"", + "required": true + }, + "identifier": { + "type": "String", + "doc": "The UUID of an identifier to check if it is registered on Signal. This UUID is either a Phone Number Identity (PNI) or an Account Identity (ACI).", + "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"", + "required": true + } + }, + "doc": "Determine whether an account identifier is registered on the Signal service." + }, + "BooleanMessage": { + "fields": { + "value": { + "type": "boolean" + } + }, + "doc": "A message containing a single boolean, usually as a response" + }, + "WaitForScanRequest": { + "fields": { + "session_id": { + "type": "String" + } + }, + "doc": "An optional part of the linking process. Intended to be called after displaying the QR code, will return quickly after the user scans the QR code. finish_link must be called after wait_for_scan returns a non-error" + }, "JsonDataMessage": { "fields": { "timestamp": { @@ -1722,7 +2734,7 @@ "contacts": { "list": true, "type": "SharedContact", - "version": "v0", + "version": "v1", "doc": "if the incoming message has a shared contact, the contact's information will be here" }, "previews": { @@ -1901,6 +2913,53 @@ } } }, + "JsonAttachment": { + "fields": { + "contentType": { + "type": "String" + }, + "id": { + "type": "String" + }, + "size": { + "type": "int" + }, + "storedFilename": { + "type": "String", + "doc": "when receiving, the path that file has been downloaded to" + }, + "filename": { + "type": "String", + "doc": "when sending, the path to the local file to upload" + }, + "customFilename": { + "type": "String", + "doc": "the original name of the file" + }, + "caption": { + "type": "String" + }, + "width": { + "type": "int" + }, + "height": { + "type": "int" + }, + "voiceNote": { + "type": "boolean" + }, + "key": { + "type": "String" + }, + "digest": { + "type": "String" + }, + "blurhash": { + "type": "String" + } + }, + "doc": "represents a file attached to a message. When sending, only `filename` is required." + }, "JsonQuote": { "fields": { "id": { @@ -1916,7 +2975,7 @@ "text": { "type": "String", "doc": "the body of the message being quoted", - "example": "\"hey  what's up?\"" + "example": "\"hey ? what's up?\"" }, "attachments": { "list": true, @@ -2006,7 +3065,7 @@ "emoji": { "type": "String", "doc": "the emoji to react with", - "example": "\"👍\"" + "example": "\"?\"" }, "remove": { "type": "boolean", @@ -2106,6 +3165,15 @@ }, "gv1-migration": { "type": "boolean" + }, + "sender_key": { + "type": "boolean" + }, + "announcement_group": { + "type": "boolean" + }, + "change_number": { + "type": "boolean" } } }, @@ -2195,52 +3263,41 @@ }, "doc": "A remote config (feature flag) entry." }, - "JsonAttachment": { + "SharedContact": { "fields": { - "contentType": { - "type": "String" + "name": { + "type": "SharedContactName", + "version": "v1", + "doc": "the name of the shared contact" }, - "id": { - "type": "String" + "email": { + "list": true, + "type": "SharedContactEmail", + "version": "v1", + "doc": "the email addresses of the shared contact" }, - "size": { - "type": "int" + "phone": { + "list": true, + "type": "SharedContactPhone", + "version": "v1", + "doc": "the phone numbers of the shared contact" }, - "storedFilename": { + "address": { + "list": true, + "type": "SharedContactAddress", + "version": "v1", + "doc": "the physical addresses of the shared contact" + }, + "avatar": { + "type": "SharedContactAvatar", + "version": "v1", + "doc": "the profile picture/avatar of the shared contact" + }, + "organization": { "type": "String", - "doc": "when receiving, the path that file has been downloaded to" - }, - "filename": { - "type": "String", - "doc": "when sending, the path to the local file to upload" - }, - "customFilename": { - "type": "String", - "doc": "the original name of the file" - }, - "caption": { - "type": "String" - }, - "width": { - "type": "int" - }, - "height": { - "type": "int" - }, - "voiceNote": { - "type": "boolean" - }, - "key": { - "type": "String" - }, - "digest": { - "type": "String" - }, - "blurhash": { - "type": "String" + "doc": "the organization (e.g. workplace) of the shared contact" } - }, - "doc": "represents a file attached to a message. When seding, only `filename` is required." + } }, "RemoteDelete": { "fields": { @@ -2453,923 +3510,107 @@ "type": "String" } } - } - }, - "v0": { - "JsonAccountList": { - "fields": { - "accounts": { - "list": true, - "type": "JsonAccount", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 }, - "JsonMessageEnvelope": { - "fields": { - "username": { - "type": "String", - "example": "\"+12024561414\"" - }, - "uuid": { - "type": "String", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"" - }, - "source": { - "type": "JsonAddress", - "version": "v0" - }, - "sourceDevice": { - "type": "int" - }, - "type": { - "type": "String" - }, - "relay": { - "type": "String", - "doc": "this field is no longer available and will never be populated" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - }, - "timestampISO": { - "type": "String" - }, - "serverTimestamp": { - "type": "long" - }, - "serverDeliveredTimestamp": { - "type": "long", - "example": "161557644247580" - }, - "hasLegacyMessage": { - "type": "boolean" - }, - "hasContent": { - "type": "boolean" - }, - "isUnidentifiedSender": { - "type": "boolean" - }, - "dataMessage": { - "type": "JsonDataMessage", - "version": "v0" - }, - "syncMessage": { - "type": "JsonSyncMessage", - "version": "v0" - }, - "callMessage": { - "type": "JsonCallMessage", - "version": "v0" - }, - "receipt": { - "type": "JsonReceiptMessage", - "version": "v0" - }, - "typing": { - "type": "JsonTypingMessage", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonCallMessage": { - "fields": { - "offerMessage": { - "type": "OfferMessage", - "version": "v0" - }, - "answerMessage": { - "type": "AnswerMessage", - "version": "v0" - }, - "busyMessage": { - "type": "BusyMessage", - "version": "v0" - }, - "hangupMessage": { - "type": "HangupMessage", - "version": "v0" - }, - "iceUpdateMessages": { - "list": true, - "type": "IceUpdateMessage", - "version": "v0" - }, - "destinationDeviceId": { - "type": "int" - }, - "isMultiRing": { - "type": "boolean" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonReceiptMessage": { - "fields": { - "type": { - "type": "String" - }, - "timestamps": { - "list": true, - "type": "Long" - }, - "when": { - "type": "long" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonTypingMessage": { - "fields": { - "action": { - "type": "String" - }, - "timestamp": { - "type": "long" - }, - "groupId": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonAccount": { - "fields": { - "deviceId": { - "type": "int" - }, - "username": { - "type": "String" - }, - "filename": { - "type": "String" - }, - "uuid": { - "type": "String" - }, - "registered": { - "type": "boolean" - }, - "has_keys": { - "type": "boolean" - }, - "subscribed": { - "type": "boolean" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonAddress": { - "fields": { - "number": { - "type": "String" - }, - "uuid": { - "type": "UUID" - }, - "relay": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonDataMessage": { - "fields": { - "timestamp": { - "type": "long", - "doc": "the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes.", - "example": "1615576442475" - }, - "attachments": { - "list": true, - "type": "JsonAttachment", - "version": "v0", - "doc": "files attached to the incoming message" - }, - "body": { - "type": "String", - "doc": "the text body of the incoming message.", - "example": "\"hello\"" - }, - "group": { - "type": "JsonGroupInfo", - "version": "v0", - "doc": "if the incoming message was sent to a v1 group, information about that group will be here" - }, - "groupV2": { - "type": "JsonGroupV2Info", - "version": "v0", - "doc": "is the incoming message was sent to a v2 group, basic identifying information about that group will be here. For full information, use list_groups" - }, - "endSession": { - "type": "boolean" - }, - "expiresInSeconds": { - "type": "int", - "doc": "the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds" - }, - "profileKeyUpdate": { - "type": "boolean" - }, - "quote": { - "type": "JsonQuote", - "version": "v0", - "doc": "if the incoming message is a quote or reply to another message, this will contain information about that message" - }, - "contacts": { - "list": true, - "type": "SharedContact", - "version": "v0", - "doc": "if the incoming message has a shared contact, the contact's information will be here" - }, - "previews": { - "list": true, - "type": "JsonPreview", - "version": "v0", - "doc": "if the incoming message has a link preview, information about that preview will be here" - }, - "sticker": { - "type": "JsonSticker", - "version": "v0", - "doc": "if the incoming message is a sticker, information about the sicker will be here" - }, - "viewOnce": { - "type": "boolean", - "doc": "indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image." - }, - "reaction": { - "type": "JsonReaction", - "version": "v0", - "doc": "if the message adds or removes a reaction to another message, this will indicate what change is being made" - }, - "remoteDelete": { - "type": "RemoteDelete", - "version": "v0", - "doc": "if the inbound message is deleting a previously sent message, indicates which message should be deleted" - }, - "mentions": { - "list": true, - "type": "JsonMention", - "version": "v0", - "doc": "list of mentions in the message" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonSyncMessage": { - "fields": { - "sent": { - "type": "JsonSentTranscriptMessage", - "version": "v0" - }, - "contacts": { - "type": "JsonAttachment", - "version": "v0" - }, - "contactsComplete": { - "type": "boolean" - }, - "groups": { - "type": "JsonAttachment", - "version": "v0" - }, - "blockedList": { - "type": "JsonBlockedListMessage", - "version": "v0" - }, - "request": { - "type": "String" - }, - "readMessages": { - "list": true, - "type": "JsonReadMessage", - "version": "v0" - }, - "viewOnceOpen": { - "type": "JsonViewOnceOpenMessage", - "version": "v0" - }, - "verified": { - "type": "JsonVerifiedMessage", - "version": "v0" - }, - "configuration": { - "type": "ConfigurationMessage", - "version": "v0" - }, - "stickerPackOperations": { - "list": true, - "type": "JsonStickerPackOperationMessage", - "version": "v0" - }, - "fetchType": { - "type": "String" - }, - "messageRequestResponse": { - "type": "JsonMessageRequestResponseMessage", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonAttachment": { - "fields": { - "contentType": { - "type": "String" - }, - "id": { - "type": "String" - }, - "size": { - "type": "int" - }, - "storedFilename": { - "type": "String" - }, - "filename": { - "type": "String" - }, - "customFilename": { - "type": "String" - }, - "caption": { - "type": "String" - }, - "width": { - "type": "int" - }, - "height": { - "type": "int" - }, - "voiceNote": { - "type": "boolean" - }, - "key": { - "type": "String" - }, - "digest": { - "type": "String" - }, - "blurhash": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "SharedContact": { - "fields": { - "name": { - "type": "Name", - "version": "v0" - }, - "avatar": { - "type": "Optional", - "version": "v0" - }, - "phone": { - "type": "Optional", - "version": "v0" - }, - "email": { - "type": "Optional", - "version": "v0" - }, - "address": { - "type": "Optional", - "version": "v0" - }, - "organization": { - "type": "Optional", - "version": "v0" - } - } - }, - "JsonSticker": { - "fields": { - "packID": { - "type": "String" - }, - "packKey": { - "type": "String" - }, - "stickerID": { - "type": "int" - }, - "attachment": { - "type": "JsonAttachment", - "version": "v0" - }, - "image": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "ConfigurationMessage": { - "fields": { - "readReceipts": { - "type": "Optional", - "version": "v0" - }, - "unidentifiedDeliveryIndicators": { - "type": "Optional", - "version": "v0" - }, - "typingIndicators": { - "type": "Optional", - "version": "v0" - }, - "linkPreviews": { - "type": "Optional", - "version": "v0" - } - } - }, - "JsonStickerPackOperationMessage": { - "fields": { - "packID": { - "type": "String" - }, - "packKey": { - "type": "String" - }, - "type": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "OfferMessage": { - "fields": { - "id": { - "type": "long" - }, - "sdp": { - "type": "String" - }, - "type": { - "type": "Type", - "version": "v0" - }, - "opaque": { - "type": "String" - } - } - }, - "AnswerMessage": { - "fields": { - "id": { - "type": "long" - }, - "sdp": { - "type": "String" - }, - "opaque": { - "type": "String" - } - } - }, - "BusyMessage": { - "fields": { - "id": { - "type": "long" - } - } - }, - "HangupMessage": { - "fields": { - "id": { - "type": "long" - }, - "type": { - "type": "Type", - "version": "v0" - }, - "deviceId": { - "type": "int" - }, - "legacy": { - "type": "boolean" - } - } - }, - "IceUpdateMessage": { - "fields": { - "id": { - "type": "long" - }, - "opaque": { - "type": "String" - }, - "sdp": { - "type": "String" - } - } - }, - "JsonGroupInfo": { - "fields": { - "groupId": { - "type": "String" - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v0" - }, - "name": { - "type": "String" - }, - "type": { - "type": "String" - }, - "avatarId": { - "type": "long" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonGroupV2Info": { - "fields": { - "id": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "revision": { - "type": "int", - "example": "5" - }, - "title": { - "type": "String", - "example": "\"Parkdale Run Club\"" - }, - "description": { - "type": "String" - }, - "avatar": { - "type": "String", - "doc": "path to the group's avatar on local disk, if available", - "example": "\"/var/lib/signald/avatars/group-EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "timer": { - "type": "int", - "example": "604800" - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v0" - }, - "pendingMembers": { - "list": true, - "type": "JsonAddress", - "version": "v0" - }, - "requestingMembers": { - "list": true, - "type": "JsonAddress", - "version": "v0" - }, - "inviteLink": { - "type": "String", - "doc": "the signal.group link, if applicable" - }, - "accessControl": { - "type": "GroupAccessControl", - "version": "v0", - "doc": "current access control settings for this group" - }, - "memberDetail": { - "list": true, - "type": "GroupMember", - "version": "v0", - "doc": "detailed member list" - }, - "pendingMemberDetail": { - "list": true, - "type": "GroupMember", - "version": "v0", - "doc": "detailed pending member list" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonQuote": { - "fields": { - "id": { - "type": "long", - "doc": "the client timestamp of the message being quoted", - "example": "1615576442475" - }, - "author": { - "type": "JsonAddress", - "version": "v0", - "doc": "the author of the message being quoted" - }, - "text": { - "type": "String", - "doc": "the body of the message being quoted", - "example": "\"hey  what's up?\"" - }, - "attachments": { - "list": true, - "type": "JsonQuotedAttachment", - "version": "v0", - "doc": "list of files attached to the quoted message" - }, - "mentions": { - "list": true, - "type": "JsonMention", - "version": "v0", - "doc": "list of mentions in the quoted message" - } - }, - "doc": "A quote is a reply to a previous message. ID is the sent time of the message being replied to", - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonPreview": { - "fields": { - "url": { - "type": "String" - }, - "title": { - "type": "String" - }, - "attachment": { - "type": "JsonAttachment", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonReaction": { - "fields": { - "emoji": { - "type": "String", - "doc": "the emoji to react with", - "example": "\"👍\"" - }, - "remove": { - "type": "boolean", - "doc": "set to true to remove the reaction. requires emoji be set to previously reacted emoji" - }, - "targetAuthor": { - "type": "JsonAddress", - "version": "v0", - "doc": "the author of the message being reacted to" - }, - "targetSentTimestamp": { - "type": "long", - "doc": "the client timestamp of the message being reacted to", - "example": "1615576442475" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "RemoteDelete": { - "fields": { - "targetSentTimestamp": { - "type": "long" - } - } - }, - "JsonMention": { - "fields": { - "uuid": { - "type": "String", - "doc": "The UUID of the account being mentioned", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" - }, - "start": { - "type": "int", - "doc": "The number of characters in that the mention starts at. Note that due to a quirk of how signald encodes JSON, if this value is 0 (for example if the first character in the message is the mention) the field won't show up.", - "example": "4" - }, - "length": { - "type": "int", - "doc": "The length of the mention represented in the message. Seems to always be 1 but included here in case that changes.", - "example": "1" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonSentTranscriptMessage": { - "fields": { - "destination": { - "type": "JsonAddress", - "version": "v0" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - }, - "expirationStartTimestamp": { - "type": "long" - }, - "message": { - "type": "JsonDataMessage", - "version": "v0" - }, - "unidentifiedStatus": { - "type": "Map" - }, - "isRecipientUpdate": { - "type": "boolean" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonBlockedListMessage": { - "fields": { - "addresses": { - "list": true, - "type": "JsonAddress", - "version": "v0" - }, - "groupIds": { - "list": true, - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonReadMessage": { - "fields": { - "sender": { - "type": "JsonAddress", - "version": "v0" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonViewOnceOpenMessage": { - "fields": { - "sender": { - "type": "JsonAddress", - "version": "v0" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonVerifiedMessage": { - "fields": { - "destination": { - "type": "JsonAddress", - "version": "v0" - }, - "identityKey": { - "type": "String" - }, - "verified": { - "type": "String" - }, - "timestamp": { - "type": "long" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonMessageRequestResponseMessage": { - "fields": { - "person": { - "type": "JsonAddress", - "version": "v0" - }, - "groupId": { - "type": "String" - }, - "type": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonQuotedAttachment": { - "fields": { - "contentType": { - "type": "String" - }, - "fileName": { - "type": "String" - }, - "thumbnail": { - "type": "JsonAttachment", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "Name": { + "SharedContactName": { "fields": { "display": { - "type": "Optional", - "version": "v0" + "type": "String", + "doc": "the full name that should be displayed" }, "given": { - "type": "Optional", - "version": "v0" - }, - "family": { - "type": "Optional", - "version": "v0" - }, - "prefix": { - "type": "Optional", - "version": "v0" - }, - "suffix": { - "type": "Optional", - "version": "v0" + "type": "String", + "doc": "given name" }, "middle": { - "type": "Optional", - "version": "v0" + "type": "String", + "doc": "middle name" + }, + "family": { + "type": "String", + "doc": "family name (surname)" + }, + "prefix": { + "type": "String" + }, + "suffix": { + "type": "String" } } }, - "Optional": { + "SharedContactEmail": { "fields": { - "present": { + "type": { + "type": "String", + "doc": "the type of email (options: HOME, WORK, MOBILE, CUSTOM)" + }, + "value": { + "type": "String", + "doc": "the email address" + }, + "label": { + "type": "String", + "doc": "the type label when type is CUSTOM" + } + } + }, + "SharedContactPhone": { + "fields": { + "type": { + "type": "String", + "doc": "the type of phone (options: HOME, WORK, MOBILE, CUSTOM)" + }, + "value": { + "type": "String", + "doc": "the phone number" + }, + "label": { + "type": "String", + "doc": "the type label when type is CUSTOM" + } + } + }, + "SharedContactAddress": { + "fields": { + "type": { + "type": "String", + "doc": "the type of address (options: HOME, WORK, CUSTOM)" + }, + "label": { + "type": "String" + }, + "street": { + "type": "String" + }, + "pobox": { + "type": "String" + }, + "neighborhood": { + "type": "String" + }, + "city": { + "type": "String" + }, + "region": { + "type": "String" + }, + "postcode": { + "type": "String" + }, + "country": { + "type": "String" + } + } + }, + "SharedContactAvatar": { + "fields": { + "attachment": { + "type": "JsonAttachment", + "version": "v1" + }, + "is_profile": { "type": "boolean" } } - }, - "Type": { - "fields": {} - }, - "GroupAccessControl": { - "fields": { - "link": { - "type": "String", - "doc": "UNSATISFIABLE when the group link is disabled, ADMINISTRATOR when the group link is enabled but an administrator must approve new members, ANY when the group link is enabled and no approval is required", - "example": "\"ANY\"" - }, - "attributes": { - "type": "String", - "doc": "who can edit group info" - }, - "members": { - "type": "String", - "doc": "who can add members" - } - }, - "doc": "group access control settings. Options for each controlled action are: UNKNOWN, ANY, MEMBER, ADMINISTRATOR, UNSATISFIABLE and UNRECOGNIZED", - "deprecated": true, - "removal_date": 1641027661 - }, - "GroupMember": { - "fields": { - "uuid": { - "type": "String", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" - }, - "role": { - "type": "String", - "doc": "possible values are: UNKNOWN, DEFAULT, ADMINISTRATOR and UNRECOGNIZED", - "example": "\"DEFAULT\"" - }, - "joined_revision": { - "type": "int" - } - }, - "deprecated": true, - "removal_date": 1641027661 } } }, @@ -3408,6 +3649,9 @@ }, { "name": "InvalidRecipientError" + }, + { + "name": "AttachmentTooLargeError" } ] }, @@ -3442,6 +3686,9 @@ }, { "name": "RateLimitError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -3474,6 +3721,9 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" } ] }, @@ -3502,13 +3752,16 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" } ] }, "get_group": { "request": "GetGroupRequest", "response": "JsonGroupV2Info", - "doc": "Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupError' is returned.", + "doc": "Query the server for the latest state of a known group. If the account is not a member of the group, an UnknownGroupError is returned.", "errors": [ { "name": "NoSuchAccountError" @@ -3533,6 +3786,9 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" } ] }, @@ -3616,6 +3872,9 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" } ] }, @@ -3644,6 +3903,12 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -3680,6 +3945,9 @@ }, { "name": "NoSuchAccountError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -3700,6 +3968,9 @@ }, { "name": "UntrustedIdentityError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -3722,6 +3993,9 @@ }, { "name": "ProfileUnavailableError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -3797,6 +4071,9 @@ }, { "name": "UnknownGroupError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -3824,6 +4101,9 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" } ] }, @@ -3843,7 +4123,7 @@ "finish_link": { "request": "FinishLinkRequest", "response": "Account", - "doc": "After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device.", + "doc": "After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device and the new account is setup. Note that the account setup process can sometimes take some time, if rapid userfeedback is required after scanning, use wait_for_scan first, then finish setup with finish_link.", "errors": [ { "name": "NoSuchSessionError" @@ -3862,6 +4142,9 @@ }, { "name": "UserAlreadyExistsError" + }, + { + "name": "ScanTimeoutError" } ] }, @@ -3947,6 +4230,9 @@ }, { "name": "NoSuchAccountError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -3980,6 +4266,9 @@ }, { "name": "InvalidFingerprintError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -4031,6 +4320,9 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -4065,6 +4357,9 @@ }, { "name": "InvalidRecipientError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -4086,6 +4381,9 @@ }, { "name": "UntrustedIdentityError" + }, + { + "name": "InvalidRequestError" } ] }, @@ -4174,12 +4472,18 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "UnregisteredUserError" } ] }, "set_device_name": { "request": "SetDeviceNameRequest", - "doc": "set this device's name. This will show up on the mobile device on the same account under ", + "doc": "set this device's name. This will show up on the mobile device on the same account under settings -> linked devices", "errors": [ { "name": "InternalError" @@ -4229,6 +4533,9 @@ }, { "name": "InternalError" + }, + { + "name": "AuthorizationFailedError" } ] }, @@ -4275,6 +4582,9 @@ }, { "name": "RateLimitError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -4342,6 +4652,9 @@ }, { "name": "RateLimitError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -4389,6 +4702,12 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "UnregisteredUserError" } ] }, @@ -4411,6 +4730,40 @@ "name": "InternalError" } ] + }, + "is_identifier_registered": { + "request": "IsIdentifierRegisteredRequest", + "response": "BooleanMessage", + "doc": "Determine whether an account identifier is registered on the Signal service.", + "errors": [ + { + "name": "InternalError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "NoSuchAccountError" + } + ] + }, + "wait_for_scan": { + "request": "WaitForScanRequest", + "doc": "An optional part of the linking process. Intended to be called after displaying the QR code, will return quickly after the user scans the QR code. finish_link must be called after wait_for_scan returns a non-error", + "errors": [ + { + "name": "NoSuchSessionError" + }, + { + "name": "ScanTimeoutError" + }, + { + "name": "InternalError" + } + ] } } } diff --git a/signald/client-protocol/v1/errors.go b/signald/client-protocol/v1/errors.go index 3081b52..0b3662c 100644 --- a/signald/client-protocol/v1/errors.go +++ b/signald/client-protocol/v1/errors.go @@ -32,6 +32,20 @@ func mkerr(response client_protocol.BasicResponse) error { return err } return result + case "AttachmentTooLargeError": + result := AttachmentTooLargeError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result + case "AuthorizationFailedError": + result := AuthorizationFailedError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result case "CaptchaRequiredError": result := CaptchaRequiredError{} err := json.Unmarshal(response.Error, &result) @@ -207,6 +221,13 @@ func mkerr(response client_protocol.BasicResponse) error { return err } return result + case "ScanTimeoutError": + result := ScanTimeoutError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result case "ServerNotFoundError": result := ServerNotFoundError{} err := json.Unmarshal(response.Error, &result) @@ -228,6 +249,13 @@ func mkerr(response client_protocol.BasicResponse) error { return err } return result + case "UnregisteredUserError": + result := UnregisteredUserError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result case "UntrustedIdentityError": result := UntrustedIdentityError{} err := json.Unmarshal(response.Error, &result) @@ -272,6 +300,24 @@ func (e AccountLockedError) Error() string { return e.Message } +type AttachmentTooLargeError struct { + Filename string `json:"filename,omitempty" yaml:"filename,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e AttachmentTooLargeError) Error() string { + return e.Message +} + +// AuthorizationFailedError: indicates the server rejected our credentials. Typically means the linked device was removed by the primary device, or that the account was re-registered +type AuthorizationFailedError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e AuthorizationFailedError) Error() string { + return e.Message +} + type CaptchaRequiredError struct { Message string `json:"message,omitempty" yaml:"message,omitempty"` More string `json:"more,omitempty" yaml:"more,omitempty"` @@ -282,7 +328,8 @@ func (e CaptchaRequiredError) Error() string { } type DuplicateMessageError struct { - Message string `json:"message,omitempty" yaml:"message,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` } func (e DuplicateMessageError) Error() string { @@ -470,6 +517,7 @@ type ProtocolInvalidMessageError struct { Message string `json:"message,omitempty" yaml:"message,omitempty"` Sender string `json:"sender,omitempty" yaml:"sender,omitempty"` SenderDevice int32 `json:"sender_device,omitempty" yaml:"sender_device,omitempty"` + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` } func (e ProtocolInvalidMessageError) Error() string { @@ -484,6 +532,14 @@ func (e RateLimitError) Error() string { return e.Message } +type ScanTimeoutError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e ScanTimeoutError) Error() string { + return e.Message +} + type ServerNotFoundError struct { Message string `json:"message,omitempty" yaml:"message,omitempty"` UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` @@ -509,6 +565,15 @@ func (e UnknownIdentityKeyError) Error() string { return e.Message } +type UnregisteredUserError struct { + E164Number string `json:"e164_number,omitempty" yaml:"e164_number,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e UnregisteredUserError) Error() string { + return e.Message +} + type UntrustedIdentityError struct { Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` IdentityKey **IdentityKey `json:"identity_key,omitempty" yaml:"identity_key,omitempty"` diff --git a/signald/client-protocol/v1/requests.go b/signald/client-protocol/v1/requests.go index 5a38921..a59691b 100644 --- a/signald/client-protocol/v1/requests.go +++ b/signald/client-protocol/v1/requests.go @@ -217,7 +217,7 @@ func (r *RemoveServerRequest) Submit(conn *signald.Signald) (err error) { } -// Submit: After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device. +// Submit: After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device and the new account is setup. Note that the account setup process can sometimes take some time, if rapid userfeedback is required after scanning, use wait_for_scan first, then finish setup with finish_link. func (r *FinishLinkRequest) Submit(conn *signald.Signald) (response Account, err error) { r.Version = "v1" r.Type = "finish_link" @@ -316,7 +316,7 @@ func (r *GetAllIdentities) Submit(conn *signald.Signald) (response AllIdentityKe } -// Submit: Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupError' is returned. +// Submit: Query the server for the latest state of a known group. If the account is not a member of the group, an UnknownGroupError is returned. func (r *GetGroupRequest) Submit(conn *signald.Signald) (response JsonGroupV2Info, err error) { r.Version = "v1" r.Type = "get_group" @@ -546,6 +546,39 @@ func (r *GroupLinkInfoRequest) Submit(conn *signald.Signald) (response JsonGroup } +// Submit: Determine whether an account identifier is registered on the Signal service. +func (r *IsIdentifierRegisteredRequest) Submit(conn *signald.Signald) (response BooleanMessage, err error) { + r.Version = "v1" + r.Type = "is_identifier_registered" + if r.ID == "" { + r.ID = signald.GenerateID() + } + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = mkerr(rawResponse) + return + } + + err = json.Unmarshal(rawResponse.Data, &response) + if err != nil { + rawResponseJson, _ := rawResponse.Data.MarshalJSON() + log.Println("signald-go: error unmarshalling response from signald of type", rawResponse.Type, string(rawResponseJson)) + return + } + + return response, nil + +} + // Submit: Join a group using the a signal.group URL. Note that you must have a profile name set to join groups. func (r *JoinGroupRequest) Submit(conn *signald.Signald) (response JsonGroupJoinInfo, err error) { r.Version = "v1" @@ -1048,7 +1081,7 @@ func (r *SendPaymentRequest) Submit(conn *signald.Signald) (response SendRespons } -// Submit: set this device's name. This will show up on the mobile device on the same account under +// Submit: set this device's name. This will show up on the mobile device on the same account under settings -> linked devices func (r *SetDeviceNameRequest) Submit(conn *signald.Signald) (err error) { r.Version = "v1" r.Type = "set_device_name" @@ -1391,3 +1424,29 @@ func (r *VersionRequest) Submit(conn *signald.Signald) (response JsonVersionMess return response, nil } + +// Submit: An optional part of the linking process. Intended to be called after displaying the QR code, will return quickly after the user scans the QR code. finish_link must be called after wait_for_scan returns a non-error +func (r *WaitForScanRequest) Submit(conn *signald.Signald) (err error) { + r.Version = "v1" + r.Type = "wait_for_scan" + if r.ID == "" { + r.ID = signald.GenerateID() + } + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = mkerr(rawResponse) + return + } + + return err + +} diff --git a/signald/client-protocol/v1/structs.go b/signald/client-protocol/v1/structs.go index 38d30ea..e589b8e 100644 --- a/signald/client-protocol/v1/structs.go +++ b/signald/client-protocol/v1/structs.go @@ -62,6 +62,11 @@ type ApproveMembershipRequest struct { Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // list of requesting members to approve } +// BooleanMessage: A message containing a single boolean, usually as a response +type BooleanMessage struct { + Value bool `json:"value,omitempty" yaml:"value,omitempty"` +} + type BusyMessage struct { ID int64 `json:"id,omitempty" yaml:"id,omitempty"` } @@ -77,9 +82,12 @@ type CallMessage struct { } type Capabilities struct { - Gv1Migration bool `json:"gv1-migration,omitempty" yaml:"gv1-migration,omitempty"` - Gv2 bool `json:"gv2,omitempty" yaml:"gv2,omitempty"` - Storage bool `json:"storage,omitempty" yaml:"storage,omitempty"` + AnnouncementGroup bool `json:"announcement_group,omitempty" yaml:"announcement_group,omitempty"` + ChangeNumber bool `json:"change_number,omitempty" yaml:"change_number,omitempty"` + Gv1Migration bool `json:"gv1-migration,omitempty" yaml:"gv1-migration,omitempty"` + Gv2 bool `json:"gv2,omitempty" yaml:"gv2,omitempty"` + SenderKey bool `json:"sender_key,omitempty" yaml:"sender_key,omitempty"` + Storage bool `json:"storage,omitempty" yaml:"storage,omitempty"` } // ClientMessageWrapper: Wraps all incoming messages sent to the client after a v1 subscribe request is issued @@ -115,7 +123,7 @@ type DeviceInfo struct { Name string `json:"name,omitempty" yaml:"name,omitempty"` } -// FinishLinkRequest: After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device. +// FinishLinkRequest: After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device and the new account is setup. Note that the account setup process can sometimes take some time, if rapid userfeedback is required after scanning, use wait_for_scan first, then finish setup with finish_link. type FinishLinkRequest struct { Request DeviceName string `json:"device_name,omitempty" yaml:"device_name,omitempty"` @@ -134,7 +142,7 @@ type GetAllIdentities struct { Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with } -// GetGroupRequest: Query the server for the latest state of a known group. If no account in signald is a member of the group (anymore), an error with error_type: 'UnknownGroupError' is returned. +// GetGroupRequest: Query the server for the latest state of a known group. If the account is not a member of the group, an UnknownGroupError is returned. type GetGroupRequest struct { Request Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with @@ -243,6 +251,13 @@ type IncomingMessage struct { UnidentifiedSender bool `json:"unidentified_sender,omitempty" yaml:"unidentified_sender,omitempty"` } +// IsIdentifierRegisteredRequest: Determine whether an account identifier is registered on the Signal service. +type IsIdentifierRegisteredRequest struct { + Request + Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to use to use + Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` // The UUID of an identifier to check if it is registered on Signal. This UUID is either a Phone Number Identity (PNI) or an Account Identity (ACI). +} + // JoinGroupRequest: Join a group using the a signal.group URL. Note that you must have a profile name set to join groups. type JoinGroupRequest struct { Request @@ -256,7 +271,7 @@ type JsonAddress struct { UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` // A UUID, the unique identifier for a particular Signal account. } -// JsonAttachment: represents a file attached to a message. When seding, only `filename` is required. +// JsonAttachment: represents a file attached to a message. When sending, only `filename` is required. type JsonAttachment struct { Blurhash string `json:"blurhash,omitempty" yaml:"blurhash,omitempty"` Caption string `json:"caption,omitempty" yaml:"caption,omitempty"` @@ -279,24 +294,24 @@ type JsonBlockedListMessage struct { } type JsonDataMessage struct { - Attachments []*JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` // files attached to the incoming message - Body string `json:"body,omitempty" yaml:"body,omitempty"` // the text body of the incoming message. - Contacts []*v0.SharedContact `json:"contacts,omitempty" yaml:"contacts,omitempty"` // if the incoming message has a shared contact, the contact's information will be here - EndSession bool `json:"endSession,omitempty" yaml:"endSession,omitempty"` - ExpiresInSeconds int32 `json:"expiresInSeconds,omitempty" yaml:"expiresInSeconds,omitempty"` // the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds - Group *JsonGroupInfo `json:"group,omitempty" yaml:"group,omitempty"` // if the incoming message was sent to a v1 group, information about that group will be here - GroupV2 *JsonGroupV2Info `json:"groupV2,omitempty" yaml:"groupV2,omitempty"` // if the incoming message was sent to a v2 group, basic identifying information about that group will be here. If group information changes, JsonGroupV2Info.revision is incremented. If the group revision is higher than previously seen, a client can retrieve the group information by calling get_group. - GroupCallUpdate string `json:"group_call_update,omitempty" yaml:"group_call_update,omitempty"` // the eraId string from a group call message update - Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` // list of mentions in the message - Payment *Payment `json:"payment,omitempty" yaml:"payment,omitempty"` // details about the MobileCoin payment attached to the message, if present - Previews []*JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"` // if the incoming message has a link preview, information about that preview will be here - ProfileKeyUpdate bool `json:"profileKeyUpdate,omitempty" yaml:"profileKeyUpdate,omitempty"` - Quote *JsonQuote `json:"quote,omitempty" yaml:"quote,omitempty"` // if the incoming message is a quote or reply to another message, this will contain information about that message - Reaction *JsonReaction `json:"reaction,omitempty" yaml:"reaction,omitempty"` // if the message adds or removes a reaction to another message, this will indicate what change is being made - RemoteDelete *RemoteDelete `json:"remoteDelete,omitempty" yaml:"remoteDelete,omitempty"` // if the inbound message is deleting a previously sent message, indicates which message should be deleted - Sticker *v0.JsonSticker `json:"sticker,omitempty" yaml:"sticker,omitempty"` // if the incoming message is a sticker, information about the sicker will be here - Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` // the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes. - ViewOnce bool `json:"viewOnce,omitempty" yaml:"viewOnce,omitempty"` // indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image. + Attachments []*JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` // files attached to the incoming message + Body string `json:"body,omitempty" yaml:"body,omitempty"` // the text body of the incoming message. + Contacts []*SharedContact `json:"contacts,omitempty" yaml:"contacts,omitempty"` // if the incoming message has a shared contact, the contact's information will be here + EndSession bool `json:"endSession,omitempty" yaml:"endSession,omitempty"` + ExpiresInSeconds int32 `json:"expiresInSeconds,omitempty" yaml:"expiresInSeconds,omitempty"` // the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds + Group *JsonGroupInfo `json:"group,omitempty" yaml:"group,omitempty"` // if the incoming message was sent to a v1 group, information about that group will be here + GroupV2 *JsonGroupV2Info `json:"groupV2,omitempty" yaml:"groupV2,omitempty"` // if the incoming message was sent to a v2 group, basic identifying information about that group will be here. If group information changes, JsonGroupV2Info.revision is incremented. If the group revision is higher than previously seen, a client can retrieve the group information by calling get_group. + GroupCallUpdate string `json:"group_call_update,omitempty" yaml:"group_call_update,omitempty"` // the eraId string from a group call message update + Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` // list of mentions in the message + Payment *Payment `json:"payment,omitempty" yaml:"payment,omitempty"` // details about the MobileCoin payment attached to the message, if present + Previews []*JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"` // if the incoming message has a link preview, information about that preview will be here + ProfileKeyUpdate bool `json:"profileKeyUpdate,omitempty" yaml:"profileKeyUpdate,omitempty"` + Quote *JsonQuote `json:"quote,omitempty" yaml:"quote,omitempty"` // if the incoming message is a quote or reply to another message, this will contain information about that message + Reaction *JsonReaction `json:"reaction,omitempty" yaml:"reaction,omitempty"` // if the message adds or removes a reaction to another message, this will indicate what change is being made + RemoteDelete *RemoteDelete `json:"remoteDelete,omitempty" yaml:"remoteDelete,omitempty"` // if the inbound message is deleting a previously sent message, indicates which message should be deleted + Sticker *v0.JsonSticker `json:"sticker,omitempty" yaml:"sticker,omitempty"` // if the incoming message is a sticker, information about the sicker will be here + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` // the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes. + ViewOnce bool `json:"viewOnce,omitempty" yaml:"viewOnce,omitempty"` // indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image. } // JsonGroupInfo: information about a legacy group @@ -330,6 +345,7 @@ type JsonGroupV2Info struct { Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` PendingMemberDetail []*GroupMember `json:"pendingMemberDetail,omitempty" yaml:"pendingMemberDetail,omitempty"` // detailed pending member list PendingMembers []*JsonAddress `json:"pendingMembers,omitempty" yaml:"pendingMembers,omitempty"` + Removed bool `json:"removed,omitempty" yaml:"removed,omitempty"` // will be set to true for incoming messages to indicate the user has been removed from the group RequestingMembers []*JsonAddress `json:"requestingMembers,omitempty" yaml:"requestingMembers,omitempty"` Revision int32 `json:"revision,omitempty" yaml:"revision,omitempty"` Timer int32 `json:"timer,omitempty" yaml:"timer,omitempty"` @@ -522,6 +538,7 @@ type Profile struct { MobilecoinAddress string `json:"mobilecoin_address,omitempty" yaml:"mobilecoin_address,omitempty"` // *base64-encoded* mobilecoin address. Note that this is not the traditional MobileCoin address encoding. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. If unset, null or an empty string, will empty the profile payment address Name string `json:"name,omitempty" yaml:"name,omitempty"` // The user's name from local contact names if available, or if not in contact list their Signal profile name ProfileName string `json:"profile_name,omitempty" yaml:"profile_name,omitempty"` // The user's Signal profile name + VisibleBadgeIds []string `json:"visible_badge_ids,omitempty" yaml:"visible_badge_ids,omitempty"` // currently unclear how these work, as they are not available in the production Signal apps } type ProfileList struct { @@ -612,6 +629,7 @@ type RequestSyncRequest struct { Configuration bool `json:"configuration,omitempty" yaml:"configuration,omitempty"` // request configuration sync (default true) Contacts bool `json:"contacts,omitempty" yaml:"contacts,omitempty"` // request contact sync (default true) Groups bool `json:"groups,omitempty" yaml:"groups,omitempty"` // request group sync (default true) + Keys bool `json:"keys,omitempty" yaml:"keys,omitempty"` // request storage service keys } // ResetSessionRequest: reset a session with a particular user @@ -640,16 +658,16 @@ type SendPaymentRequest struct { type SendRequest struct { Request - Attachments []*v0.JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` - Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified - Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` - MessageBody string `json:"messageBody,omitempty" yaml:"messageBody,omitempty"` - Previews []*JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"` - Quote *JsonQuote `json:"quote,omitempty" yaml:"quote,omitempty"` - RecipientAddress *JsonAddress `json:"recipientAddress,omitempty" yaml:"recipientAddress,omitempty"` - RecipientGroupID string `json:"recipientGroupId,omitempty" yaml:"recipientGroupId,omitempty"` - Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` - Username string `json:"username,omitempty" yaml:"username,omitempty"` + Attachments []*JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` + Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified + Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` + MessageBody string `json:"messageBody,omitempty" yaml:"messageBody,omitempty"` + Previews []*JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"` + Quote *JsonQuote `json:"quote,omitempty" yaml:"quote,omitempty"` + RecipientAddress *JsonAddress `json:"recipientAddress,omitempty" yaml:"recipientAddress,omitempty"` + RecipientGroupID string `json:"recipientGroupId,omitempty" yaml:"recipientGroupId,omitempty"` + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` + Username string `json:"username,omitempty" yaml:"username,omitempty"` } type SendResponse struct { @@ -692,7 +710,7 @@ type ServerList struct { Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"` } -// SetDeviceNameRequest: set this device's name. This will show up on the mobile device on the same account under +// SetDeviceNameRequest: set this device's name. This will show up on the mobile device on the same account under settings -> linked devices type SetDeviceNameRequest struct { Request Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to set the device name of @@ -710,12 +728,60 @@ type SetExpirationRequest struct { type SetProfile struct { Request - About string `json:"about,omitempty" yaml:"about,omitempty"` // an optional about string. If unset, null or an empty string will unset profile about field - Account string `json:"account,omitempty" yaml:"account,omitempty"` // The phone number of the account to use - AvatarFile string `json:"avatarFile,omitempty" yaml:"avatarFile,omitempty"` // Path to new profile avatar file. If unset or null, unset the profile avatar - Emoji string `json:"emoji,omitempty" yaml:"emoji,omitempty"` // an optional single emoji character. If unset, null or an empty string will unset profile emoji - MobilecoinAddress string `json:"mobilecoin_address,omitempty" yaml:"mobilecoin_address,omitempty"` // an optional *base64-encoded* MobileCoin address to set in the profile. Note that this is not the traditional MobileCoin address encoding, which is custom. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. If unset, null or an empty string, will empty the profile payment address - Name string `json:"name,omitempty" yaml:"name,omitempty"` // New profile name. Set to empty string for no profile name + About string `json:"about,omitempty" yaml:"about,omitempty"` // Change the 'about' profile field + Account string `json:"account,omitempty" yaml:"account,omitempty"` // The phone number of the account to use + AvatarFile string `json:"avatarFile,omitempty" yaml:"avatarFile,omitempty"` // Path to new profile avatar file. If unset or null, unset the profile avatar + Emoji string `json:"emoji,omitempty" yaml:"emoji,omitempty"` // Change the profile emoji + MobilecoinAddress string `json:"mobilecoin_address,omitempty" yaml:"mobilecoin_address,omitempty"` // Change the profile payment address. Payment address must be a *base64-encoded* MobileCoin address. Note that this is not the traditional MobileCoin address encoding, which is custom. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. + Name string `json:"name,omitempty" yaml:"name,omitempty"` // Change the profile name + VisibleBadgeIds []string `json:"visible_badge_ids,omitempty" yaml:"visible_badge_ids,omitempty"` // configure visible badge IDs +} + +type SharedContact struct { + Address []*SharedContactAddress `json:"address,omitempty" yaml:"address,omitempty"` // the physical addresses of the shared contact + Avatar *SharedContactAvatar `json:"avatar,omitempty" yaml:"avatar,omitempty"` // the profile picture/avatar of the shared contact + Email []*SharedContactEmail `json:"email,omitempty" yaml:"email,omitempty"` // the email addresses of the shared contact + Name *SharedContactName `json:"name,omitempty" yaml:"name,omitempty"` // the name of the shared contact + Organization string `json:"organization,omitempty" yaml:"organization,omitempty"` // the organization (e.g. workplace) of the shared contact + Phone []*SharedContactPhone `json:"phone,omitempty" yaml:"phone,omitempty"` // the phone numbers of the shared contact +} + +type SharedContactAddress struct { + City string `json:"city,omitempty" yaml:"city,omitempty"` + Country string `json:"country,omitempty" yaml:"country,omitempty"` + Label string `json:"label,omitempty" yaml:"label,omitempty"` + Neighborhood string `json:"neighborhood,omitempty" yaml:"neighborhood,omitempty"` + Pobox string `json:"pobox,omitempty" yaml:"pobox,omitempty"` + Postcode string `json:"postcode,omitempty" yaml:"postcode,omitempty"` + Region string `json:"region,omitempty" yaml:"region,omitempty"` + Street string `json:"street,omitempty" yaml:"street,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` // the type of address (options: HOME, WORK, CUSTOM) +} + +type SharedContactAvatar struct { + Attachment *JsonAttachment `json:"attachment,omitempty" yaml:"attachment,omitempty"` + IsProfile bool `json:"is_profile,omitempty" yaml:"is_profile,omitempty"` +} + +type SharedContactEmail struct { + Label string `json:"label,omitempty" yaml:"label,omitempty"` // the type label when type is CUSTOM + Type string `json:"type,omitempty" yaml:"type,omitempty"` // the type of email (options: HOME, WORK, MOBILE, CUSTOM) + Value string `json:"value,omitempty" yaml:"value,omitempty"` // the email address +} + +type SharedContactName struct { + Display string `json:"display,omitempty" yaml:"display,omitempty"` // the full name that should be displayed + Family string `json:"family,omitempty" yaml:"family,omitempty"` // family name (surname) + Given string `json:"given,omitempty" yaml:"given,omitempty"` // given name + Middle string `json:"middle,omitempty" yaml:"middle,omitempty"` // middle name + Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` + Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"` +} + +type SharedContactPhone struct { + Label string `json:"label,omitempty" yaml:"label,omitempty"` // the type label when type is CUSTOM + Type string `json:"type,omitempty" yaml:"type,omitempty"` // the type of phone (options: HOME, WORK, MOBILE, CUSTOM) + Value string `json:"value,omitempty" yaml:"value,omitempty"` // the phone number } type SubmitChallengeRequest struct { @@ -801,6 +867,12 @@ type VersionRequest struct { Request } +// WaitForScanRequest: An optional part of the linking process. Intended to be called after displaying the QR code, will return quickly after the user scans the QR code. finish_link must be called after wait_for_scan returns a non-error +type WaitForScanRequest struct { + Request + SessionId string `json:"session_id,omitempty" yaml:"session_id,omitempty"` +} + // WebSocketConnectionState: indicates when the websocket connection state to the signal server has changed type WebSocketConnectionState struct { Socket string `json:"socket,omitempty" yaml:"socket,omitempty"` // One of: UNIDENTIFIED, IDENTIFIED From acc817a043c6535b3232860ef08e4c4b0d083614 Mon Sep 17 00:00:00 2001 From: finn Date: Fri, 18 Feb 2022 14:07:14 -0500 Subject: [PATCH 36/73] add db move subcommand --- .gitlab-ci.yml | 8 +- cmd/signaldctl/cmd/db/migrate.go | 523 ++++++++++++++++++++++++++++++ cmd/signaldctl/cmd/db/postgres.go | 219 +++++++++++++ cmd/signaldctl/cmd/root.go | 2 + debian/control | 5 +- go.mod | 3 + go.sum | 6 + 7 files changed, 761 insertions(+), 5 deletions(-) create mode 100644 cmd/signaldctl/cmd/db/migrate.go create mode 100644 cmd/signaldctl/cmd/db/postgres.go diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef46119..994e572 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,10 +8,10 @@ lint: before_script: - apt-get update - apt-get install -y wget golang-go - - wget https://github.com/golangci/golangci-lint/releases/download/v1.39.0/golangci-lint-1.39.0-linux-amd64.deb - - apt-get install -y ./golangci-lint-1.39.0-linux-amd64.deb + - wget https://github.com/golangci/golangci-lint/releases/download/v1.44.2/golangci-lint-1.44.2-linux-amd64.deb + - apt-get install -y ./golangci-lint-1.44.2-linux-amd64.deb script: - - golangci-lint run + - golangci-lint --timeout 59m run - go mod tidy - diff --color=always go.mod "${CI_PROJECT_DIR}/go.mod" - diff --color=always go.sum "${CI_PROJECT_DIR}/go.sum" @@ -40,7 +40,7 @@ lint: script: - echo deb http://deb.debian.org/debian buster-backports main > /etc/apt/sources.list.d/backports.list - apt-get update - - apt-get install -y -t buster-backports git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev golang-github-google-uuid-dev + - apt-get install -y -t buster-backports git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev golang-github-google-uuid-dev golang-github-mattn-go-sqlite3-dev golang-github-lib-pq-dev golang-github-satori-go.uuid-dev - apt-get install -y ./*.deb && rm -vf *.deb - 'sed -i "s/^Architecture:.*/Architecture: ${ARCH}/g" debian/control' - go run ./cmd/signaldctl doc -o man diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go new file mode 100644 index 0000000..7c57dd8 --- /dev/null +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -0,0 +1,523 @@ +package db + +import ( + "database/sql" + "errors" + "fmt" + "log" + "os" + "os/user" + "time" + + "github.com/lib/pq" + _ "github.com/mattn/go-sqlite3" + uuid "github.com/satori/go.uuid" + "github.com/spf13/cobra" + "gitlab.com/signald/signald-go/cmd/signaldctl/common" +) + +var ( + sqlitePath string + postgresURL string + MoveCmd = &cobra.Command{ + Use: "db-move pg-url [sqlite-path]", + Short: "move a signald database from sqlite to postgres", + Long: `move a signald sqlite database into a postgres database. + If sqlite-path is not specified, the default (~/.config/signald/signald.db) will be used. + + Please note that signald must NOT be running while this command runs. + + After the data is moved, the sqlite file will be deleted`, + Annotations: map[string]string{common.AnnotationNoSocketConnection: "true"}, + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("at least one argument required") + } + postgresURL = args[0] + if len(args) > 1 { + sqlitePath = args[1] + } else { + usr, _ := user.Current() + sqlitePath = fmt.Sprintf("%s/.config/signald/signald.db", usr.HomeDir) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + source, err := sql.Open("sqlite3", sqlitePath) + if err != nil { + return err + } + defer source.Close() + + if err := source.Ping(); err != nil { + log.Println("error connecting to source database") + return err + } + + if err := verifyMigration(source); err != nil { + return err + } + + dest, err := sql.Open("postgres", postgresURL) + if err != nil { + return err + } + defer dest.Close() + + if err := dest.Ping(); err != nil { + log.Println("error connecting to destination database") + return err + } + + if err := createSchema(dest); err != nil { + log.Println("error creating schema in postgres") + return err + } + log.Println("created schema") + + if err := moveAccounts(source, dest); err != nil { + log.Println("error migrating accounts table") + return err + } + log.Println("moved accounts table") + + if err := moveRecipients(source, dest); err != nil { + log.Println("error migrating recipients table") + return err + } + log.Println("moved recipients table") + + if err := movePrekeys(source, dest); err != nil { + log.Println("error migrating prekeys table") + return err + } + log.Println("moved prekeys table") + + if err := moveSessions(source, dest); err != nil { + log.Println("error migrating sessions table") + return err + } + log.Println("moved sessions table") + + if err := moveSignedPrekeys(source, dest); err != nil { + log.Println("error migrating signed prekeys table") + return err + } + log.Println("moved signed prekeys table") + + if err := moveIdentityKeys(source, dest); err != nil { + log.Println("error migrating identity keys table") + return err + } + log.Println("moved identity keys table") + + if err := moveAccountData(source, dest); err != nil { + log.Println("error migrating account data") + return err + } + log.Println("moved account data table") + + if err := movePendingAccountData(source, dest); err != nil { + log.Println("error migrating pending account data tabe") + return err + } + log.Println("moved pending account data table") + + if err := moveSenderKeys(source, dest); err != nil { + log.Println("error migrating sender keys table") + return err + } + log.Println("moved sender keys table") + + if err := moveSenderKeyShared(source, dest); err != nil { + log.Println("error migrating sender key shared table") + return err + } + log.Println("moved sender key shared table") + + if err := moveGroups(source, dest); err != nil { + log.Println("error migrating groups table") + return err + } + log.Println("moved groups table") + + if err := moveGroupCredentials(source, dest); err != nil { + log.Println("error migrating group credentials table") + return err + } + log.Println("moved group credentials table") + + if err := os.Remove(sqlitePath); err != nil { + log.Println("error deleting sqlite file") + return err + } + log.Println("sqlite file deleted, your data is now in postgres :)") + return nil + }, + } +) + +func verifyMigration(source *sql.DB) error { + rows, err := source.Query("SELECT * FROM flyway_schema_history WHERE version = 11") + if err != nil { + return err + } + defer rows.Close() + + if !rows.Next() { + return errors.New("source database is not up to date! Please update to signald 0.17.0-16-5101c6ef and start it to move the sqlite database to an acceptable format") + } + return nil +} + +func createSchema(dest *sql.DB) error { + _, err := dest.Exec(pgScheme) + if err != nil { + return err + } + + _, err = dest.Exec("INSERT INTO flyway_schema_history (installed_rank, version, description, type, script, checksum, installed_by, execution_time, success) VALUES ($1, $2, $3, $4, $5, $6, current_user, $7, $8)", + 1, 1, "create tables", "SQL", "V1__create_tables.sql", -1247750968, 0, true) + return err +} + +func moveAccounts(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT uuid, e164, filename, server FROM accounts") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + e164 string + filename string + server uuid.UUID + ) + err = rows.Scan(&accountUUID, &e164, &filename, &server) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_accounts (uuid, e164, filename, server) VALUES ($1, $2, $3, $4)", accountUUID, e164, filename, server) + if err != nil { + return err + } + } + return nil +} + +func moveRecipients(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT rowid, account_uuid, uuid, e164 FROM recipients") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + rowID int64 + accountUUID uuid.UUID + recipientUUID uuid.UUID + e164 sql.NullString + ) + err = rows.Scan(&rowID, &accountUUID, &recipientUUID, &e164) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_recipients (rowid, account_uuid, uuid, e164) VALUES ($1, $2, $3, $4)", rowID, accountUUID, recipientUUID, e164) + if err != nil { + return err + } + } + return nil +} + +func movePrekeys(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, id, record FROM prekeys") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + id int64 + record []byte + ) + err = rows.Scan(&accountUUID, &id, &record) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_prekeys (account_uuid, id, record) VALUES ($1, $2, $3)", accountUUID, id, record) + if err != nil { + return err + } + } + return nil +} + +func moveSessions(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, recipient, device_id, record FROM sessions") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + recipient int64 + deviceID int64 + record []byte + ) + err = rows.Scan(&accountUUID, &recipient, &deviceID, &record) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_sessions (account_uuid, recipient, device_id, record) VALUES ($1, $2, $3, $4)", accountUUID, recipient, deviceID, record) + + if err != nil { + if pqErr, ok := err.(*pq.Error); ok { + if pqErr.Constraint == "signald_sessions_recipient_fkey" { + log.Println("failed to import session from non-existent recipient, ignoring") + } else { + return err + } + } else { + return err + } + } + } + return nil +} + +func moveSignedPrekeys(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, id, record FROM signed_prekeys") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + id int64 + record []byte + ) + err = rows.Scan(&accountUUID, &id, &record) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_signed_prekeys (account_uuid, id, record) VALUES ($1, $2, $3)", accountUUID, id, record) + if err != nil { + return err + } + } + return nil +} + +func moveIdentityKeys(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, recipient, identity_key, trust_level, added FROM identity_keys") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + recipient int64 + identityKey []byte + trustLevel string + added time.Time + ) + err = rows.Scan(&accountUUID, &recipient, &identityKey, &trustLevel, &added) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_identity_keys (account_uuid, recipient, identity_key, trust_level, added) VALUES ($1, $2, $3, $4, $5)", accountUUID, recipient, identityKey, trustLevel, added) + if err != nil { + return err + } + } + return nil +} + +func moveAccountData(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, key, value FROM account_data") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + key string + value []byte + ) + err = rows.Scan(&accountUUID, &key, &value) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_account_data (account_uuid, key, value) VALUES ($1, $2, $3)", accountUUID, key, value) + if err != nil { + return err + } + } + return nil +} + +func movePendingAccountData(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT username, key, value FROM pending_account_data") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + key string + value []byte + ) + err = rows.Scan(&accountUUID, &key, &value) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_pending_account_data (username, key, value) VALUES ($1, $2, $3)", accountUUID, key, value) + if err != nil { + return err + } + } + return nil +} + +func moveSenderKeys(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, address, device, distribution_id, record, created_at FROM sender_keys") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + address string + device int64 + distributionID uuid.UUID + record []byte + createdAt int64 + ) + err = rows.Scan(&accountUUID, &address, &device, &distributionID, &record, &createdAt) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_sender_keys (account_uuid, address, device, distribution_id, record, created_at) VALUES ($1, $2, $3, $4, $5, $6)", accountUUID, address, device, distributionID, record, time.Unix(createdAt, 0)) + if err != nil { + if pqErr, ok := err.(*pq.Error); ok { + if pqErr.Constraint == "signald_sender_keys_account_uuid_fkey" { + log.Println("failed to import sender keys from non-existent account, ignoring") + } else { + return err + } + } else { + return err + } + } + } + return nil +} + +func moveSenderKeyShared(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, distribution_id, address, device FROM sender_key_shared") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + distributionID uuid.UUID + address string + device int64 + ) + err = rows.Scan(&accountUUID, &distributionID, &address, &device) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_sender_key_shared (account_uuid, distribution_id, address, device) VALUES ($1, $2, $3, $4)", accountUUID, distributionID, address, device) + if err != nil { + if pqErr, ok := err.(*pq.Error); ok { + if pqErr.Constraint == "signald_sender_key_shared_account_uuid_fkey" { + log.Println("failed to import sender keys shared from non-existent account, ignoring") + } else if pqErr.Constraint == "signald_sender_key_shared_pkey" { + log.Println("failed to import duplicate sender key shared entry, ignoring") + } + } else { + return err + } + } else { + return err + } + } + return nil +} + +func moveGroups(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT rowid, account_uuid, group_id, master_key, revision, last_avatar_fetch, distribution_id, group_info FROM groups") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + rowID int64 + accountUUID uuid.UUID + groupID []byte + masterKey []byte + revision int64 + lastAvatarFetch int64 + distributionID *uuid.UUID + groupInfo []byte + ) + err = rows.Scan(&rowID, &accountUUID, &groupID, &masterKey, &revision, &lastAvatarFetch, &distributionID, &groupInfo) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_groups (rowid, account_uuid, group_id, master_key, revision, last_avatar_fetch, distribution_id, group_info) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", rowID, accountUUID, groupID, masterKey, revision, lastAvatarFetch, distributionID, groupInfo) + if err != nil { + return err + } + } + return nil +} + +func moveGroupCredentials(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, date, credential FROM group_credentials") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + date int64 + credential []byte + ) + err = rows.Scan(&accountUUID, &date, &credential) + if err != nil { + return err + } + _, err = dest.Exec("INSERT INTO signald_group_credentials (account_uuid, date, credential) VALUES ($1, $2, $3)", accountUUID, date, credential) + if err != nil { + return err + } + } + return nil +} diff --git a/cmd/signaldctl/cmd/db/postgres.go b/cmd/signaldctl/cmd/db/postgres.go new file mode 100644 index 0000000..af2776a --- /dev/null +++ b/cmd/signaldctl/cmd/db/postgres.go @@ -0,0 +1,219 @@ +package db + +var ( + // from signald in src/main/resources/db/migration/postgresql/V1__create_tables.sql + pgScheme = `CREATE TABLE signald_message_queue ( + id SERIAL PRIMARY KEY, + account UUID NOT NULL, + version INTEGER NOT NULL, + type INTEGER NOT NULL, + source_e164 TEXT, + source_uuid UUID, + source_device INTEGER, + "timestamp" BIGINT, + content BYTEA, + legacy_message BYTEA, + server_received_timestamp BIGINT, + server_delivered_timestamp BIGINT, + server_uuid UUID + ); + + CREATE TABLE signald_servers ( + server_uuid UUID PRIMARY KEY, + service_url TEXT NOT NULL, + cdn_urls TEXT NOT NULL, + contact_discovery_url TEXT NOT NULL, + key_backup_url TEXT NOT NULL, + storage_url TEXT NOT NULL, + zk_group_public_params BYTEA NOT NULL, + unidentified_sender_root BYTEA NOT NULL, + proxy TEXT, + ca BYTEA NOT NULL, + key_backup_service_name VARCHAR(64), + key_backup_service_id VARCHAR(64), + key_backup_mrenclave VARCHAR(64), + cds_mrenclave VARCHAR(64), + ias_ca BYTEA + ); + + INSERT INTO signald_servers VALUES + ( + '6e2eb5a8-5706-45d0-8377-127a816411a4', -- server_uuid + 'https://chat.signal.org', -- service_url + '{"0":"https://cdn.signal.org","2":"https://cdn2.signal.org"}', -- cdn_urls + 'https://api.directory.signal.org', -- contact_discovery_url + 'https://api.backup.signal.org', -- key_backup_url + 'https://storage.signal.org', -- storage_url + -- zk_group_public_params + E'\\x00c85fe72c15c084d932c7dffde0b2b9d671f490e692491b57a3c89f31b8a7cc7756a54948588cbe7be510a5ae4686ffd5e6887ad477d4861e01b9b435d3ae1c7f108be45ec62d702e5a73228d60b2d1d605f673cb5faa1d15790384ea3e9d7963304f9b45928205ba3db4a7f85e257f9ed50a71c5ee9f12bf3000d996493d825446df17edb6e0f87de2f8f1231fd0d722d344aacdac35cba0dfbc594032e6ed7dfa9cea063ece785ec106ccf74457e8ad40d1941448d8e97f54bfe01cba4b3b369c86bc2a0ac46c202a01395f227e9cd2a5c871ce2dbe8dd4db87c81ad9ae0b58fc96091d1a28a39084a98281a9d16799b4d5184902bc92b12e78f02967fe7c43e859e4058f939b0e370a3197f6266d807baf71fa2914e60057b119a817de065d', + -- unidentified_sender_root + E'\\x057bba408295cf9300f20b2dcdf3a04501aac8ba8ec0d2872fa20d92fdc81d6305', + -- proxy + NULL, + -- ca + E'\\x0000000100000014e119602f608a01dba6f603f0212fa3a85bbe307c000007b7010011746578747365637572652d67636d2d63610000013da3b16fa9000000000005582e353039000003f3308203ef308202d7a00302010202090089ba2dab4ae4f362300d06092a864886f70d010105050030818d310b30090603550406130255533113301106035504080c0a43616c69666f726e69613116301406035504070c0d53616e204672616e636973636f311d301b060355040a0c144f70656e20576869737065722053797374656d73311d301b060355040b0c144f70656e20576869737065722053797374656d733113301106035504030c0a54657874536563757265301e170d3133303332353232313833355a170d3233303332333232313833355a30818d310b30090603550406130255533113301106035504080c0a43616c69666f726e69613116301406035504070c0d53616e204672616e636973636f311d301b060355040a0c144f70656e20576869737065722053797374656d73311d301b060355040b0c144f70656e20576869737065722053797374656d733113301106035504030c0a5465787453656375726530820122300d06092a864886f70d01010105000382010f003082010a0282010100c14960693820431748b8ab67788c05e4497506a5b796ba054f4028da2faa83f500cec5e5b569fbdf3e8f3ade2e82aead4a0f11e9077e84c3355c8f02e415320c7a8eedf6cd45428940b77984ad5d94d2f89ee957acfb28fa82b49fc7020301c76c2a62346b549009996bdd0d6d70702400a151c5835ac000f2a6ce41f999aa7207fae774d74b9ff420f3d02e870b9fc1962f039836761f06f20b283308b66653b446ee71d8a0c4403d99f22fbc700311bb5e2ed560c019bd479bd5bedc2b0fa082c48de9807ec41fb90c3f439df77c0b8acbe7d7284763b16e625ae462a76432f594acaa00495c388dbebdbda789094754f870a85ce48fd4f41c2fa59e9cd0870203010001a350304e301d0603551d0e04160414018b18f13ffb3919446e8586be946532a7323c90301f0603551d23041830168014018b18f13ffb3919446e8586be946532a7323c90300c0603551d13040530030101ff300d06092a864886f70d010105050003820101007e1ebe210b9ea6d0bbc902ed512ba36e3c36d330437bfe33288c51f11f83f75cf15fba98d1f9b903ab4a4f394e898c8c67ac3d570ff1159934da4d563988e85ecafed4cc939e7dbda92ce73daacbaf2387e76844a1f5b873967f8b5f1b42ad13c6864c40d48308a3727fb36585fca558672722bba6f8b9b27dd573c0b81359c089ae83bfecad6bdba64d21aba03acf5e345935522ad246ec43f8d6425efe8178bac63927b2cf9312b72d6ccc2676f9108243991bf21fca724705a0e909baaa2185688e272fc414e0b6b5c767a7e0319745a2cc3d1c2310b36040fa1d477f33a0232e03c80765f65862867b0fb435c0d522547731b85ac478169825b2c4af02d500bbf2ff8236b854e90220d0ed6cfbc9a370eed025', + -- key_backup_service_name + 'fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe', + -- key_backup_service_id + 'fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe', + -- key_backup_mrenclave + 'a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87', + -- cds_mrenclave + 'c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15', + -- ias_ca + E'\\x00000002000000141e139877c131235200356d48d741b9e8538d4a290000078101000369617300000164adeb7976000000000005582e3530390000054f3082054b308203b3a003020102020900d107765d32a3b094300d06092a864886f70d01010b0500307e310b3009060355040613025553310b300906035504080c0243413114301206035504070c0b53616e746120436c617261311a3018060355040a0c11496e74656c20436f72706f726174696f6e3130302e06035504030c27496e74656c20534758204174746573746174696f6e205265706f7274205369676e696e672043413020170d3136313131343135333733315a180f32303439313233313233353935395a307e310b3009060355040613025553310b300906035504080c0243413114301206035504070c0b53616e746120436c617261311a3018060355040a0c11496e74656c20436f72706f726174696f6e3130302e06035504030c27496e74656c20534758204174746573746174696f6e205265706f7274205369676e696e67204341308201a2300d06092a864886f70d01010105000382018f003082018a02820181009f3c647eb5773cbb512d2732c0d7415ebb55a0fa9ede2e649199e6821db910d53177370977466a6a5e4786ccd2ddebd4149d6a2f6325529dd10cc98737b0779c1a07e29c47a1ae004948476c489f45a5a15d7ac8ecc6acc645adb43d87679df59c093bc5a2e9696c5478541b979e754b573914be55d32ff4c09ddf27219934cd990527b3f92ed78fbf29246abecb71240ef39c2d7107b447545a7ffb10eb060a68a98580219e36910952683892d6a5e2a80803193e407531404e36b315623799aa825074409754a2dfe8f5afd5fe631e1fc2af3808906f28a790d9dd9fe060939b125790c5805d037df56a99531b96de69de33ed226cc1207d1042b5c9ab7f404fc711c0fe4769fb9578b1dc0ec469ea1a25e0ff9914886ef2699b235bb4847dd6ff40b606e6170793c2fb98b314587f9cfd257362dfeab10b3bd2d97673a1a4bd44c453aaf47fc1f2d3d0f384f74a06f89c089f0da6cdb7fceee8c9821a8e54f25c0416d18c46839a5f8012fbdd3dc74d256279adc2c0d55aff6f0622425d1b0203010001a381c93081c630600603551d1f045930573055a053a051864f687474703a2f2f7472757374656473657276696365732e696e74656c2e636f6d2f636f6e74656e742f43524c2f5347582f4174746573746174696f6e5265706f72745369676e696e6743412e63726c301d0603551d0e0416041478437b76a67ebcd0af7e4237eb357c3b8701513c301f0603551d2304183016801478437b76a67ebcd0af7e4237eb357c3b8701513c300e0603551d0f0101ff04040302010630120603551d130101ff040830060101ff020100300d06092a864886f70d01010b05000382018100785f2d60c5c80af42a797610213915da82c9b29e89e0902a25a6c75b16091c68ab204aae711889492c7e1e320911455a8fc13442312e77a63994d99795c8ea4576823cea8ad1e191cfa862fab8a932d3d9b0535a0702d0555f74e520e30330f33480e7adc9d7c81e20703142bf00c528a80b463381fd602a82c7035281aae59562ccb5334ea8903e650b010681f5ce8eb62eac9c414988243aec92f25bf13cdff7ebcc298ee51bba5a3538b66b26cbc45a51de003cad306531ad7cf5d4ef0f8805d1b9133d24135ab3c4641a2f8808349d7333295e0e76ee4bc5227232628efa80d79d92ab4e3d1120f3fb5ad119cd8d544aa1d4a6865e6b57beac5771307e2e3cb9070da47b4bfc8869e01413ea093541de8a792811b74636c5e91452cf0cee59f2fb404acd0bc584cb9c835404734c0e7ec6605cdfcf2ff439b6d4719f702f0e0c3fa04fdb12a6cb2ad1ab1c9af1f8f4c3a08edd72a32b0bb5d0ad256ffd159a683b2a5a1f1d11fa62532f03d754caef0da5735a1e5a884c7e89d91218c9d7008515e5f5992ccc471f3b1bc1aaec24a2997e6ad3' + ), + ( + '97c17f0c-e53b-426f-8ffa-c052d4183f83', -- server_uuid + 'https://chat.staging.signal.org', -- service_url + '{"0":"https://cdn-staging.signal.org","2":"https://cdn2-staging.signal.org"}', -- cdn_urls + 'https://api-staging.directory.signal.org', -- contact_discovery_url + 'https://api-staging.backup.signal.org', -- key_backup_url + 'https://storage-staging.signal.org', -- storage_url + -- zk_group_public_params + E'\\x001498db555c91071b49754d08645825c7d61e200c666a53b5310b7039b181d15bb69fdb5ac4b165d30acdf0a9f2bbc8b3ca1c094dc1dfb7d3debe0c8b9a807a6786791d97fbf626386479a1fba2eed0f998341fb2d008f62fb85a932d21ef0a0b7c14e70dc89eadee356566a06b692a776c35fc09ac28341ddf7398e6e1ca95274a47d89f6a2830e3a70697dd6a746daef7ad6546b20cc482e624917172a9765ba4ae9cf3b0222f1308f042525854f3903e3e15d05e145d705d1d22cad39ba83c10901bc1bdad820679d62c0a52579dbae01981b778c4c6e619f1e17e27b404418042ee3165941047d22b49a35e0fbfda53e659c4d9591f6792a81040fd2d6f3ba23e6ef81f6c0c3b8bb559a7def94c32225213f4beca2d2d7d030f2be2c3eb5d', + -- unidentified_sender_root + E'\\x05ba98d43ce8844e0d519a1517e2f5f2850facade420b9652c4261d949cf4ac131', + -- proxy + NULL, + -- ca + E'\\x0000000100000014e119602f608a01dba6f603f0212fa3a85bbe307c000007b7010011746578747365637572652d67636d2d63610000013da3b16fa9000000000005582e353039000003f3308203ef308202d7a00302010202090089ba2dab4ae4f362300d06092a864886f70d010105050030818d310b30090603550406130255533113301106035504080c0a43616c69666f726e69613116301406035504070c0d53616e204672616e636973636f311d301b060355040a0c144f70656e20576869737065722053797374656d73311d301b060355040b0c144f70656e20576869737065722053797374656d733113301106035504030c0a54657874536563757265301e170d3133303332353232313833355a170d3233303332333232313833355a30818d310b30090603550406130255533113301106035504080c0a43616c69666f726e69613116301406035504070c0d53616e204672616e636973636f311d301b060355040a0c144f70656e20576869737065722053797374656d73311d301b060355040b0c144f70656e20576869737065722053797374656d733113301106035504030c0a5465787453656375726530820122300d06092a864886f70d01010105000382010f003082010a0282010100c14960693820431748b8ab67788c05e4497506a5b796ba054f4028da2faa83f500cec5e5b569fbdf3e8f3ade2e82aead4a0f11e9077e84c3355c8f02e415320c7a8eedf6cd45428940b77984ad5d94d2f89ee957acfb28fa82b49fc7020301c76c2a62346b549009996bdd0d6d70702400a151c5835ac000f2a6ce41f999aa7207fae774d74b9ff420f3d02e870b9fc1962f039836761f06f20b283308b66653b446ee71d8a0c4403d99f22fbc700311bb5e2ed560c019bd479bd5bedc2b0fa082c48de9807ec41fb90c3f439df77c0b8acbe7d7284763b16e625ae462a76432f594acaa00495c388dbebdbda789094754f870a85ce48fd4f41c2fa59e9cd0870203010001a350304e301d0603551d0e04160414018b18f13ffb3919446e8586be946532a7323c90301f0603551d23041830168014018b18f13ffb3919446e8586be946532a7323c90300c0603551d13040530030101ff300d06092a864886f70d010105050003820101007e1ebe210b9ea6d0bbc902ed512ba36e3c36d330437bfe33288c51f11f83f75cf15fba98d1f9b903ab4a4f394e898c8c67ac3d570ff1159934da4d563988e85ecafed4cc939e7dbda92ce73daacbaf2387e76844a1f5b873967f8b5f1b42ad13c6864c40d48308a3727fb36585fca558672722bba6f8b9b27dd573c0b81359c089ae83bfecad6bdba64d21aba03acf5e345935522ad246ec43f8d6425efe8178bac63927b2cf9312b72d6ccc2676f9108243991bf21fca724705a0e909baaa2185688e272fc414e0b6b5c767a7e0319745a2cc3d1c2310b36040fa1d477f33a0232e03c80765f65862867b0fb435c0d522547731b85ac478169825b2c4af02d500bbf2ff8236b854e90220d0ed6cfbc9a370eed025', + -- key_backup_service_name + '823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9', + -- key_backup_service_id + '16b94ac6d2b7f7b9d72928f36d798dbb35ed32e7bb14c42b4301ad0344b46f29', + -- key_backup_mrenclave + 'a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87', + -- cds_mrenclave + 'c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15', + -- ias_ca + E'\\x00000002000000141e139877c131235200356d48d741b9e8538d4a290000078101000369617300000164adeb7976000000000005582e3530390000054f3082054b308203b3a003020102020900d107765d32a3b094300d06092a864886f70d01010b0500307e310b3009060355040613025553310b300906035504080c0243413114301206035504070c0b53616e746120436c617261311a3018060355040a0c11496e74656c20436f72706f726174696f6e3130302e06035504030c27496e74656c20534758204174746573746174696f6e205265706f7274205369676e696e672043413020170d3136313131343135333733315a180f32303439313233313233353935395a307e310b3009060355040613025553310b300906035504080c0243413114301206035504070c0b53616e746120436c617261311a3018060355040a0c11496e74656c20436f72706f726174696f6e3130302e06035504030c27496e74656c20534758204174746573746174696f6e205265706f7274205369676e696e67204341308201a2300d06092a864886f70d01010105000382018f003082018a02820181009f3c647eb5773cbb512d2732c0d7415ebb55a0fa9ede2e649199e6821db910d53177370977466a6a5e4786ccd2ddebd4149d6a2f6325529dd10cc98737b0779c1a07e29c47a1ae004948476c489f45a5a15d7ac8ecc6acc645adb43d87679df59c093bc5a2e9696c5478541b979e754b573914be55d32ff4c09ddf27219934cd990527b3f92ed78fbf29246abecb71240ef39c2d7107b447545a7ffb10eb060a68a98580219e36910952683892d6a5e2a80803193e407531404e36b315623799aa825074409754a2dfe8f5afd5fe631e1fc2af3808906f28a790d9dd9fe060939b125790c5805d037df56a99531b96de69de33ed226cc1207d1042b5c9ab7f404fc711c0fe4769fb9578b1dc0ec469ea1a25e0ff9914886ef2699b235bb4847dd6ff40b606e6170793c2fb98b314587f9cfd257362dfeab10b3bd2d97673a1a4bd44c453aaf47fc1f2d3d0f384f74a06f89c089f0da6cdb7fceee8c9821a8e54f25c0416d18c46839a5f8012fbdd3dc74d256279adc2c0d55aff6f0622425d1b0203010001a381c93081c630600603551d1f045930573055a053a051864f687474703a2f2f7472757374656473657276696365732e696e74656c2e636f6d2f636f6e74656e742f43524c2f5347582f4174746573746174696f6e5265706f72745369676e696e6743412e63726c301d0603551d0e0416041478437b76a67ebcd0af7e4237eb357c3b8701513c301f0603551d2304183016801478437b76a67ebcd0af7e4237eb357c3b8701513c300e0603551d0f0101ff04040302010630120603551d130101ff040830060101ff020100300d06092a864886f70d01010b05000382018100785f2d60c5c80af42a797610213915da82c9b29e89e0902a25a6c75b16091c68ab204aae711889492c7e1e320911455a8fc13442312e77a63994d99795c8ea4576823cea8ad1e191cfa862fab8a932d3d9b0535a0702d0555f74e520e30330f33480e7adc9d7c81e20703142bf00c528a80b463381fd602a82c7035281aae59562ccb5334ea8903e650b010681f5ce8eb62eac9c414988243aec92f25bf13cdff7ebcc298ee51bba5a3538b66b26cbc45a51de003cad306531ad7cf5d4ef0f8805d1b9133d24135ab3c4641a2f8808349d7333295e0e76ee4bc5227232628efa80d79d92ab4e3d1120f3fb5ad119cd8d544aa1d4a6865e6b57beac5771307e2e3cb9070da47b4bfc8869e01413ea093541de8a792811b74636c5e91452cf0cee59f2fb404acd0bc584cb9c835404734c0e7ec6605cdfcf2ff439b6d4719f702f0e0c3fa04fdb12a6cb2ad1ab1c9af1f8f4c3a08edd72a32b0bb5d0ad256ffd159a683b2a5a1f1d11fa62532f03d754caef0da5735a1e5a884c7e89d91218c9d7008515e5f5992ccc471f3b1bc1aaec24a2997e6ad3' + ); + + CREATE TABLE signald_accounts ( + uuid UUID NOT NULL, + e164 TEXT NOT NULL, + filename TEXT NOT NULL, + server UUID NOT NULL REFERENCES signald_servers(server_uuid) ON DELETE CASCADE, + + PRIMARY KEY (uuid, e164, filename, server), + UNIQUE (e164), + UNIQUE (filename), + UNIQUE (uuid) + ); + + CREATE TABLE signald_recipients ( + rowid SERIAL PRIMARY KEY, + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + uuid UUID, + e164 TEXT, + + UNIQUE (account_uuid, e164, uuid) + ); + + CREATE TABLE signald_prekeys ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + id INTEGER NOT NULL, + record BYTEA NOT NULL, + + PRIMARY KEY (account_uuid, id) + ); + + CREATE TABLE signald_sessions ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + recipient INTEGER NOT NULL REFERENCES signald_recipients(rowid) ON DELETE CASCADE, + device_id INTEGER, + record BYTEA NOT NULL, + + PRIMARY KEY (account_uuid, recipient, device_id) + ); + + CREATE TABLE signald_signed_prekeys ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + id INTEGER NOT NULL, + record BYTEA NOT NULL, + + PRIMARY KEY (account_uuid, id) + ); + + CREATE TABLE signald_identity_keys ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + recipient INTEGER NOT NULL, + identity_key BYTEA NOT NULL, + trust_level TEXT NOT NULL, + added TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, + + PRIMARY KEY (account_uuid, recipient, identity_key) + ); + + CREATE TABLE signald_account_data ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + key TEXT NOT NULL, + value BYTEA NOT NULL, + + PRIMARY KEY (account_uuid, key) + ); + + CREATE TABLE signald_pending_account_data ( + username TEXT, + key TEXT NOT NULL, + value BYTEA NOT NULL, + + PRIMARY KEY (username, key) + ); + + CREATE TABLE signald_sender_keys ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + address TEXT NOT NULL, + device INTEGER NOT NULL, + distribution_id UUID NOT NULL, + record BYTEA NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + + PRIMARY KEY (account_uuid, address, device, distribution_id) + ); + + CREATE TABLE signald_sender_key_shared ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + distribution_id UUID NOT NULL, + address TEXT NOT NULL, + device INTEGER NOT NULL, + + PRIMARY KEY (account_uuid, address, device) + ); + + CREATE TABLE signald_groups ( + rowid SERIAL PRIMARY KEY, + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + group_id BYTEA NOT NULL, + master_key BYTEA NOT NULL, + revision INTEGER NOT NULL, + last_avatar_fetch INTEGER, + distribution_id UUID, + group_info BYTEA, + + UNIQUE (account_uuid, group_id) + ); + + CREATE TABLE signald_group_credentials ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + date BIGINT NOT NULL, + credential BYTEA NOT NULL, + + PRIMARY KEY (account_uuid, date) + ); + + CREATE TABLE public.flyway_schema_history ( + installed_rank integer NOT NULL, + version character varying(50), + description character varying(200) NOT NULL, + type character varying(20) NOT NULL, + script character varying(1000) NOT NULL, + checksum integer, + installed_by character varying(100) NOT NULL, + installed_on timestamp without time zone DEFAULT now() NOT NULL, + execution_time integer NOT NULL, + success boolean NOT NULL + ); +` +) diff --git a/cmd/signaldctl/cmd/root.go b/cmd/signaldctl/cmd/root.go index ac78c5b..4d4db93 100644 --- a/cmd/signaldctl/cmd/root.go +++ b/cmd/signaldctl/cmd/root.go @@ -24,6 +24,7 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account" configcmd "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/config" + "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/db" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/device" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/key" @@ -80,6 +81,7 @@ func init() { RootCmd.PersistentFlags().StringVarP(&common.OutputFormat, "output-format", "o", "default", "the output format. options are usually table, yaml and json, default is usually table. Some commands have other options.") RootCmd.AddCommand(account.AccountCmd) RootCmd.AddCommand(configcmd.ConfigCmd) + RootCmd.AddCommand(db.MoveCmd) RootCmd.AddCommand(device.DeviceCmd) RootCmd.AddCommand(group.GroupCmd) RootCmd.AddCommand(key.KeyCmd) diff --git a/debian/control b/debian/control index 989ff78..d9543e0 100644 --- a/debian/control +++ b/debian/control @@ -9,7 +9,10 @@ Build-Depends: debhelper (>= 11), golang-any, golang-github-spf13-cobra-dev, golang-github-spf13-viper-dev, - golang-github-google-uuid-dev + golang-github-google-uuid-dev, + golang-github-mattn-go-sqlite3-dev, + golang-github-lib-pq-dev, + golang-github-satori-go.uuid-dev Standards-Version: 4.2.1 Homepage: https://signald.org Vcs-Browser: https://gitlab.com/signald/signald-go diff --git a/go.mod b/go.mod index a08f00b..5b9aa22 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,12 @@ require ( github.com/google/uuid v1.3.0 github.com/jedib0t/go-pretty/v6 v6.1.0 github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.10.4 github.com/mattn/go-runewidth v0.0.10 // indirect + github.com/mattn/go-sqlite3 v1.14.11 github.com/mdp/qrterminal v1.0.1 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/satori/go.uuid v1.2.0 github.com/spf13/cobra v1.1.2 github.com/stretchr/testify v1.6.1 // indirect golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect diff --git a/go.sum b/go.sum index d22c029..55fa519 100644 --- a/go.sum +++ b/go.sum @@ -112,12 +112,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ= +github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c= github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ= @@ -160,6 +164,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= From 9a371b8f91d144589b3b298920b51da6d330f0d6 Mon Sep 17 00:00:00 2001 From: finn Date: Wed, 23 Feb 2022 07:16:27 -0800 Subject: [PATCH 37/73] fix apt publishing --- .gitlab-ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 994e572..9c3dc5d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -142,9 +142,11 @@ publish deb: - aptly repo create signald - aptly mirror create -ignore-signatures backfill-mirror https://updates.signald.org "${DISTRIBUTION}" main - aptly mirror update -ignore-signatures backfill-mirror - - aptly repo import backfill-mirror signald signald signaldctl + - aptly repo import backfill-mirror signald signald 'signaldctl (>= 0.3.0)' - aptly repo add signald *.deb - - aptly publish repo -config=.aptly.conf -batch -gpg-key="${SIGNING_KEY}" -distribution="${DISTRIBUTION}" "signald" "s3:updates.signald.org:" + - gpg1 --import "${SIGNING_KEY_PATH}" + - gpg1 --list-secret-keys + - aptly publish repo -config=.aptly.conf -batch -gpg-key="${SIGNING_KEY_ID}" -distribution="${DISTRIBUTION}" "signald" "s3:updates.signald.org:" variables: DISTRIBUTION: unstable rules: From 17f823064f1944c75bc72b68eb0039689ec251f4 Mon Sep 17 00:00:00 2001 From: finn Date: Tue, 1 Mar 2022 16:00:37 -0800 Subject: [PATCH 38/73] reset the value generator for SERIAL columns after import --- cmd/signaldctl/cmd/db/migrate.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 7c57dd8..bad9eb0 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -230,6 +230,12 @@ func moveRecipients(source *sql.DB, dest *sql.DB) error { return err } } + + // start new rowids one above the current max value + _, err = dest.Exec("SELECT setval(pg_get_serial_sequence('signald_recipients', 'rowid', (SELECT MAX(rowid) FROM signald_recipients)+1);") + if err != nil { + return err + } return nil } @@ -494,6 +500,12 @@ func moveGroups(source *sql.DB, dest *sql.DB) error { return err } } + + // start new rowids one above the current max value + _, err = dest.Exec("SELECT setval(pg_get_serial_sequence('signald_groups', 'rowid', (SELECT MAX(rowid) FROM signald_groups)+1);") + if err != nil { + return err + } return nil } From 56cc98de30d8104bda642f9a72ae450ff1a66a74 Mon Sep 17 00:00:00 2001 From: finn Date: Tue, 1 Mar 2022 16:56:56 -0800 Subject: [PATCH 39/73] fix queries --- cmd/signaldctl/cmd/db/migrate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index bad9eb0..b3ea5e3 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -232,7 +232,7 @@ func moveRecipients(source *sql.DB, dest *sql.DB) error { } // start new rowids one above the current max value - _, err = dest.Exec("SELECT setval(pg_get_serial_sequence('signald_recipients', 'rowid', (SELECT MAX(rowid) FROM signald_recipients)+1);") + _, err = dest.Exec("SELECT setval(pg_get_serial_sequence('signald_recipients', 'rowid'), (SELECT MAX(rowid) FROM signald_recipients)+1)") if err != nil { return err } @@ -502,7 +502,7 @@ func moveGroups(source *sql.DB, dest *sql.DB) error { } // start new rowids one above the current max value - _, err = dest.Exec("SELECT setval(pg_get_serial_sequence('signald_groups', 'rowid', (SELECT MAX(rowid) FROM signald_groups)+1);") + _, err = dest.Exec("SELECT setval(pg_get_serial_sequence('signald_groups', 'rowid'), (SELECT MAX(rowid) FROM signald_groups)+1)") if err != nil { return err } From 21420e0d77d29cc13d6dfa540869954b75f30bc5 Mon Sep 17 00:00:00 2001 From: finn Date: Wed, 2 Mar 2022 13:10:41 -0800 Subject: [PATCH 40/73] pending account data table uses string account identifier not uuid --- cmd/signaldctl/cmd/db/migrate.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index b3ea5e3..6edb49c 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -385,15 +385,15 @@ func movePendingAccountData(source *sql.DB, dest *sql.DB) error { for rows.Next() { var ( - accountUUID uuid.UUID - key string - value []byte + username string + key string + value []byte ) - err = rows.Scan(&accountUUID, &key, &value) + err = rows.Scan(&username, &key, &value) if err != nil { return err } - _, err = dest.Exec("INSERT INTO signald_pending_account_data (username, key, value) VALUES ($1, $2, $3)", accountUUID, key, value) + _, err = dest.Exec("INSERT INTO signald_pending_account_data (username, key, value) VALUES ($1, $2, $3)", username, key, value) if err != nil { return err } From fa1f0765d5b5863dcbedb595fa951c1dd11d7415 Mon Sep 17 00:00:00 2001 From: finn Date: Wed, 2 Mar 2022 13:42:12 -0800 Subject: [PATCH 41/73] allow null recipient UUIDs --- cmd/signaldctl/cmd/db/migrate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 6edb49c..01e1d4a 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -218,7 +218,7 @@ func moveRecipients(source *sql.DB, dest *sql.DB) error { var ( rowID int64 accountUUID uuid.UUID - recipientUUID uuid.UUID + recipientUUID uuid.NullUUID e164 sql.NullString ) err = rows.Scan(&rowID, &accountUUID, &recipientUUID, &e164) From 8bb40967e5858ee8c93debc65f56c3eb218fe83d Mon Sep 17 00:00:00 2001 From: finn Date: Wed, 2 Mar 2022 15:16:25 -0800 Subject: [PATCH 42/73] forget e164s that don't start with a + --- cmd/signaldctl/cmd/db/migrate.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 01e1d4a..0bb6c9b 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -7,6 +7,7 @@ import ( "log" "os" "os/user" + "strings" "time" "github.com/lib/pq" @@ -225,6 +226,13 @@ func moveRecipients(source *sql.DB, dest *sql.DB) error { if err != nil { return err } + + if e164.Valid && !strings.HasPrefix(e164.String, "+") { + log.Println("corrupt e164 found, setting to null") + e164.Valid = false + e164.String = "" + } + _, err = dest.Exec("INSERT INTO signald_recipients (rowid, account_uuid, uuid, e164) VALUES ($1, $2, $3, $4)", rowID, accountUUID, recipientUUID, e164) if err != nil { return err From 689d560eb17da613d057097545b8d205e32c22e4 Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Mon, 7 Mar 2022 21:03:24 -0700 Subject: [PATCH 43/73] move script: update for new contacts table --- cmd/signaldctl/cmd/db/migrate.go | 67 +++++++++++++++++++++++++++++-- cmd/signaldctl/cmd/db/postgres.go | 12 ++++++ 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 0bb6c9b..8aa5c43 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -148,6 +148,12 @@ var ( } log.Println("moved group credentials table") + if err := moveContacts(source, dest); err != nil { + log.Println("error migrating group credentials table") + return err + } + log.Println("moved contacts table") + if err := os.Remove(sqlitePath); err != nil { log.Println("error deleting sqlite file") return err @@ -159,14 +165,26 @@ var ( ) func verifyMigration(source *sql.DB) error { - rows, err := source.Query("SELECT * FROM flyway_schema_history WHERE version = 11") + // Lower bound of the database state. + rows, err := source.Query("SELECT * FROM flyway_schema_history WHERE version = 12") if err != nil { return err } defer rows.Close() if !rows.Next() { - return errors.New("source database is not up to date! Please update to signald 0.17.0-16-5101c6ef and start it to move the sqlite database to an acceptable format") + return errors.New("source database is not up to date! Please update signald and start it to move the sqlite database to an acceptable format") + } + + // Upper bound of the database state + rows, err = source.Query("SELECT * FROM flyway_schema_history WHERE version = 13") + if err != nil { + return err + } + defer rows.Close() + + if rows.Next() { + return errors.New("source database is too new! Please update signaldctl to the latest version") } return nil } @@ -177,8 +195,16 @@ func createSchema(dest *sql.DB) error { return err } - _, err = dest.Exec("INSERT INTO flyway_schema_history (installed_rank, version, description, type, script, checksum, installed_by, execution_time, success) VALUES ($1, $2, $3, $4, $5, $6, current_user, $7, $8)", - 1, 1, "create tables", "SQL", "V1__create_tables.sql", -1247750968, 0, true) + _, err = dest.Exec(` + INSERT INTO flyway_schema_history + (installed_rank, version, description, type, script, checksum, installed_by, execution_time, success) + VALUES ($1, $2, $3, $4, $5, $6, current_user, $7, $8), + ($9, $10, $11, $12, $13, $14, current_user, $15, $16) + `, + // Row 1 + 1, 1, "create tables", "SQL", "V1__create_tables.sql", -1247750968, 0, true, + // Row 2 + 2, 12, "create contacts table", "SQL", "V12__create_contacts_table.sql", -852729911, 0, true) return err } @@ -541,3 +567,36 @@ func moveGroupCredentials(source *sql.DB, dest *sql.DB) error { } return nil } + +func moveContacts(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, recipient, name, color, profile_key, message_expiration_time, inbox_position FROM contacts") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + recipient int64 + name sql.NullString + color sql.NullString + profile_key []byte + message_expiration_time sql.NullInt64 + inbox_position sql.NullInt64 + ) + err = rows.Scan(&accountUUID, &recipient, &name, &color, &profile_key, &message_expiration_time, &inbox_position) + if err != nil { + return err + } + _, err = dest.Exec(` + INSERT INTO signald_contacts + (account_uuid, recipient, name, color, profile_key, message_expiration_time, inbox_position) + VALUES ($1, $2, $3, $4, $5, $6, $7) + `, accountUUID, recipient, name, color, profile_key, message_expiration_time, inbox_position) + if err != nil { + return err + } + } + return nil +} diff --git a/cmd/signaldctl/cmd/db/postgres.go b/cmd/signaldctl/cmd/db/postgres.go index af2776a..47f7388 100644 --- a/cmd/signaldctl/cmd/db/postgres.go +++ b/cmd/signaldctl/cmd/db/postgres.go @@ -215,5 +215,17 @@ var ( execution_time integer NOT NULL, success boolean NOT NULL ); + + CREATE TABLE signald_contacts ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + recipient INTEGER NOT NULL REFERENCES signald_recipients(rowid) ON DELETE CASCADE, + name TEXT, + color TEXT, + profile_key BYTEA, + message_expiration_time INTEGER, + inbox_position INTEGER, + + PRIMARY KEY (account_uuid, recipient) + ); ` ) From 3ccb424f53061658112c2735ff104352bf20a283 Mon Sep 17 00:00:00 2001 From: finn Date: Tue, 15 Mar 2022 19:04:35 -0700 Subject: [PATCH 44/73] don't push messages with IDs to the channel --- signald/signald.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signald/signald.go b/signald/signald.go index 5a0f9d8..0cfd740 100644 --- a/signald/signald.go +++ b/signald/signald.go @@ -125,7 +125,7 @@ func (s *Signald) Listen(c chan client_protocol.BasicResponse) { subscribers <- msg } - if c != nil { + if c != nil && msg.ID == "" && msg.Type != "version" { c <- msg } } From 4bc97fb829f6be1a24b5da73bbeab95bdac7f119 Mon Sep 17 00:00:00 2001 From: finn Date: Fri, 18 Mar 2022 19:10:36 -0700 Subject: [PATCH 45/73] add signaldctl subscribe subcommand with initial test at a parsable output format --- cmd/signaldctl/cmd/subscribe.go | 96 +++++++++++++++++++++++++++++ signald/client-protocol/protocol.go | 12 ++-- signald/signald.go | 2 +- 3 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 cmd/signaldctl/cmd/subscribe.go diff --git a/cmd/signaldctl/cmd/subscribe.go b/cmd/signaldctl/cmd/subscribe.go new file mode 100644 index 0000000..dfa0638 --- /dev/null +++ b/cmd/signaldctl/cmd/subscribe.go @@ -0,0 +1,96 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/spf13/cobra" + + "gitlab.com/signald/signald-go/cmd/signaldctl/common" + "gitlab.com/signald/signald-go/cmd/signaldctl/config" + client_protocol "gitlab.com/signald/signald-go/signald/client-protocol" + v1 "gitlab.com/signald/signald-go/signald/client-protocol/v1" +) + +var ( + accountIdentifier string + subscribeCmd = &cobra.Command{ + Use: "subscribe", + Short: "subscribe to incoming messages from signald", + Long: `subscribe to incoming messages from signald. + + if no default account is set, the -a/--account argument is required. + + the default output format (-o default) is being worked on, subject to change and should not be relied upon. Thoughts? come by the #signald IRC/matrix room + + if you want to future-proof your scripts, use json output (-o json) and parse it.`, + PreRun: func(cmd *cobra.Command, args []string) { + if accountIdentifier == "" { + accountIdentifier = config.Config.DefaultAccount + } + if accountIdentifier == "" { + common.Must(cmd.Help()) + log.Fatal("No account specified. Please specify with --account or set a default") + } + }, + RunE: func(cmd *cobra.Command, args []string) error { + incoming := make(chan client_protocol.BasicResponse) + go common.Signald.Listen(incoming) + + req := v1.SubscribeRequest{Account: accountIdentifier} + err := req.Submit(common.Signald) + if err != nil { + panic(err) + } + + for msg := range incoming { + switch common.OutputFormat { + case common.OutputFormatDefault: + if msg.Type != "IncomingMessage" { + continue + } + var data v1.IncomingMessage + err := json.Unmarshal(msg.Data, &data) + if err != nil { + panic(err) + } + + if data.DataMessage == nil { + continue + } + + group := "-" + if data.DataMessage.GroupV2 != nil { + group = fmt.Sprintf("%s|%d", data.DataMessage.GroupV2.ID, data.DataMessage.GroupV2.Revision) + } + + attachment := "-" + if len(data.DataMessage.Attachments) > 0 { + attachment = data.DataMessage.Attachments[0].StoredFilename + } + + body := "-" + if data.DataMessage.Body != "" { + body = data.DataMessage.Body + } + fmt.Println(data.DataMessage.Timestamp, data.Account, data.Source.UUID, group, attachment, body) + case common.OutputFormatJSON: + err := json.NewEncoder(os.Stdout).Encode(msg) + if err != nil { + panic(err) + } + default: + log.Fatal("unsupported output format") + } + } + return nil + }, + } +) + +func init() { + subscribeCmd.Flags().StringVarP(&accountIdentifier, "account", "a", "", "the signald account to use") + RootCmd.AddCommand(subscribeCmd) +} diff --git a/signald/client-protocol/protocol.go b/signald/client-protocol/protocol.go index 1403b18..c0cb562 100644 --- a/signald/client-protocol/protocol.go +++ b/signald/client-protocol/protocol.go @@ -5,10 +5,10 @@ import ( ) type BasicResponse struct { - ID string - Type string - ErrorType string - Error json.RawMessage - Data json.RawMessage - Account string + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + ErrorType string `json:"error_type,omitempty"` + Error json.RawMessage `json:"error,omitempty"` + Data json.RawMessage `json:"data,omitempty"` + Account string `json:"account,omitempty"` } diff --git a/signald/signald.go b/signald/signald.go index 0cfd740..9f646f0 100644 --- a/signald/signald.go +++ b/signald/signald.go @@ -125,7 +125,7 @@ func (s *Signald) Listen(c chan client_protocol.BasicResponse) { subscribers <- msg } - if c != nil && msg.ID == "" && msg.Type != "version" { + if c != nil && !(msg.ID == "" && msg.Type == "version") { c <- msg } } From 0f3e6dce72d4e61a2450a4271bd6ac434b98b0f6 Mon Sep 17 00:00:00 2001 From: finn Date: Sat, 19 Mar 2022 01:38:25 -0700 Subject: [PATCH 46/73] allow send to accept message from stdin --- .../cmd/message/send/send-message.go | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/cmd/signaldctl/cmd/message/send/send-message.go b/cmd/signaldctl/cmd/message/send/send-message.go index e4f705e..81b4f44 100644 --- a/cmd/signaldctl/cmd/message/send/send-message.go +++ b/cmd/signaldctl/cmd/message/send/send-message.go @@ -18,6 +18,7 @@ package send import ( "encoding/json" "fmt" + "io/ioutil" "log" "os" "path/filepath" @@ -37,9 +38,10 @@ var ( toAddress *v1.JsonAddress toGroup string attachments []string + message string SendMessageCmd = &cobra.Command{ - Use: "send ", + Use: "send {group id | phone number} [message]", Short: "send a message", PreRun: func(cmd *cobra.Command, args []string) { if account == "" { @@ -49,9 +51,20 @@ var ( common.Must(cmd.Help()) log.Fatal("No account specified. Please specify with --account or set a default") } - if len(args) < 2 { + if len(args) < 1 { common.Must(cmd.Help()) - log.Fatal("must specify both destination (either group id or phone number) and message") + log.Fatal("must specify both destination (either group id or phone number)") + } + + if len(args) == 1 { + messageBytes, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Println("error reading message from stdin, perhaps you meant to include it in the command line arguments?") + panic(err) + } + message = string(messageBytes) + } else { + message = strings.Join(args[1:], " ") } to, err := common.StringToAddress(args[0]) if err != nil { @@ -65,7 +78,7 @@ var ( req := v1.SendRequest{ Username: account, - MessageBody: strings.Join(args[1:], " "), + MessageBody: message, Attachments: []*v1.JsonAttachment{}, RecipientAddress: toAddress, RecipientGroupID: toGroup, From 2b1f0dada7602ce8746d132ec68821fb47bf23d0 Mon Sep 17 00:00:00 2001 From: finn Date: Sat, 19 Mar 2022 16:57:19 -0700 Subject: [PATCH 47/73] don't panic if we can't unmarshal a message from signald --- cmd/signaldctl/cmd/subscribe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/signaldctl/cmd/subscribe.go b/cmd/signaldctl/cmd/subscribe.go index dfa0638..b649b22 100644 --- a/cmd/signaldctl/cmd/subscribe.go +++ b/cmd/signaldctl/cmd/subscribe.go @@ -54,7 +54,7 @@ var ( var data v1.IncomingMessage err := json.Unmarshal(msg.Data, &data) if err != nil { - panic(err) + continue } if data.DataMessage == nil { From 7ff54921568d266d52e858d23dec353e0dad14ae Mon Sep 17 00:00:00 2001 From: finn Date: Sat, 19 Mar 2022 22:44:31 -0700 Subject: [PATCH 48/73] expose close() option for socket --- signald/signald.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/signald/signald.go b/signald/signald.go index 9f646f0..202ec46 100644 --- a/signald/signald.go +++ b/signald/signald.go @@ -99,6 +99,11 @@ func (s *Signald) connect() error { return nil } + +func (s *Signald) Close() error { + return s.socket.Close() +} + // Listen listens for events from signald func (s *Signald) Listen(c chan client_protocol.BasicResponse) { for { From ec276e54113b360b6e22cc6cb78df038ecb157a1 Mon Sep 17 00:00:00 2001 From: finn Date: Fri, 25 Mar 2022 22:26:47 -0700 Subject: [PATCH 49/73] add group preview subcommand --- cmd/signaldctl/cmd/group/preview/preview.go | 78 +++++++++++++++++++++ cmd/signaldctl/cmd/group/root.go | 2 + 2 files changed, 80 insertions(+) create mode 100644 cmd/signaldctl/cmd/group/preview/preview.go diff --git a/cmd/signaldctl/cmd/group/preview/preview.go b/cmd/signaldctl/cmd/group/preview/preview.go new file mode 100644 index 0000000..43cf888 --- /dev/null +++ b/cmd/signaldctl/cmd/group/preview/preview.go @@ -0,0 +1,78 @@ +// Copyright © 2021 Finn Herzfeld +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package preview + +import ( + "encoding/json" + "errors" + "log" + "os" + "strings" + + "github.com/spf13/cobra" + + "gitlab.com/signald/signald-go/cmd/signaldctl/common" + "gitlab.com/signald/signald-go/cmd/signaldctl/config" + "gitlab.com/signald/signald-go/signald/client-protocol/v1" +) + +var ( + account string + joinURL string + PreviewGroupCmd = &cobra.Command{ + Use: "preview https://signal.group/...", + Short: "preview information about a group without joining", + PreRunE: func(cmd *cobra.Command, args []string) error { + if account == "" { + account = config.Config.DefaultAccount + } + if account == "" { + common.Must(cmd.Help()) + log.Fatal("No account specified. Please specify with --account or set a default") + } + + if len(args) == 0 { + return errors.New("please specify signald.group URL") + } + joinURL = args[0] + + if !strings.HasPrefix(joinURL, "https://signal.group/#") { + return errors.New("invalid group link (must start with https://signal.group/#") + } + + return nil + }, + RunE: func(_ *cobra.Command, args []string) error { + go common.Signald.Listen(nil) + req := v1.GroupLinkInfoRequest{ + Account: account, + Uri: joinURL, + } + + info, err := req.Submit(common.Signald) + if err != nil { + return err + } + + switch common.OutputFormat { + case common.OutputFormatJSON, common.OutputFormatDefault: + return json.NewEncoder(os.Stdout).Encode(info) + } + + return nil + }, + } +) diff --git a/cmd/signaldctl/cmd/group/root.go b/cmd/signaldctl/cmd/group/root.go index 3f7d0ea..8be78ea 100644 --- a/cmd/signaldctl/cmd/group/root.go +++ b/cmd/signaldctl/cmd/group/root.go @@ -24,6 +24,7 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/join" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/leave" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/list" + "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/preview" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/removemember" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/show" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/group/update" @@ -41,6 +42,7 @@ func init() { GroupCmd.AddCommand(join.JoinGroupCmd) GroupCmd.AddCommand(leave.LeaveGroupCmd) GroupCmd.AddCommand(list.ListGroupCmd) + GroupCmd.AddCommand(preview.PreviewGroupCmd) GroupCmd.AddCommand(removemember.RemoveMemberCmd) GroupCmd.AddCommand(show.ShowGroupCmd) GroupCmd.AddCommand(update.UpdateGroupCmd) From d8fdb8edb00733761e1e2b1087b15309dfc20d56 Mon Sep 17 00:00:00 2001 From: finn Date: Sat, 26 Mar 2022 01:23:26 -0700 Subject: [PATCH 50/73] add table output to preview subcommand --- cmd/signaldctl/cmd/group/preview/preview.go | 26 ++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/cmd/signaldctl/cmd/group/preview/preview.go b/cmd/signaldctl/cmd/group/preview/preview.go index 43cf888..4f51e77 100644 --- a/cmd/signaldctl/cmd/group/preview/preview.go +++ b/cmd/signaldctl/cmd/group/preview/preview.go @@ -22,7 +22,9 @@ import ( "os" "strings" + "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" + "gopkg.in/yaml.v2" "gitlab.com/signald/signald-go/cmd/signaldctl/common" "gitlab.com/signald/signald-go/cmd/signaldctl/config" @@ -68,8 +70,30 @@ var ( } switch common.OutputFormat { - case common.OutputFormatJSON, common.OutputFormatDefault: + case common.OutputFormatJSON: return json.NewEncoder(os.Stdout).Encode(info) + case common.OutputFormatYAML: + return yaml.NewEncoder(os.Stdout).Encode(info) + case common.OutputFormatDefault, common.OutputFormatTable: + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + + joinApproval := "unknown" + switch info.AddFromInviteLink { + case 3: + joinApproval = "yes" + case 1: + joinApproval = "no" + } + + t.AppendRows([]table.Row{ + table.Row{"Title", info.Title}, + table.Row{"Group ID", info.GroupID}, + table.Row{"Member Count", info.MemberCount}, + table.Row{"Membership Approval", joinApproval}, + }) + common.StylizeTable(t) + t.Render() } return nil From 0ff6400ce7a8b4c9a282611ce5233e5a0db98b5f Mon Sep 17 00:00:00 2001 From: finn Date: Mon, 28 Mar 2022 09:42:37 -0700 Subject: [PATCH 51/73] signaldctl: create config directory if missing --- cmd/signaldctl/config/config.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/signaldctl/config/config.go b/cmd/signaldctl/config/config.go index 319f0a1..41de719 100644 --- a/cmd/signaldctl/config/config.go +++ b/cmd/signaldctl/config/config.go @@ -18,6 +18,7 @@ package config import ( "log" "os" + "path" "gopkg.in/yaml.v2" ) @@ -54,6 +55,9 @@ func Load() error { } func Save() error { + if err := os.MkdirAll(path.Dir(Path), 0660); err != nil { + return err + } f, err := os.Create(Path) if err != nil { return err From e8131dc92864034910703f1125f4011a5f3e6512 Mon Sep 17 00:00:00 2001 From: finn Date: Mon, 28 Mar 2022 10:06:22 -0700 Subject: [PATCH 52/73] fix mode on created directories --- cmd/signaldctl/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/signaldctl/config/config.go b/cmd/signaldctl/config/config.go index 41de719..ea5c391 100644 --- a/cmd/signaldctl/config/config.go +++ b/cmd/signaldctl/config/config.go @@ -55,7 +55,7 @@ func Load() error { } func Save() error { - if err := os.MkdirAll(path.Dir(Path), 0660); err != nil { + if err := os.MkdirAll(path.Dir(Path), 0770); err != nil { return err } f, err := os.Create(Path) From 4e3feffaad86e72d891a8defa60addda738bb044 Mon Sep 17 00:00:00 2001 From: finn Date: Sun, 10 Apr 2022 15:38:12 -0700 Subject: [PATCH 53/73] force linter job to golang 1.17 as golangci-lint seems to break on 1.18 --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9c3dc5d..9802ce6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: - publish lint: - image: golang:latest + image: golang:1.17 stage: build before_script: - apt-get update From 204657b38bf5396132b39bc36f9f22c33138fd17 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 10 Apr 2022 01:46:46 +0200 Subject: [PATCH 54/73] avoid races when sending requests --- signald/client-protocol/v1/requests.go | 235 ++++++++++--------------- tools/generator/requests.go.tmpl | 5 +- 2 files changed, 96 insertions(+), 144 deletions(-) diff --git a/signald/client-protocol/v1/requests.go b/signald/client-protocol/v1/requests.go index a59691b..03388b9 100644 --- a/signald/client-protocol/v1/requests.go +++ b/signald/client-protocol/v1/requests.go @@ -16,15 +16,14 @@ func (r *AcceptInvitationRequest) Submit(conn *signald.Signald) (response JsonGr if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -49,15 +48,14 @@ func (r *AddLinkedDeviceRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -75,15 +73,14 @@ func (r *AddServerRequest) Submit(conn *signald.Signald) (response string, err e if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -108,15 +105,14 @@ func (r *ApproveMembershipRequest) Submit(conn *signald.Signald) (response JsonG if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -140,15 +136,14 @@ func (r *CreateGroupRequest) Submit(conn *signald.Signald) (response JsonGroupV2 if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -173,15 +168,14 @@ func (r *DeleteAccountRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -198,15 +192,14 @@ func (r *RemoveServerRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -224,15 +217,14 @@ func (r *FinishLinkRequest) Submit(conn *signald.Signald) (response Account, err if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -257,15 +249,14 @@ func (r *GenerateLinkingURIRequest) Submit(conn *signald.Signald) (response Link if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -290,15 +281,14 @@ func (r *GetAllIdentities) Submit(conn *signald.Signald) (response AllIdentityKe if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -323,15 +313,14 @@ func (r *GetGroupRequest) Submit(conn *signald.Signald) (response JsonGroupV2Inf if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -356,15 +345,14 @@ func (r *GetIdentitiesRequest) Submit(conn *signald.Signald) (response IdentityK if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -389,15 +377,14 @@ func (r *GetLinkedDevicesRequest) Submit(conn *signald.Signald) (response Linked if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -422,15 +409,14 @@ func (r *GetProfileRequest) Submit(conn *signald.Signald) (response Profile, err if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -455,15 +441,14 @@ func (r *RemoteConfigRequest) Submit(conn *signald.Signald) (response RemoteConf if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -487,15 +472,14 @@ func (r *GetServersRequest) Submit(conn *signald.Signald) (response ServerList, if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -520,15 +504,14 @@ func (r *GroupLinkInfoRequest) Submit(conn *signald.Signald) (response JsonGroup if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -553,15 +536,14 @@ func (r *IsIdentifierRegisteredRequest) Submit(conn *signald.Signald) (response if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -586,15 +568,14 @@ func (r *JoinGroupRequest) Submit(conn *signald.Signald) (response JsonGroupJoin if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -618,15 +599,14 @@ func (r *LeaveGroupRequest) Submit(conn *signald.Signald) (response GroupInfo, e if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -651,15 +631,14 @@ func (r *ListAccountsRequest) Submit(conn *signald.Signald) (response AccountLis if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -683,15 +662,14 @@ func (r *ListContactsRequest) Submit(conn *signald.Signald) (response ProfileLis if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -715,15 +693,14 @@ func (r *ListGroupsRequest) Submit(conn *signald.Signald) (response GroupList, e if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -747,15 +724,14 @@ func (r *MarkReadRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -773,15 +749,14 @@ func (r *ReactRequest) Submit(conn *signald.Signald) (response SendResponse, err if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -806,15 +781,14 @@ func (r *RefuseMembershipRequest) Submit(conn *signald.Signald) (response JsonGr if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -839,15 +813,14 @@ func (r *RegisterRequest) Submit(conn *signald.Signald) (response Account, err e if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -872,15 +845,14 @@ func (r *RemoteDeleteRequest) Submit(conn *signald.Signald) (response SendRespon if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -905,15 +877,14 @@ func (r *RemoveLinkedDeviceRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -931,15 +902,14 @@ func (r *RequestSyncRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -957,15 +927,14 @@ func (r *ResetSessionRequest) Submit(conn *signald.Signald) (response SendRespon if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -990,15 +959,14 @@ func (r *ResolveAddressRequest) Submit(conn *signald.Signald) (response JsonAddr if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1022,15 +990,14 @@ func (r *SendRequest) Submit(conn *signald.Signald) (response SendResponse, err if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1055,15 +1022,14 @@ func (r *SendPaymentRequest) Submit(conn *signald.Signald) (response SendRespons if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1088,15 +1054,14 @@ func (r *SetDeviceNameRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1114,15 +1079,14 @@ func (r *SetExpirationRequest) Submit(conn *signald.Signald) (response SendRespo if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1146,15 +1110,14 @@ func (r *SetProfile) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1171,15 +1134,14 @@ func (r *SubmitChallengeRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1197,15 +1159,14 @@ func (r *SubscribeRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1223,15 +1184,14 @@ func (r *TrustRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1249,15 +1209,14 @@ func (r *TypingRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1275,15 +1234,14 @@ func (r *UnsubscribeRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1301,15 +1259,14 @@ func (r *UpdateContactRequest) Submit(conn *signald.Signald) (response Profile, if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1334,15 +1291,14 @@ func (r *UpdateGroupRequest) Submit(conn *signald.Signald) (response GroupInfo, if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1367,15 +1323,14 @@ func (r *VerifyRequest) Submit(conn *signald.Signald) (response Account, err err if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1399,15 +1354,14 @@ func (r *VersionRequest) Submit(conn *signald.Signald) (response JsonVersionMess if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) @@ -1432,15 +1386,14 @@ func (r *WaitForScanRequest) Submit(conn *signald.Signald) (err error) { if r.ID == "" { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <-responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) diff --git a/tools/generator/requests.go.tmpl b/tools/generator/requests.go.tmpl index fe08ac1..6be8d72 100644 --- a/tools/generator/requests.go.tmpl +++ b/tools/generator/requests.go.tmpl @@ -22,15 +22,14 @@ func {{$action.FnName}}(conn *signald.Signald) ({{if ne $action.Response ""}}res if(r.ID == "") { r.ID = signald.GenerateID() } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) err = conn.RawRequest(r) if err != nil { log.Println("signald-go: error submitting request to signald") return } - responseChannel := conn.GetResponseListener(r.ID) - defer conn.CloseResponseListener(r.ID) - rawResponse := <- responseChannel if rawResponse.Error != nil { err = mkerr(rawResponse) From e50f39228854663437d2f7484fcf941c259aef53 Mon Sep 17 00:00:00 2001 From: finn Date: Mon, 25 Apr 2022 17:05:37 -0700 Subject: [PATCH 55/73] add signaldctl account request-sync --- .../cmd/account/requestsync/main.go | 36 +++++++++++++++++++ cmd/signaldctl/cmd/account/root.go | 6 ++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 cmd/signaldctl/cmd/account/requestsync/main.go diff --git a/cmd/signaldctl/cmd/account/requestsync/main.go b/cmd/signaldctl/cmd/account/requestsync/main.go new file mode 100644 index 0000000..480a595 --- /dev/null +++ b/cmd/signaldctl/cmd/account/requestsync/main.go @@ -0,0 +1,36 @@ +package requestsync + +import ( + "log" + + "github.com/spf13/cobra" + "gitlab.com/signald/signald-go/cmd/signaldctl/common" + "gitlab.com/signald/signald-go/cmd/signaldctl/config" + v1 "gitlab.com/signald/signald-go/signald/client-protocol/v1" +) + +var ( + account string + RequestSyncCmd = &cobra.Command{ + Use: "request-sync", + Short: "Ask other devices on the account to send sync data. Must subscribe for result", + PreRun: func(cmd *cobra.Command, args []string) { + if account == "" { + account = config.Config.DefaultAccount + } + }, + Run: func(_ *cobra.Command, _ []string) { + go common.Signald.Listen(nil) + req := &v1.RequestSyncRequest{Account: account} + err := req.Submit(common.Signald) + if err != nil { + panic(err) + } + log.Println("sync requested. Must be subscribed to receive response") + }, + } +) + +func init() { + RequestSyncCmd.Flags().StringVarP(&account, "account", "a", "", "the signald account to use") +} diff --git a/cmd/signaldctl/cmd/account/root.go b/cmd/signaldctl/cmd/account/root.go index 2888868..aca722e 100644 --- a/cmd/signaldctl/cmd/account/root.go +++ b/cmd/signaldctl/cmd/account/root.go @@ -23,6 +23,7 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/list" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/register" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/remoteconfig" + "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/requestsync" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/setprofile" "gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/verify" ) @@ -37,7 +38,8 @@ func init() { AccountCmd.AddCommand(link.LinkAccountCmd) AccountCmd.AddCommand(list.ListAccountCmd) AccountCmd.AddCommand(register.RegisterAccountCmd) - AccountCmd.AddCommand(verify.VerifyAccountCmd) - AccountCmd.AddCommand(setprofile.SetProfileCmd) AccountCmd.AddCommand(remoteconfig.RemoteConfigCmd) + AccountCmd.AddCommand(requestsync.RequestSyncCmd) + AccountCmd.AddCommand(setprofile.SetProfileCmd) + AccountCmd.AddCommand(verify.VerifyAccountCmd) } From 8458f63316a68be3277f4c1ddd6a503c49b133b6 Mon Sep 17 00:00:00 2001 From: finn Date: Mon, 25 Apr 2022 17:21:52 -0700 Subject: [PATCH 56/73] update db-move to support migration version 14 --- cmd/signaldctl/cmd/db/migrate.go | 23 ++++++++------ cmd/signaldctl/cmd/db/postgres.go | 53 ++++++++++++++++--------------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 8aa5c43..ecc8cc6 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -17,6 +17,8 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/common" ) +const expectedMigrationVersion = "14" + var ( sqlitePath string postgresURL string @@ -25,7 +27,7 @@ var ( Short: "move a signald database from sqlite to postgres", Long: `move a signald sqlite database into a postgres database. If sqlite-path is not specified, the default (~/.config/signald/signald.db) will be used. - + Please note that signald must NOT be running while this command runs. After the data is moved, the sqlite file will be deleted`, @@ -166,7 +168,7 @@ var ( func verifyMigration(source *sql.DB) error { // Lower bound of the database state. - rows, err := source.Query("SELECT * FROM flyway_schema_history WHERE version = 12") + rows, err := source.Query("SELECT version FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 1") if err != nil { return err } @@ -176,16 +178,16 @@ func verifyMigration(source *sql.DB) error { return errors.New("source database is not up to date! Please update signald and start it to move the sqlite database to an acceptable format") } - // Upper bound of the database state - rows, err = source.Query("SELECT * FROM flyway_schema_history WHERE version = 13") + var version string + err = rows.Scan(&version) if err != nil { return err } - defer rows.Close() - if rows.Next() { - return errors.New("source database is too new! Please update signaldctl to the latest version") + if version != expectedMigrationVersion { + return fmt.Errorf("source database must be on migration %s (found %s instead)", expectedMigrationVersion, version) } + return nil } @@ -235,7 +237,7 @@ func moveAccounts(source *sql.DB, dest *sql.DB) error { } func moveRecipients(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT rowid, account_uuid, uuid, e164 FROM recipients") + rows, err := source.Query("SELECT rowid, account_uuid, uuid, e164, registered FROM recipients") if err != nil { return err } @@ -247,8 +249,9 @@ func moveRecipients(source *sql.DB, dest *sql.DB) error { accountUUID uuid.UUID recipientUUID uuid.NullUUID e164 sql.NullString + registered bool ) - err = rows.Scan(&rowID, &accountUUID, &recipientUUID, &e164) + err = rows.Scan(&rowID, &accountUUID, &recipientUUID, &e164, ®istered) if err != nil { return err } @@ -259,7 +262,7 @@ func moveRecipients(source *sql.DB, dest *sql.DB) error { e164.String = "" } - _, err = dest.Exec("INSERT INTO signald_recipients (rowid, account_uuid, uuid, e164) VALUES ($1, $2, $3, $4)", rowID, accountUUID, recipientUUID, e164) + _, err = dest.Exec("INSERT INTO signald_recipients (rowid, account_uuid, uuid, e164, registered) VALUES ($1, $2, $3, $4, $5)", rowID, accountUUID, recipientUUID, e164, registered) if err != nil { return err } diff --git a/cmd/signaldctl/cmd/db/postgres.go b/cmd/signaldctl/cmd/db/postgres.go index 47f7388..50952d1 100644 --- a/cmd/signaldctl/cmd/db/postgres.go +++ b/cmd/signaldctl/cmd/db/postgres.go @@ -17,7 +17,7 @@ var ( server_delivered_timestamp BIGINT, server_uuid UUID ); - + CREATE TABLE signald_servers ( server_uuid UUID PRIMARY KEY, service_url TEXT NOT NULL, @@ -35,7 +35,7 @@ var ( cds_mrenclave VARCHAR(64), ias_ca BYTEA ); - + INSERT INTO signald_servers VALUES ( '6e2eb5a8-5706-45d0-8377-127a816411a4', -- server_uuid @@ -89,79 +89,80 @@ var ( -- ias_ca E'\\x00000002000000141e139877c131235200356d48d741b9e8538d4a290000078101000369617300000164adeb7976000000000005582e3530390000054f3082054b308203b3a003020102020900d107765d32a3b094300d06092a864886f70d01010b0500307e310b3009060355040613025553310b300906035504080c0243413114301206035504070c0b53616e746120436c617261311a3018060355040a0c11496e74656c20436f72706f726174696f6e3130302e06035504030c27496e74656c20534758204174746573746174696f6e205265706f7274205369676e696e672043413020170d3136313131343135333733315a180f32303439313233313233353935395a307e310b3009060355040613025553310b300906035504080c0243413114301206035504070c0b53616e746120436c617261311a3018060355040a0c11496e74656c20436f72706f726174696f6e3130302e06035504030c27496e74656c20534758204174746573746174696f6e205265706f7274205369676e696e67204341308201a2300d06092a864886f70d01010105000382018f003082018a02820181009f3c647eb5773cbb512d2732c0d7415ebb55a0fa9ede2e649199e6821db910d53177370977466a6a5e4786ccd2ddebd4149d6a2f6325529dd10cc98737b0779c1a07e29c47a1ae004948476c489f45a5a15d7ac8ecc6acc645adb43d87679df59c093bc5a2e9696c5478541b979e754b573914be55d32ff4c09ddf27219934cd990527b3f92ed78fbf29246abecb71240ef39c2d7107b447545a7ffb10eb060a68a98580219e36910952683892d6a5e2a80803193e407531404e36b315623799aa825074409754a2dfe8f5afd5fe631e1fc2af3808906f28a790d9dd9fe060939b125790c5805d037df56a99531b96de69de33ed226cc1207d1042b5c9ab7f404fc711c0fe4769fb9578b1dc0ec469ea1a25e0ff9914886ef2699b235bb4847dd6ff40b606e6170793c2fb98b314587f9cfd257362dfeab10b3bd2d97673a1a4bd44c453aaf47fc1f2d3d0f384f74a06f89c089f0da6cdb7fceee8c9821a8e54f25c0416d18c46839a5f8012fbdd3dc74d256279adc2c0d55aff6f0622425d1b0203010001a381c93081c630600603551d1f045930573055a053a051864f687474703a2f2f7472757374656473657276696365732e696e74656c2e636f6d2f636f6e74656e742f43524c2f5347582f4174746573746174696f6e5265706f72745369676e696e6743412e63726c301d0603551d0e0416041478437b76a67ebcd0af7e4237eb357c3b8701513c301f0603551d2304183016801478437b76a67ebcd0af7e4237eb357c3b8701513c300e0603551d0f0101ff04040302010630120603551d130101ff040830060101ff020100300d06092a864886f70d01010b05000382018100785f2d60c5c80af42a797610213915da82c9b29e89e0902a25a6c75b16091c68ab204aae711889492c7e1e320911455a8fc13442312e77a63994d99795c8ea4576823cea8ad1e191cfa862fab8a932d3d9b0535a0702d0555f74e520e30330f33480e7adc9d7c81e20703142bf00c528a80b463381fd602a82c7035281aae59562ccb5334ea8903e650b010681f5ce8eb62eac9c414988243aec92f25bf13cdff7ebcc298ee51bba5a3538b66b26cbc45a51de003cad306531ad7cf5d4ef0f8805d1b9133d24135ab3c4641a2f8808349d7333295e0e76ee4bc5227232628efa80d79d92ab4e3d1120f3fb5ad119cd8d544aa1d4a6865e6b57beac5771307e2e3cb9070da47b4bfc8869e01413ea093541de8a792811b74636c5e91452cf0cee59f2fb404acd0bc584cb9c835404734c0e7ec6605cdfcf2ff439b6d4719f702f0e0c3fa04fdb12a6cb2ad1ab1c9af1f8f4c3a08edd72a32b0bb5d0ad256ffd159a683b2a5a1f1d11fa62532f03d754caef0da5735a1e5a884c7e89d91218c9d7008515e5f5992ccc471f3b1bc1aaec24a2997e6ad3' ); - + CREATE TABLE signald_accounts ( uuid UUID NOT NULL, e164 TEXT NOT NULL, filename TEXT NOT NULL, server UUID NOT NULL REFERENCES signald_servers(server_uuid) ON DELETE CASCADE, - + PRIMARY KEY (uuid, e164, filename, server), UNIQUE (e164), UNIQUE (filename), UNIQUE (uuid) ); - + CREATE TABLE signald_recipients ( rowid SERIAL PRIMARY KEY, account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, uuid UUID, e164 TEXT, - + registered BOOLEAN DEFAULT true, + UNIQUE (account_uuid, e164, uuid) ); - + CREATE TABLE signald_prekeys ( account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, id INTEGER NOT NULL, record BYTEA NOT NULL, - + PRIMARY KEY (account_uuid, id) ); - + CREATE TABLE signald_sessions ( account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, recipient INTEGER NOT NULL REFERENCES signald_recipients(rowid) ON DELETE CASCADE, device_id INTEGER, record BYTEA NOT NULL, - + PRIMARY KEY (account_uuid, recipient, device_id) ); - + CREATE TABLE signald_signed_prekeys ( account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, id INTEGER NOT NULL, record BYTEA NOT NULL, - + PRIMARY KEY (account_uuid, id) ); - + CREATE TABLE signald_identity_keys ( account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, recipient INTEGER NOT NULL, identity_key BYTEA NOT NULL, trust_level TEXT NOT NULL, added TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, - + PRIMARY KEY (account_uuid, recipient, identity_key) ); - + CREATE TABLE signald_account_data ( account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, key TEXT NOT NULL, value BYTEA NOT NULL, - + PRIMARY KEY (account_uuid, key) ); - + CREATE TABLE signald_pending_account_data ( username TEXT, key TEXT NOT NULL, value BYTEA NOT NULL, - + PRIMARY KEY (username, key) ); - + CREATE TABLE signald_sender_keys ( account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, address TEXT NOT NULL, @@ -169,19 +170,19 @@ var ( distribution_id UUID NOT NULL, record BYTEA NOT NULL, created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, - + PRIMARY KEY (account_uuid, address, device, distribution_id) ); - + CREATE TABLE signald_sender_key_shared ( account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, distribution_id UUID NOT NULL, address TEXT NOT NULL, device INTEGER NOT NULL, - + PRIMARY KEY (account_uuid, address, device) ); - + CREATE TABLE signald_groups ( rowid SERIAL PRIMARY KEY, account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, @@ -191,15 +192,15 @@ var ( last_avatar_fetch INTEGER, distribution_id UUID, group_info BYTEA, - + UNIQUE (account_uuid, group_id) ); - + CREATE TABLE signald_group_credentials ( account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, date BIGINT NOT NULL, credential BYTEA NOT NULL, - + PRIMARY KEY (account_uuid, date) ); From 890417af5ddb490131a8f67626af46b17bdc0375 Mon Sep 17 00:00:00 2001 From: schubisu Date: Thu, 12 May 2022 14:42:09 +0200 Subject: [PATCH 57/73] remove incorrect alias --- cmd/signaldctl/cmd/device/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/signaldctl/cmd/device/root.go b/cmd/signaldctl/cmd/device/root.go index f3169bd..408e559 100644 --- a/cmd/signaldctl/cmd/device/root.go +++ b/cmd/signaldctl/cmd/device/root.go @@ -25,7 +25,7 @@ import ( var DeviceCmd = &cobra.Command{ Use: "device", - Aliases: []string{"msg", "devices"}, + Aliases: []string{"devices"}, } func init() { From b343c02f77c562c01d3141dd9fc4b31ee09bc2fd Mon Sep 17 00:00:00 2001 From: finn Date: Mon, 16 May 2022 18:55:46 -0700 Subject: [PATCH 58/73] captcha helper support for send feedback appriciated, I can't get captchas reliably --- .../cmd/message/send/send-message.go | 109 +++++++++++++++++- 1 file changed, 103 insertions(+), 6 deletions(-) diff --git a/cmd/signaldctl/cmd/message/send/send-message.go b/cmd/signaldctl/cmd/message/send/send-message.go index 81b4f44..4e99598 100644 --- a/cmd/signaldctl/cmd/message/send/send-message.go +++ b/cmd/signaldctl/cmd/message/send/send-message.go @@ -18,9 +18,11 @@ package send import ( "encoding/json" "fmt" + "io" "io/ioutil" "log" "os" + "os/exec" "path/filepath" "strings" @@ -30,15 +32,20 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/common" "gitlab.com/signald/signald-go/cmd/signaldctl/config" - "gitlab.com/signald/signald-go/signald/client-protocol/v1" + v1 "gitlab.com/signald/signald-go/signald/client-protocol/v1" +) + +const ( + CAPTCHA_HELPER = "signal-captcha-helper" ) var ( - account string - toAddress *v1.JsonAddress - toGroup string - attachments []string - message string + account string + toAddress *v1.JsonAddress + toGroup string + attachments []string + message string + captchaHelper bool SendMessageCmd = &cobra.Command{ Use: "send {group id | phone number} [message]", @@ -97,6 +104,48 @@ var ( if err != nil { log.Fatal("error sending request to signald: ", err) } + + resends := []*v1.JsonAddress{} + + for _, result := range resp.Results { + if result.ProofRequiredFailure != nil { + if captchaHelper { + err = runCaptchaHelper(result.ProofRequiredFailure.Token) + if err != nil { + log.Println("error running captcha helper: ", err) + } + resends = append(resends, result.Address) + } + } + } + + if len(resends) > 0 { + resendReq := v1.SendRequest{ + Username: req.Username, + MessageBody: req.MessageBody, + Attachments: req.Attachments, + RecipientGroupID: req.RecipientGroupID, + Members: resends, + Timestamp: resp.Timestamp, + } + resendResponse, err := resendReq.Submit(common.Signald) + if err != nil { + log.Println("error resending messages: ", err) + } else { + for i, originalResult := range resp.Results { + if originalResult.ProofRequiredFailure == nil { + continue + } + + for _, result := range resendResponse.Results { + if result.Address.UUID == originalResult.Address.UUID { + resp.Results[i] = result + } + } + } + } + } + switch common.OutputFormat { case common.OutputFormatJSON: err := json.NewEncoder(os.Stdout).Encode(resp) @@ -148,7 +197,55 @@ var ( } ) +func runCaptchaHelper(challenge string) error { + if !captchaHelper { + return nil + } + + _, err := exec.LookPath(CAPTCHA_HELPER) + if err != nil { + return err + } + + cmd := exec.Command(CAPTCHA_HELPER, "--challenge") + + stdout, err := cmd.StdoutPipe() + if err != nil { + return err + } + + err = cmd.Start() + if err != nil { + return err + } + + captchaToken := new(strings.Builder) + _, err = io.Copy(captchaToken, stdout) + if err != nil { + return err + } + + err = cmd.Wait() + if err != nil { + return err + } + + req := v1.SubmitChallengeRequest{ + Account: account, + CaptchaToken: captchaToken.String(), + Challenge: challenge, + } + + err = req.Submit(common.Signald) + if err != nil { + return err + } + + return nil +} + func init() { SendMessageCmd.Flags().StringVarP(&account, "account", "a", "", "local account to use") + SendMessageCmd.Flags().BoolVarP(&captchaHelper, "captcha-helper", "c", false, "Invoke signal-captcha-helper and process the response when a push challenge response appears. After completing the challenge, the message will be redelivered to the failed recipient") SendMessageCmd.Flags().StringSliceVarP(&attachments, "attachment", "A", []string{}, "attach a file to your outbound message. may be specified multiple times.") } From 7ace3647c7b28d329197f8b3f1ee97f0c6f75658 Mon Sep 17 00:00:00 2001 From: finn Date: Tue, 17 May 2022 16:45:21 -0700 Subject: [PATCH 59/73] add new db migration code, add sqlite to postgres migration test in CI hopefully this will keep migrations in sync --- .gitlab-ci.yml | 27 +++- cmd/signaldctl/cmd/db/migrate.go | 230 ++++++++++++++++++++++++++---- cmd/signaldctl/cmd/db/postgres.go | 89 +++++++++--- 3 files changed, 298 insertions(+), 48 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9802ce6..b9cba03 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,10 +1,11 @@ stages: - build + - test - publish lint: image: golang:1.17 - stage: build + stage: test before_script: - apt-get update - apt-get install -y wget golang-go @@ -17,6 +18,30 @@ lint: - diff --color=always go.sum "${CI_PROJECT_DIR}/go.sum" rules: - when: on_success + needs: [] + +test sqlite to postgres: + image: + name: registry.gitlab.com/signald/signald:unstable + entrypoint: [""] + stage: test + needs: + - "build:x86" + before_script: + - apt-get update && apt-get install -y postgresql-client + script: + - cd / + - signald --migrate-data + - echo 'CREATE DATABASE signald' | psql -h postgres -U postgres -a + - "${CI_PROJECT_DIR}/signaldctl db-move postgresql://postgres@postgres/signald?sslmode=disable" + - SIGNALD_DATABASE=postgresql://postgres@postgres/signald?sslmode=disable signald --migrate-data + variables: + SIGNALD_VERBOSE_LOGGING: "true" + services: + - name: postgres:latest + alias: postgres + variables: + POSTGRES_HOST_AUTH_METHOD: trust .build: stage: build diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index ecc8cc6..5bac893 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -17,9 +17,23 @@ import ( "gitlab.com/signald/signald-go/cmd/signaldctl/common" ) -const expectedMigrationVersion = "14" +type Migration struct { + InstalledRank int + Version string + Description string + Script string + Checksum int +} var ( + migrations = []Migration{ + {InstalledRank: 1, Version: "1", Description: "create tables", Script: "V1__create_tables.sql", Checksum: -1247750968}, + {InstalledRank: 2, Version: "12", Description: "create contacts table", Script: "V12__create_contacts_table.sql", Checksum: -852729911}, + {InstalledRank: 3, Version: "13", Description: "recipient registration status", Script: "V13__recipient_registration_status.sql", Checksum: 405376321}, + {InstalledRank: 4, Version: "14", Description: "multiple identity keys per account", Script: "V14__multiple_identity_keys_per_account.sql", Checksum: -1635788950}, + {InstalledRank: 5, Version: "15", Description: "profiles tables", Script: "V15__profiles_tables.sql", Checksum: 809686180}, + } + sqlitePath string postgresURL string MoveCmd = &cobra.Command{ @@ -79,83 +93,107 @@ var ( log.Println("created schema") if err := moveAccounts(source, dest); err != nil { - log.Println("error migrating accounts table") + log.Println("error moving accounts table") return err } log.Println("moved accounts table") if err := moveRecipients(source, dest); err != nil { - log.Println("error migrating recipients table") + log.Println("error moving recipients table") return err } log.Println("moved recipients table") if err := movePrekeys(source, dest); err != nil { - log.Println("error migrating prekeys table") + log.Println("error moving prekeys table") return err } log.Println("moved prekeys table") if err := moveSessions(source, dest); err != nil { - log.Println("error migrating sessions table") + log.Println("error moving sessions table") return err } log.Println("moved sessions table") if err := moveSignedPrekeys(source, dest); err != nil { - log.Println("error migrating signed prekeys table") + log.Println("error moving signed prekeys table") return err } log.Println("moved signed prekeys table") if err := moveIdentityKeys(source, dest); err != nil { - log.Println("error migrating identity keys table") + log.Println("error moving identity keys table") return err } log.Println("moved identity keys table") if err := moveAccountData(source, dest); err != nil { - log.Println("error migrating account data") + log.Println("error moving account data") return err } log.Println("moved account data table") if err := movePendingAccountData(source, dest); err != nil { - log.Println("error migrating pending account data tabe") + log.Println("error moving pending account data tabe") return err } log.Println("moved pending account data table") if err := moveSenderKeys(source, dest); err != nil { - log.Println("error migrating sender keys table") + log.Println("error moving sender keys table") return err } log.Println("moved sender keys table") if err := moveSenderKeyShared(source, dest); err != nil { - log.Println("error migrating sender key shared table") + log.Println("error moving sender key shared table") return err } log.Println("moved sender key shared table") if err := moveGroups(source, dest); err != nil { - log.Println("error migrating groups table") + log.Println("error moving groups table") return err } log.Println("moved groups table") if err := moveGroupCredentials(source, dest); err != nil { - log.Println("error migrating group credentials table") + log.Println("error moving group credentials table") return err } log.Println("moved group credentials table") if err := moveContacts(source, dest); err != nil { - log.Println("error migrating group credentials table") + log.Println("error moving group credentials table") return err } log.Println("moved contacts table") + if err := moveProfileKeys(source, dest); err != nil { + log.Println("error moving profile keys table") + return err + } + log.Println("moved profile keys table") + + if err := moveProfiles(source, dest); err != nil { + log.Println("error moving profiles tables") + return err + } + log.Println("moved profiles tables") + + if err := moveProfileCapabilities(source, dest); err != nil { + log.Println("error moving profile capabilities tables") + return err + } + log.Println("moved profile capabilities tables") + + if err := moveProfileBadges(source, dest); err != nil { + log.Println("error moving profile badges tables") + return err + } + log.Println("moved profile badges tables") + if err := os.Remove(sqlitePath); err != nil { log.Println("error deleting sqlite file") return err @@ -175,7 +213,7 @@ func verifyMigration(source *sql.DB) error { defer rows.Close() if !rows.Next() { - return errors.New("source database is not up to date! Please update signald and start it to move the sqlite database to an acceptable format") + return errors.New("source database is not up to date! Please update signald and start it to move all data into sqlite before moving data to postgres") } var version string @@ -184,8 +222,9 @@ func verifyMigration(source *sql.DB) error { return err } + expectedMigrationVersion := migrations[len(migrations)-1].Version if version != expectedMigrationVersion { - return fmt.Errorf("source database must be on migration %s (found %s instead)", expectedMigrationVersion, version) + return fmt.Errorf("source database must be on migration %s (found %s instead). Please update signald, or file an issue if the migrations are out of date", expectedMigrationVersion, version) } return nil @@ -197,21 +236,24 @@ func createSchema(dest *sql.DB) error { return err } - _, err = dest.Exec(` + for _, migration := range migrations { + _, err = dest.Exec(` INSERT INTO flyway_schema_history (installed_rank, version, description, type, script, checksum, installed_by, execution_time, success) - VALUES ($1, $2, $3, $4, $5, $6, current_user, $7, $8), - ($9, $10, $11, $12, $13, $14, current_user, $15, $16) + VALUES ($1, $2, $3, 'SQL', $4, $5, current_user, 0, true) `, - // Row 1 - 1, 1, "create tables", "SQL", "V1__create_tables.sql", -1247750968, 0, true, - // Row 2 - 2, 12, "create contacts table", "SQL", "V12__create_contacts_table.sql", -852729911, 0, true) - return err + migration.InstalledRank, migration.Version, migration.Description, migration.Script, migration.Checksum, + ) + if err != nil { + return err + } + } + + return nil } func moveAccounts(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT uuid, e164, filename, server FROM accounts") + rows, err := source.Query("SELECT uuid, e164, server FROM accounts") if err != nil { return err } @@ -221,14 +263,13 @@ func moveAccounts(source *sql.DB, dest *sql.DB) error { var ( accountUUID uuid.UUID e164 string - filename string server uuid.UUID ) - err = rows.Scan(&accountUUID, &e164, &filename, &server) + err = rows.Scan(&accountUUID, &e164, &server) if err != nil { return err } - _, err = dest.Exec("INSERT INTO signald_accounts (uuid, e164, filename, server) VALUES ($1, $2, $3, $4)", accountUUID, e164, filename, server) + _, err = dest.Exec("INSERT INTO signald_accounts (uuid, e164, server) VALUES ($1, $2, $3, $4)", accountUUID, e164, server) if err != nil { return err } @@ -603,3 +644,136 @@ func moveContacts(source *sql.DB, dest *sql.DB) error { } return nil } + +func moveProfileKeys(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, recipient, profile_key, profile_key_credential, request_pending, unidentified_access_mode FROM profile_keys") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + recipient int64 + profile_key []byte + profile_key_credential []byte + request_pending bool + unidentified_access_mode int + ) + err = rows.Scan(&accountUUID, &recipient, &profile_key, &profile_key_credential, &request_pending, &unidentified_access_mode) + if err != nil { + return err + } + _, err = dest.Exec(` + INSERT INTO signald_profile_keys + (account_uuid, recipient, profile_key, profile_key_credential, request_pending, unidentified_access_mode) + VALUES ($1, $2, $3, $4, $5, $6, $7) + `, accountUUID, recipient, profile_key, profile_key_credential, request_pending, unidentified_access_mode) + if err != nil { + return err + } + } + return nil +} + +func moveProfiles(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, recipient, last_update, given_name, family_name, about, emoji, payment_address, badges FROM profiles") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + recipient int64 + last_update int64 + given_name string + family_name string + about string + emoji string + payment_address []byte + badges string + ) + err = rows.Scan(&accountUUID, &recipient, &last_update, &given_name, &family_name, &about, &emoji, &payment_address, &badges) + if err != nil { + return err + } + _, err = dest.Exec(` + INSERT INTO signald_profiles + (account_uuid, recipient, last_update, given_name, family_name, about, emoji, payment_address, badges) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + `, accountUUID, recipient, last_update, given_name, family_name, about, emoji, payment_address, badges) + if err != nil { + return err + } + } + return nil +} + +func moveProfileCapabilities(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, recipient, storage, gv1_migration, sender_key, announcement_group, change_number, stories FROM profile_capabilities") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + recipient int64 + storage bool + gv1_migration bool + sender_key bool + announcement_group bool + change_number bool + stories bool + ) + err = rows.Scan(&accountUUID, &recipient, &storage, &gv1_migration, &sender_key, &announcement_group, &change_number, &stories) + if err != nil { + return err + } + _, err = dest.Exec(` + INSERT INTO signald_profile_capabilities + (account_uuid, recipient, storage, gv1_migration, sender_key, announcement_group, change_number, stories) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + `, accountUUID, recipient, storage, gv1_migration, sender_key, announcement_group, change_number, stories) + if err != nil { + return err + } + } + return nil +} + +func moveProfileBadges(source *sql.DB, dest *sql.DB) error { + rows, err := source.Query("SELECT account_uuid, id, category, name, description, sprite6 FROM profile_badges") + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var ( + accountUUID uuid.UUID + id string + category string + name string + description string + sprite6 string + ) + err = rows.Scan(&accountUUID, &id, &category, &name, &description, &sprite6) + if err != nil { + return err + } + _, err = dest.Exec(` + INSERT INTO signald_profile_badges + (account_uuid, id, category, name, description, sprite6) + VALUES ($1, $2, $3, $4, $5, $6) + `, accountUUID, id, category, name, description, sprite6) + if err != nil { + return err + } + } + return nil +} diff --git a/cmd/signaldctl/cmd/db/postgres.go b/cmd/signaldctl/cmd/db/postgres.go index 50952d1..5f7c2b3 100644 --- a/cmd/signaldctl/cmd/db/postgres.go +++ b/cmd/signaldctl/cmd/db/postgres.go @@ -1,8 +1,22 @@ package db var ( - // from signald in src/main/resources/db/migration/postgresql/V1__create_tables.sql - pgScheme = `CREATE TABLE signald_message_queue ( + pgScheme = ` + CREATE TABLE public.flyway_schema_history ( + installed_rank integer NOT NULL, + version character varying(50), + description character varying(200) NOT NULL, + type character varying(20) NOT NULL, + script character varying(1000) NOT NULL, + checksum integer, + installed_by character varying(100) NOT NULL, + installed_on timestamp without time zone DEFAULT now() NOT NULL, + execution_time integer NOT NULL, + success boolean NOT NULL + ); + + -- from signald in src/main/resources/db/migration/postgresql/V1__create_tables.sql + CREATE TABLE signald_message_queue ( id SERIAL PRIMARY KEY, account UUID NOT NULL, version INTEGER NOT NULL, @@ -93,12 +107,10 @@ var ( CREATE TABLE signald_accounts ( uuid UUID NOT NULL, e164 TEXT NOT NULL, - filename TEXT NOT NULL, server UUID NOT NULL REFERENCES signald_servers(server_uuid) ON DELETE CASCADE, - PRIMARY KEY (uuid, e164, filename, server), + PRIMARY KEY (uuid, e164, server), UNIQUE (e164), - UNIQUE (filename), UNIQUE (uuid) ); @@ -107,7 +119,7 @@ var ( account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, uuid UUID, e164 TEXT, - registered BOOLEAN DEFAULT true, + registered BOOLEAN DEFAULT true, -- from signald in src/main/resources/db/migration/postgresql/V13__recipient_registration_status.sql UNIQUE (account_uuid, e164, uuid) ); @@ -204,19 +216,7 @@ var ( PRIMARY KEY (account_uuid, date) ); - CREATE TABLE public.flyway_schema_history ( - installed_rank integer NOT NULL, - version character varying(50), - description character varying(200) NOT NULL, - type character varying(20) NOT NULL, - script character varying(1000) NOT NULL, - checksum integer, - installed_by character varying(100) NOT NULL, - installed_on timestamp without time zone DEFAULT now() NOT NULL, - execution_time integer NOT NULL, - success boolean NOT NULL - ); - + -- from signald in src/main/resources/db/migration/postgresql/V12__create_contacts_table.sql CREATE TABLE signald_contacts ( account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, recipient INTEGER NOT NULL REFERENCES signald_recipients(rowid) ON DELETE CASCADE, @@ -228,5 +228,56 @@ var ( PRIMARY KEY (account_uuid, recipient) ); + + -- from signald in src/main/resources/db/migration/postgresql/V15__profiles_tables.sql + + CREATE TABLE signald_profile_keys ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + recipient INTEGER NOT NULL REFERENCES signald_recipients(rowid) ON DELETE CASCADE, + profile_key BYTEA DEFAULT NULL, + profile_key_credential BYTEA DEFAULT NULL, + request_pending boolean DEFAULT FALSE, + unidentified_access_mode int DEFAULT 0, + + PRIMARY KEY (account_uuid, recipient) + ); + + CREATE TABLE signald_profiles ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + recipient INTEGER NOT NULL REFERENCES signald_recipients(rowid) ON DELETE CASCADE, + last_update BIGINT, + given_name TEXT, + family_name TEXT, + about TEXT, + emoji TEXT, + payment_address BYTEA, + badges TEXT, + + PRIMARY KEY (account_uuid, recipient) + ); + + CREATE TABLE signald_profile_capabilities ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + recipient INTEGER NOT NULL REFERENCES signald_recipients(rowid) ON DELETE CASCADE, + storage BOOLEAN, + gv1_migration BOOLEAN, + sender_key BOOLEAN, + announcement_group BOOLEAN, + change_number BOOLEAN, + stories BOOLEAN, + + PRIMARY KEY (account_uuid, recipient) + ); + + CREATE TABLE signald_profile_badges ( + account_uuid UUID NOT NULL REFERENCES signald_accounts(uuid) ON DELETE CASCADE, + id TEXT NOT NULL, + category TEXT NOT NULL, + name TEXT NOT NULL, + description TEXT NOT NULL, + sprite6 TEXT NOT NULL, + + PRIMARY KEY (account_uuid, id) + ); ` ) From b5af9d176b18a1a1abbc2c7831402457de40d84c Mon Sep 17 00:00:00 2001 From: finn Date: Tue, 17 May 2022 18:14:09 -0700 Subject: [PATCH 60/73] bump protocol --- protocol.json | 1306 +++++++++++++++++++----- signald/client-protocol/v0/structs.go | 1 + signald/client-protocol/v1/errors.go | 73 +- signald/client-protocol/v1/requests.go | 128 +++ signald/client-protocol/v1/structs.go | 225 +++- 5 files changed, 1400 insertions(+), 333 deletions(-) diff --git a/protocol.json b/protocol.json index a0ded3f..edf0bcb 100644 --- a/protocol.json +++ b/protocol.json @@ -2,9 +2,9 @@ "doc_version": "v1", "version": { "name": "signald", - "version": "0.17.0", + "version": "0.18.3", "branch": "main", - "commit": "3d482a6060b7235a18cdb8a4dc60aba61f99da74" + "commit": "2d898baa70a20c44ae918d2ce6e1f108579ee271" }, "info": "This document describes objects that may be used when communicating with signald.", "types": { @@ -487,7 +487,7 @@ "text": { "type": "String", "doc": "the body of the message being quoted", - "example": "\"hey ? what's up?\"" + "example": "\"hey  what's up?\"" }, "attachments": { "list": true, @@ -577,7 +577,7 @@ "emoji": { "type": "String", "doc": "the emoji to react with", - "example": "\"?\"" + "example": "\"👍\"" }, "remove": { "type": "boolean", @@ -916,6 +916,9 @@ }, "Optional": { "fields": { + "empty": { + "type": "boolean" + }, "present": { "type": "boolean" } @@ -926,13 +929,125 @@ } }, "v1": { - "ListenerState": { + "ClientMessageWrapper": { "fields": { - "connected": { - "type": "boolean" + "type": { + "type": "String", + "doc": "the type of object to expect in the `data` field" + }, + "version": { + "type": "String", + "doc": "the version of the object in the `data` field" + }, + "data": { + "type": "Object", + "doc": "the incoming object. The structure will vary from message to message, see `type` and `version` fields" + }, + "error": { + "type": "Boolean", + "doc": "true if the incoming message represents an error" + }, + "account": { + "type": "String", + "doc": "the account this message is from" } }, - "doc": "prior attempt to indicate signald connectivity state. WebSocketConnectionState messages will be delivered at the same time as well as in other parts of the websocket lifecycle." + "doc": "Wraps all incoming messages sent to the client after a v1 subscribe request is issued" + }, + "ProtocolInvalidMessageError": { + "fields": { + "sender": { + "type": "String" + }, + "timestamp": { + "type": "long" + }, + "message": { + "type": "String" + }, + "sender_device": { + "type": "int" + }, + "content_hint": { + "type": "int" + }, + "group_id": { + "type": "String" + } + }, + "error": true + }, + "UntrustedIdentityError": { + "fields": { + "identifier": { + "type": "String" + }, + "message": { + "type": "String" + }, + "identity_key": { + "type": "IdentityKey", + "version": "v1" + } + }, + "error": true + }, + "ProtocolNoSessionError": { + "fields": { + "sender": { + "type": "String" + }, + "timestamp": { + "type": "long" + }, + "message": { + "type": "String" + }, + "sender_device": { + "type": "int" + }, + "content_hint": { + "type": "int" + }, + "group_id": { + "type": "String" + } + }, + "error": true + }, + "ProtocolInvalidKeyIdError": { + "fields": { + "sender": { + "type": "String" + }, + "timestamp": { + "type": "long" + }, + "message": { + "type": "String" + }, + "sender_device": { + "type": "int" + }, + "content_hint": { + "type": "int" + }, + "group_id": { + "type": "String" + } + }, + "error": true + }, + "DuplicateMessageError": { + "fields": { + "timestamp": { + "type": "long" + }, + "message": { + "type": "String" + } + }, + "error": true }, "IncomingMessage": { "fields": { @@ -991,11 +1106,23 @@ "type": "TypingMessage", "version": "v1" }, + "story_message": { + "type": "StoryMessage", + "version": "v1" + }, "server_guid": { "type": "String" } } }, + "ListenerState": { + "fields": { + "connected": { + "type": "boolean" + } + }, + "doc": "prior attempt to indicate signald connectivity state. WebSocketConnectionState messages will be delivered at the same time as well as in other parts of the websocket lifecycle." + }, "WebSocketConnectionState": { "fields": { "state": { @@ -1009,154 +1136,24 @@ }, "doc": "indicates when the websocket connection state to the signal server has changed" }, - "JsonMessageEnvelope": { + "StorageChange": { "fields": { - "username": { - "type": "String", - "example": "\"+12024561414\"" - }, - "uuid": { - "type": "String", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"" - }, - "source": { - "type": "JsonAddress", - "version": "v1" - }, - "sourceDevice": { - "type": "int" - }, - "type": { - "type": "String" - }, - "relay": { - "type": "String" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - }, - "timestampISO": { - "type": "String" - }, - "serverTimestamp": { - "type": "long" - }, - "serverDeliveredTimestamp": { - "type": "long", - "example": "161557644247580" - }, - "hasLegacyMessage": { - "type": "boolean" - }, - "hasContent": { - "type": "boolean" - }, - "isUnidentifiedSender": { - "type": "boolean" - }, - "dataMessage": { - "type": "JsonDataMessage", - "version": "v1" - }, - "syncMessage": { - "type": "JsonSyncMessage", - "version": "v1" - }, - "callMessage": { - "type": "JsonCallMessage", - "version": "v0" - }, - "receipt": { - "type": "JsonReceiptMessage", - "version": "v0" - }, - "typing": { - "type": "JsonTypingMessage", - "version": "v0" - } - } - }, - "ClientMessageWrapper": { - "fields": { - "type": { - "type": "String", - "doc": "the type of object to expect in the `data` field" - }, "version": { - "type": "String", - "doc": "the version of the object in the `data` field" - }, - "data": { - "type": "Object", - "doc": "the incoming object. The structure will vary from message to message, see `type` and `version` fields" - }, - "error": { - "type": "Boolean", - "doc": "true if the incoming message represents an error" - }, - "account": { - "type": "String", - "doc": "the account this message is from" + "type": "long", + "doc": "Seems to behave like the group version numbers and increments every time the state changes" } }, - "doc": "Wraps all incoming messages sent to the client after a v1 subscribe request is issued" - }, - "UntrustedIdentityError": { - "fields": { - "identifier": { - "type": "String" - }, - "message": { - "type": "String" - }, - "identity_key": { - "type": "IdentityKey", - "version": "v1" - } - }, - "error": true - }, - "ProtocolInvalidMessageError": { - "fields": { - "sender": { - "type": "String" - }, - "timestamp": { - "type": "long" - }, - "message": { - "type": "String" - }, - "sender_device": { - "type": "int" - }, - "content_hint": { - "type": "int" - }, - "group_id": { - "type": "String" - } - }, - "error": true - }, - "DuplicateMessageError": { - "fields": { - "timestamp": { - "type": "long" - }, - "message": { - "type": "String" - } - }, - "error": true + "doc": "Broadcast to subscribed clients when there is a state change from the storage service" }, "SendRequest": { "fields": { "username": { "type": "String", - "example": "\"+12024561414\"", - "required": true + "example": "\"+12024561414\"" + }, + "account": { + "type": "String", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"" }, "recipientAddress": { "type": "JsonAddress", @@ -1318,6 +1315,23 @@ }, "error": true }, + "AuthorizationFailedError": { + "fields": { + "message": { + "type": "String" + } + }, + "doc": "Indicates the server rejected our credentials or a failed group update. Typically means the linked device was removed by the primary device, or that the account was re-registered. For group updates, this can indicate that we lack permissions.", + "error": true + }, + "SQLError": { + "fields": { + "message": { + "type": "String" + } + }, + "error": true + }, "ReactRequest": { "fields": { "username": { @@ -1372,7 +1386,7 @@ }, "version": { "type": "String", - "example": "\"0.17.0\"" + "example": "\"0.18.3\"" }, "branch": { "type": "String", @@ -1380,7 +1394,7 @@ }, "commit": { "type": "String", - "example": "\"3d482a6060b7235a18cdb8a4dc60aba61f99da74\"" + "example": "\"2d898baa70a20c44ae918d2ce6e1f108579ee271\"" } } }, @@ -1389,7 +1403,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "groupID": { @@ -1469,6 +1483,16 @@ "removed": { "type": "boolean", "doc": "will be set to true for incoming messages to indicate the user has been removed from the group" + }, + "banned_members": { + "list": true, + "type": "BannedGroupMember", + "version": "v1" + }, + "group_change": { + "type": "GroupChange", + "version": "v1", + "doc": "Represents a peer-to-peer group change done by a user. Will not be set if the group change signature fails verification. This is usually only set inside of incoming messages." } }, "doc": "Information about a Signal group" @@ -1481,13 +1505,13 @@ }, "error": true }, - "AuthorizationFailedError": { + "GroupPatchNotAcceptedError": { "fields": { "message": { "type": "String" } }, - "doc": "indicates the server rejected our credentials. Typically means the linked device was removed by the primary device, or that the account was re-registered", + "doc": "Indicates the server rejected our group update. This can be due to errors such as trying to add a user that's already in the group.", "error": true }, "ApproveMembershipRequest": { @@ -1495,7 +1519,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "groupID": { @@ -1526,7 +1550,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "groupID": { @@ -1554,7 +1578,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true } }, @@ -1574,7 +1598,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "uri": { @@ -1657,7 +1681,7 @@ "account": { "type": "String", "doc": "The identifier of the account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "groupID": { @@ -1776,7 +1800,7 @@ "account": { "type": "String", "doc": "The signal account to use", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "partial": { @@ -1809,7 +1833,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "to": { @@ -1835,6 +1859,7 @@ "account": { "type": "String", "doc": "the signald account to use", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "async": { @@ -1912,6 +1937,7 @@ "fields": { "account": { "type": "String", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true } } @@ -1934,6 +1960,7 @@ "fields": { "account": { "type": "String", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "async": { @@ -1956,7 +1983,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "title": { @@ -1998,7 +2025,7 @@ "account": { "type": "String", "doc": "The account to use", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "groupID": { @@ -2030,6 +2057,10 @@ }, "FinishLinkRequest": { "fields": { + "overwrite": { + "type": "boolean", + "doc": "overwrite existing account data if the phone number conflicts. false by default" + }, "device_name": { "type": "String" }, @@ -2050,6 +2081,9 @@ "type": "Boolean", "doc": "indicates the account has not completed registration" }, + "pni": { + "type": "String" + }, "device_id": { "type": "int", "doc": "The Signal device ID. Official Signal mobile clients (iPhone and Android) have device ID = 1, while linked devices such as Signal Desktop or Signal iPad have higher device IDs." @@ -2093,7 +2127,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "uri": { @@ -2188,7 +2222,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "address": { @@ -2219,7 +2253,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "address": { @@ -2274,7 +2308,7 @@ "account": { "type": "String", "doc": "The account to delete", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "server": { @@ -2289,7 +2323,7 @@ "account": { "type": "String", "doc": "The account to use", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "address": { @@ -2324,7 +2358,7 @@ "account": { "type": "String", "doc": "The account to use", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "address": { @@ -2364,7 +2398,7 @@ "account": { "type": "String", "doc": "The account to use", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true } }, @@ -2388,7 +2422,7 @@ "account": { "type": "String", "doc": "The account to use", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "uri": { @@ -2412,6 +2446,7 @@ "fields": { "account": { "type": "String", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "address": { @@ -2436,7 +2471,7 @@ "account": { "type": "String", "doc": "The account to use", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "address": { @@ -2460,7 +2495,7 @@ "account": { "type": "String", "doc": "The account to set the device name of", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "device_name": { @@ -2495,7 +2530,7 @@ "account": { "type": "String", "doc": "The account to subscribe to incoming message for", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true } }, @@ -2506,7 +2541,7 @@ "account": { "type": "String", "doc": "The account to unsubscribe from", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true } }, @@ -2517,7 +2552,7 @@ "account": { "type": "String", "doc": "the account to use", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "address": { @@ -2577,7 +2612,7 @@ "account": { "type": "String", "doc": "the account to use", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "address": { @@ -2602,7 +2637,7 @@ "account": { "type": "String", "doc": "The account to use to retrieve the remote config", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true } }, @@ -2622,7 +2657,7 @@ "account": { "type": "String", "doc": "The account to interact with", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "members": { @@ -2636,6 +2671,9 @@ "type": "String", "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", "required": true + }, + "also_ban": { + "type": "boolean" } }, "doc": "deny a request to join a group" @@ -2644,6 +2682,7 @@ "fields": { "account": { "type": "String", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "challenge": { @@ -2660,7 +2699,7 @@ "account": { "type": "String", "doc": "The account to use to use", - "example": "\"+12024561414\"", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", "required": true }, "identifier": { @@ -2688,6 +2727,158 @@ }, "doc": "An optional part of the linking process. Intended to be called after displaying the QR code, will return quickly after the user scans the QR code. finish_link must be called after wait_for_scan returns a non-error" }, + "GetGroupRevisionPagesRequest": { + "fields": { + "account": { + "type": "String", + "doc": "The account to interact with", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", + "required": true + }, + "group_id": { + "type": "String", + "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", + "required": true + }, + "from_revision": { + "type": "int", + "doc": "The revision to start the pages from. Note that if this is lower than the revision you joined the group, an AuthorizationFailedError is returned.", + "required": true + }, + "include_first_revision": { + "type": "boolean", + "doc": "Whether to include the first state in the returned pages (default false)" + } + }, + "doc": "Query the server for group revision history. The history contains information about the changes between each revision and the user that made the change." + }, + "GroupHistoryPage": { + "fields": { + "results": { + "list": true, + "type": "GroupHistoryEntry", + "version": "v1" + }, + "paging_data": { + "type": "PagingData", + "version": "v1" + } + }, + "doc": "The result of fetching a group's history along with paging data." + }, + "SendSyncMessageRequest": { + "fields": { + "account": { + "type": "String", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", + "required": true + }, + "view_once_open_message": { + "type": "JsonViewOnceOpenMessage", + "version": "v1", + "doc": "This can be set to indicate to other devices about having viewed a view-once message." + }, + "message_request_response": { + "type": "JsonMessageRequestResponseMessage", + "version": "v1", + "doc": "This can be set to indicate to other devices about a response to an incoming message request from an unknown user or group. Warning: Using the BLOCK and BLOCK_AND_DELETE options relies on other devices to do the blocking, and it does not make you leave the group!" + } + }, + "doc": "Sends a sync message to the account's devices" + }, + "JsonSendMessageResult": { + "fields": { + "address": { + "type": "JsonAddress", + "version": "v1" + }, + "success": { + "type": "SendSuccess", + "version": "v1" + }, + "networkFailure": { + "type": "boolean", + "example": "false" + }, + "unregisteredFailure": { + "type": "boolean", + "example": "false" + }, + "identityFailure": { + "type": "String" + }, + "proof_required_failure": { + "type": "ProofRequiredError", + "version": "v1" + } + } + }, + "BanUserRequest": { + "fields": { + "account": { + "type": "String", + "doc": "The account to interact with", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", + "required": true + }, + "group_id": { + "type": "String", + "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", + "required": true + }, + "users": { + "list": true, + "type": "JsonAddress", + "version": "v1", + "doc": "List of users to ban", + "required": true + } + }, + "doc": "Bans users from a group. This works even if the users aren't in the group. If they are currently in the group, they will also be removed." + }, + "UnbanUserRequest": { + "fields": { + "account": { + "type": "String", + "doc": "The account to interact with", + "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", + "required": true + }, + "group_id": { + "type": "String", + "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", + "required": true + }, + "users": { + "list": true, + "type": "JsonAddress", + "version": "v1", + "doc": "List of users to unban", + "required": true + } + }, + "doc": "Unbans users from a group." + }, + "IdentityKey": { + "fields": { + "added": { + "type": "long", + "doc": "the first time this identity key was seen" + }, + "safety_number": { + "type": "String", + "example": "\"373453558586758076680580548714989751943247272727416091564451\"" + }, + "qr_code_data": { + "type": "String", + "doc": "base64-encoded QR code data" + }, + "trust_level": { + "type": "String", + "doc": "One of TRUSTED_UNVERIFIED, TRUSTED_VERIFIED or UNTRUSTED" + } + } + }, "JsonDataMessage": { "fields": { "timestamp": { @@ -2773,9 +2964,17 @@ "version": "v1", "doc": "details about the MobileCoin payment attached to the message, if present" }, + "is_expiration_update": { + "type": "boolean", + "doc": "whether or not this message changes the expiresInSeconds value for the whole chat. Some messages (remote deletes, reactions, etc) will have expiresInSeconds=0 even though the chat has disappearing messages enabled." + }, "group_call_update": { "type": "String", "doc": "the eraId string from a group call message update" + }, + "story_context": { + "type": "StoryContext", + "version": "v1" } } }, @@ -2893,23 +3092,22 @@ } } }, - "IdentityKey": { + "StoryMessage": { "fields": { - "added": { - "type": "long", - "doc": "the first time this identity key was seen" + "group": { + "type": "JsonGroupV2Info", + "version": "v1" }, - "safety_number": { - "type": "String", - "example": "\"373453558586758076680580548714989751943247272727416091564451\"" + "file": { + "type": "JsonAttachment", + "version": "v1" }, - "qr_code_data": { - "type": "String", - "doc": "base64-encoded QR code data" + "text": { + "type": "TextAttachment", + "version": "v1" }, - "trust_level": { - "type": "String", - "doc": "One of TRUSTED_UNVERIFIED, TRUSTED_VERIFIED or UNTRUSTED" + "allow_replies": { + "type": "Boolean" } } }, @@ -2975,7 +3173,7 @@ "text": { "type": "String", "doc": "the body of the message being quoted", - "example": "\"hey ? what's up?\"" + "example": "\"hey  what's up?\"" }, "attachments": { "list": true, @@ -3033,39 +3231,12 @@ }, "doc": "metadata about one of the links in a message" }, - "JsonSendMessageResult": { - "fields": { - "address": { - "type": "JsonAddress", - "version": "v1" - }, - "success": { - "type": "SendSuccess", - "version": "v1" - }, - "networkFailure": { - "type": "boolean", - "example": "false" - }, - "unregisteredFailure": { - "type": "boolean", - "example": "false" - }, - "identityFailure": { - "type": "String" - }, - "proof_required_failure": { - "type": "ProofRequiredError", - "version": "v1" - } - } - }, "JsonReaction": { "fields": { "emoji": { "type": "String", "doc": "the emoji to react with", - "example": "\"?\"" + "example": "\"👍\"" }, "remove": { "type": "boolean", @@ -3117,6 +3288,125 @@ } } }, + "BannedGroupMember": { + "fields": { + "uuid": { + "type": "String", + "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" + }, + "timestamp": { + "type": "long", + "doc": "Timestamp as milliseconds since Unix epoch of when the user was banned. This field is set by the server." + } + } + }, + "GroupChange": { + "fields": { + "editor": { + "type": "JsonAddress", + "version": "v1", + "doc": "The user that made the change." + }, + "revision": { + "type": "Integer", + "doc": "The group revision that this change brings the group to." + }, + "new_members": { + "list": true, + "type": "GroupMember", + "version": "v1", + "doc": "Represents users have been added to the group. This can be from group members adding users, or a users joining via a group link that required no approval." + }, + "delete_members": { + "list": true, + "type": "JsonAddress", + "version": "v1", + "doc": "Represents users that have been removed from the group. This can be from admins removing users, or users choosing to leave the group" + }, + "modify_member_roles": { + "list": true, + "type": "GroupMember", + "version": "v1", + "doc": "Represents users with their new, modified role." + }, + "modified_profile_keys": { + "list": true, + "type": "GroupMember", + "version": "v1", + "doc": "Represents users that have rotated their profile key. Note that signald currently does not expose profile keys to clients. The joined revision property will always be 0 in this list." + }, + "new_pending_members": { + "list": true, + "type": "GroupPendingMember", + "version": "v1", + "doc": "Represents a user that has been invited to the group by another user." + }, + "delete_pending_members": { + "list": true, + "type": "JsonAddress", + "version": "v1" + }, + "promote_pending_members": { + "list": true, + "type": "GroupMember", + "version": "v1" + }, + "new_banned_members": { + "list": true, + "type": "BannedGroupMember", + "version": "v1" + }, + "new_unbanned_members": { + "list": true, + "type": "BannedGroupMember", + "version": "v1" + }, + "new_title": { + "type": "String" + }, + "new_avatar": { + "type": "Boolean", + "doc": "Whether this group change changed the avatar." + }, + "new_timer": { + "type": "Integer", + "doc": "New disappearing messages timer value." + }, + "new_access_control": { + "type": "GroupAccessControl", + "version": "v1", + "doc": "If not null, then this group change modified one of the access controls. Some of the properties in here will be null." + }, + "new_requesting_members": { + "list": true, + "type": "GroupRequestingMember", + "version": "v1", + "doc": "Represents users that have requested to join the group via the group link. Note that members requesting to join might not necessarily have the list of users in the group, so they won't be able to send a peer-to-peer group update message to inform users of their request to join. Other users in the group may inform us that the revision has increased, but the members requesting access will have to be obtained from the server instead (which signald will handle). For now, a get_group request has to be made to get the users that have requested to join the group." + }, + "delete_requesting_members": { + "list": true, + "type": "JsonAddress", + "version": "v1" + }, + "promote_requesting_members": { + "list": true, + "type": "GroupMember", + "version": "v1" + }, + "new_invite_link_password": { + "type": "Boolean", + "doc": "Whether this group change involved resetting the group invite link." + }, + "new_description": { + "type": "String" + }, + "new_is_announcement_group": { + "type": "String", + "doc": "Whether this change affected the announcement group setting. Possible values are UNKNOWN, ENABLED or DISABLED" + } + }, + "doc": "Represents a group change made by a user. This can also represent request link invites. Only the fields relevant to the group change performed will be set. Note that in signald, group changes are currently only received from incoming messages from a message subscription." + }, "DeviceInfo": { "fields": { "id": { @@ -3158,11 +3448,15 @@ "Capabilities": { "fields": { "gv2": { - "type": "boolean" + "type": "boolean", + "doc": "this capability is deprecated and will always be true" }, "storage": { "type": "boolean" }, + "stories": { + "type": "boolean" + }, "gv1-migration": { "type": "boolean" }, @@ -3263,6 +3557,93 @@ }, "doc": "A remote config (feature flag) entry." }, + "GroupHistoryEntry": { + "fields": { + "group": { + "type": "JsonGroupV2Info", + "version": "v1" + }, + "change": { + "type": "GroupChange", + "version": "v1" + } + } + }, + "PagingData": { + "fields": { + "has_more_pages": { + "type": "boolean" + }, + "next_page_revision": { + "type": "int" + } + } + }, + "JsonViewOnceOpenMessage": { + "fields": { + "sender": { + "type": "JsonAddress", + "version": "v1" + }, + "timestamp": { + "type": "long", + "example": "1615576442475" + } + } + }, + "JsonMessageRequestResponseMessage": { + "fields": { + "person": { + "type": "JsonAddress", + "version": "v1" + }, + "groupId": { + "type": "String" + }, + "type": { + "type": "String", + "doc": "One of UNKNOWN, ACCEPT, DELETE, BLOCK, BLOCK_AND_DELETE, UNBLOCK_AND_ACCEPT" + } + }, + "doc": "Responses to message requests from unknown users or groups" + }, + "SendSuccess": { + "fields": { + "unidentified": { + "type": "boolean" + }, + "needsSync": { + "type": "boolean" + }, + "duration": { + "type": "long" + }, + "devices": { + "list": true, + "type": "Integer" + } + } + }, + "ProofRequiredError": { + "fields": { + "token": { + "type": "String" + }, + "options": { + "list": true, + "type": "String", + "doc": "possible list values are RECAPTCHA and PUSH_CHALLENGE" + }, + "message": { + "type": "String" + }, + "retry_after": { + "type": "long", + "doc": "value in seconds" + } + }, + "error": true + }, "SharedContact": { "fields": { "name": { @@ -3306,6 +3687,16 @@ } } }, + "StoryContext": { + "fields": { + "author": { + "type": "String" + }, + "sent_timestamp": { + "type": "long" + } + } + }, "JsonSentTranscriptMessage": { "fields": { "destination": { @@ -3356,18 +3747,6 @@ } } }, - "JsonViewOnceOpenMessage": { - "fields": { - "sender": { - "type": "JsonAddress", - "version": "v1" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - } - } - }, "JsonVerifiedMessage": { "fields": { "destination": { @@ -3385,20 +3764,6 @@ } } }, - "JsonMessageRequestResponseMessage": { - "fields": { - "person": { - "type": "JsonAddress", - "version": "v1" - }, - "groupId": { - "type": "String" - }, - "type": { - "type": "String" - } - } - }, "OfferMessage": { "fields": { "id": { @@ -3464,42 +3829,63 @@ } } }, - "SendSuccess": { + "TextAttachment": { "fields": { - "unidentified": { - "type": "boolean" + "text": { + "type": "String" }, - "needsSync": { - "type": "boolean" + "style": { + "type": "String" }, - "duration": { - "type": "long" + "preview": { + "type": "JsonPreview", + "version": "v1" }, - "devices": { - "list": true, - "type": "Integer" + "text_foreground_color": { + "type": "String" + }, + "text_background_color": { + "type": "String" + }, + "background_gradient": { + "type": "Gradient", + "version": "v1" + }, + "background_color": { + "type": "String" } } }, - "ProofRequiredError": { + "GroupPendingMember": { "fields": { - "token": { - "type": "String" - }, - "options": { - "list": true, + "uuid": { "type": "String", - "doc": "possible list values are RECAPTCHA and PUSH_CHALLENGE" + "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" }, - "message": { - "type": "String" + "role": { + "type": "String", + "doc": "possible values are: UNKNOWN, DEFAULT, ADMINISTRATOR and UNRECOGNIZED", + "example": "\"DEFAULT\"" }, - "retry_after": { - "type": "long", - "doc": "value in seconds" + "timestamp": { + "type": "long" + }, + "added_by_uuid": { + "type": "String", + "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" } - }, - "error": true + } + }, + "GroupRequestingMember": { + "fields": { + "uuid": { + "type": "String", + "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" + }, + "timestamp": { + "type": "long" + } + } }, "ServerCDN": { "fields": { @@ -3611,6 +3997,16 @@ "type": "boolean" } } + }, + "Gradient": { + "fields": { + "start_color": { + "type": "String" + }, + "end_color": { + "type": "String" + } + } } } }, @@ -3652,6 +4048,12 @@ }, { "name": "AttachmentTooLargeError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -3689,6 +4091,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -3723,7 +4131,15 @@ "name": "InvalidRequestError" }, { - "name": "AuthorizationFailedError" + "name": "AuthorizationFailedError", + "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." + }, + { + "name": "SQLError" + }, + { + "name": "GroupPatchNotAcceptedError", + "doc": "Caused when server rejects the group update." } ] }, @@ -3754,7 +4170,15 @@ "name": "InvalidRequestError" }, { - "name": "AuthorizationFailedError" + "name": "AuthorizationFailedError", + "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." + }, + { + "name": "SQLError" + }, + { + "name": "GroupPatchNotAcceptedError", + "doc": "Caused when server rejects the group update." } ] }, @@ -3789,6 +4213,9 @@ }, { "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -3811,6 +4238,12 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -3851,6 +4284,12 @@ }, { "name": "InvalidGroupStateError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -3875,6 +4314,9 @@ }, { "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -3905,10 +4347,18 @@ "name": "InvalidRequestError" }, { - "name": "AuthorizationFailedError" + "name": "AuthorizationFailedError", + "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." }, { "name": "UnregisteredUserError" + }, + { + "name": "SQLError" + }, + { + "name": "GroupPatchNotAcceptedError", + "doc": "Caused when server rejects the group update, e.g. trying to add a user that's already in the group" } ] }, @@ -3932,6 +4382,12 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -3948,6 +4404,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -3971,6 +4433,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -3996,6 +4464,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4017,6 +4491,12 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4035,6 +4515,12 @@ }, { "name": "NoSuchAccountError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4074,6 +4560,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4104,6 +4596,9 @@ }, { "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4167,6 +4662,12 @@ }, { "name": "InternalError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4233,6 +4734,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4269,6 +4776,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4287,6 +4800,12 @@ }, { "name": "NoSuchAccountError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4323,6 +4842,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4360,6 +4885,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4384,6 +4915,12 @@ }, { "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4425,6 +4962,9 @@ }, { "name": "GroupVerificationError" + }, + { + "name": "SQLError" } ] }, @@ -4444,6 +4984,12 @@ }, { "name": "InternalError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4474,10 +5020,18 @@ "name": "InvalidRequestError" }, { - "name": "AuthorizationFailedError" + "name": "AuthorizationFailedError", + "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." }, { "name": "UnregisteredUserError" + }, + { + "name": "SQLError" + }, + { + "name": "GroupPatchNotAcceptedError", + "doc": "If updating a group, caused when server rejects the group update." } ] }, @@ -4496,6 +5050,12 @@ }, { "name": "NoSuchAccountError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4515,6 +5075,12 @@ }, { "name": "InternalError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4536,6 +5102,9 @@ }, { "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4548,6 +5117,9 @@ }, { "name": "InternalError" + }, + { + "name": "SQLError" } ] }, @@ -4585,6 +5157,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4655,6 +5233,12 @@ }, { "name": "UnregisteredUserError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" } ] }, @@ -4674,6 +5258,9 @@ }, { "name": "NoSuchAccountError" + }, + { + "name": "SQLError" } ] }, @@ -4704,10 +5291,18 @@ "name": "InvalidRequestError" }, { - "name": "AuthorizationFailedError" + "name": "AuthorizationFailedError", + "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." }, { "name": "UnregisteredUserError" + }, + { + "name": "SQLError" + }, + { + "name": "GroupPatchNotAcceptedError", + "doc": "Caused when server rejects the group update." } ] }, @@ -4728,6 +5323,9 @@ }, { "name": "InternalError" + }, + { + "name": "SQLError" } ] }, @@ -4747,6 +5345,9 @@ }, { "name": "NoSuchAccountError" + }, + { + "name": "SQLError" } ] }, @@ -4764,6 +5365,159 @@ "name": "InternalError" } ] + }, + "get_group_revision_pages": { + "request": "GetGroupRevisionPagesRequest", + "response": "GroupHistoryPage", + "doc": "Query the server for group revision history. The history contains information about the changes between each revision and the user that made the change.", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "InternalError" + }, + { + "name": "GroupVerificationError" + }, + { + "name": "InvalidGroupStateError" + }, + { + "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError", + "doc": "caused when not a member of the group, when requesting logs from a revision lower than your joinedAtVersion, etc." + }, + { + "name": "RateLimitError" + }, + { + "name": "SQLError" + } + ] + }, + "send_sync_message": { + "request": "SendSyncMessageRequest", + "response": "JsonSendMessageResult", + "doc": "Sends a sync message to the account's devices", + "errors": [ + { + "name": "InvalidRequestError" + }, + { + "name": "RateLimitError" + }, + { + "name": "InternalError" + }, + { + "name": "UnregisteredUserError" + }, + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "AuthorizationFailedError" + }, + { + "name": "SQLError" + } + ] + }, + "ban_user": { + "request": "BanUserRequest", + "response": "JsonGroupV2Info", + "doc": "Bans users from a group. This works even if the users aren't in the group. If they are currently in the group, they will also be removed.", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "GroupVerificationError" + }, + { + "name": "InternalError" + }, + { + "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError", + "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." + }, + { + "name": "SQLError" + }, + { + "name": "GroupPatchNotAcceptedError", + "doc": "Caused when server rejects the group update." + } + ] + }, + "unban_user": { + "request": "UnbanUserRequest", + "response": "JsonGroupV2Info", + "doc": "Unbans users from a group.", + "errors": [ + { + "name": "NoSuchAccountError" + }, + { + "name": "ServerNotFoundError" + }, + { + "name": "InvalidProxyError" + }, + { + "name": "UnknownGroupError" + }, + { + "name": "GroupVerificationError" + }, + { + "name": "InternalError" + }, + { + "name": "InvalidRequestError" + }, + { + "name": "AuthorizationFailedError", + "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." + }, + { + "name": "SQLError" + }, + { + "name": "GroupPatchNotAcceptedError", + "doc": "Caused when server rejects the group update." + } + ] } } } diff --git a/signald/client-protocol/v0/structs.go b/signald/client-protocol/v0/structs.go index 92de9ec..1afd428 100644 --- a/signald/client-protocol/v0/structs.go +++ b/signald/client-protocol/v0/structs.go @@ -291,6 +291,7 @@ type OfferMessage struct { } type Optional struct { + Empty bool `json:"empty,omitempty" yaml:"empty,omitempty"` Present bool `json:"present,omitempty" yaml:"present,omitempty"` } diff --git a/signald/client-protocol/v1/errors.go b/signald/client-protocol/v1/errors.go index 0b3662c..c7a4841 100644 --- a/signald/client-protocol/v1/errors.go +++ b/signald/client-protocol/v1/errors.go @@ -81,6 +81,13 @@ func mkerr(response client_protocol.BasicResponse) error { return err } return result + case "GroupPatchNotAcceptedError": + result := GroupPatchNotAcceptedError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result case "GroupVerificationError": result := GroupVerificationError{} err := json.Unmarshal(response.Error, &result) @@ -207,6 +214,13 @@ func mkerr(response client_protocol.BasicResponse) error { return err } return result + case "ProtocolInvalidKeyIdError": + result := ProtocolInvalidKeyIdError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result case "ProtocolInvalidMessageError": result := ProtocolInvalidMessageError{} err := json.Unmarshal(response.Error, &result) @@ -214,6 +228,13 @@ func mkerr(response client_protocol.BasicResponse) error { return err } return result + case "ProtocolNoSessionError": + result := ProtocolNoSessionError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result case "RateLimitError": result := RateLimitError{} err := json.Unmarshal(response.Error, &result) @@ -221,6 +242,13 @@ func mkerr(response client_protocol.BasicResponse) error { return err } return result + case "SQLError": + result := SQLError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result case "ScanTimeoutError": result := ScanTimeoutError{} err := json.Unmarshal(response.Error, &result) @@ -309,7 +337,7 @@ func (e AttachmentTooLargeError) Error() string { return e.Message } -// AuthorizationFailedError: indicates the server rejected our credentials. Typically means the linked device was removed by the primary device, or that the account was re-registered +// AuthorizationFailedError: Indicates the server rejected our credentials or a failed group update. Typically means the linked device was removed by the primary device, or that the account was re-registered. For group updates, this can indicate that we lack permissions. type AuthorizationFailedError struct { Message string `json:"message,omitempty" yaml:"message,omitempty"` } @@ -360,6 +388,15 @@ func (e GroupNotActiveError) Error() string { return e.Message } +// GroupPatchNotAcceptedError: Indicates the server rejected our group update. This can be due to errors such as trying to add a user that's already in the group. +type GroupPatchNotAcceptedError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e GroupPatchNotAcceptedError) Error() string { + return e.Message +} + type GroupVerificationError struct { Message string `json:"message,omitempty" yaml:"message,omitempty"` } @@ -511,6 +548,19 @@ func (e ProofRequiredError) Error() string { return e.Message } +type ProtocolInvalidKeyIdError struct { + ContentHint int32 `json:"content_hint,omitempty" yaml:"content_hint,omitempty"` + GroupId string `json:"group_id,omitempty" yaml:"group_id,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` + Sender string `json:"sender,omitempty" yaml:"sender,omitempty"` + SenderDevice int32 `json:"sender_device,omitempty" yaml:"sender_device,omitempty"` + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` +} + +func (e ProtocolInvalidKeyIdError) Error() string { + return e.Message +} + type ProtocolInvalidMessageError struct { ContentHint int32 `json:"content_hint,omitempty" yaml:"content_hint,omitempty"` GroupId string `json:"group_id,omitempty" yaml:"group_id,omitempty"` @@ -524,6 +574,19 @@ func (e ProtocolInvalidMessageError) Error() string { return e.Message } +type ProtocolNoSessionError struct { + ContentHint int32 `json:"content_hint,omitempty" yaml:"content_hint,omitempty"` + GroupId string `json:"group_id,omitempty" yaml:"group_id,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` + Sender string `json:"sender,omitempty" yaml:"sender,omitempty"` + SenderDevice int32 `json:"sender_device,omitempty" yaml:"sender_device,omitempty"` + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` +} + +func (e ProtocolNoSessionError) Error() string { + return e.Message +} + type RateLimitError struct { Message string `json:"message,omitempty" yaml:"message,omitempty"` } @@ -532,6 +595,14 @@ func (e RateLimitError) Error() string { return e.Message } +type SQLError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e SQLError) Error() string { + return e.Message +} + type ScanTimeoutError struct { Message string `json:"message,omitempty" yaml:"message,omitempty"` } diff --git a/signald/client-protocol/v1/requests.go b/signald/client-protocol/v1/requests.go index 03388b9..ac5d93b 100644 --- a/signald/client-protocol/v1/requests.go +++ b/signald/client-protocol/v1/requests.go @@ -130,6 +130,38 @@ func (r *ApproveMembershipRequest) Submit(conn *signald.Signald) (response JsonG } +// Submit: Bans users from a group. This works even if the users aren't in the group. If they are currently in the group, they will also be removed. +func (r *BanUserRequest) Submit(conn *signald.Signald) (response JsonGroupV2Info, err error) { + r.Version = "v1" + r.Type = "ban_user" + if r.ID == "" { + r.ID = signald.GenerateID() + } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = mkerr(rawResponse) + return + } + + err = json.Unmarshal(rawResponse.Data, &response) + if err != nil { + rawResponseJson, _ := rawResponse.Data.MarshalJSON() + log.Println("signald-go: error unmarshalling response from signald of type", rawResponse.Type, string(rawResponseJson)) + return + } + + return response, nil + +} + func (r *CreateGroupRequest) Submit(conn *signald.Signald) (response JsonGroupV2Info, err error) { r.Version = "v1" r.Type = "create_group" @@ -338,6 +370,38 @@ func (r *GetGroupRequest) Submit(conn *signald.Signald) (response JsonGroupV2Inf } +// Submit: Query the server for group revision history. The history contains information about the changes between each revision and the user that made the change. +func (r *GetGroupRevisionPagesRequest) Submit(conn *signald.Signald) (response GroupHistoryPage, err error) { + r.Version = "v1" + r.Type = "get_group_revision_pages" + if r.ID == "" { + r.ID = signald.GenerateID() + } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = mkerr(rawResponse) + return + } + + err = json.Unmarshal(rawResponse.Data, &response) + if err != nil { + rawResponseJson, _ := rawResponse.Data.MarshalJSON() + log.Println("signald-go: error unmarshalling response from signald of type", rawResponse.Type, string(rawResponseJson)) + return + } + + return response, nil + +} + // Submit: Get information about a known keys for a particular address func (r *GetIdentitiesRequest) Submit(conn *signald.Signald) (response IdentityKeyList, err error) { r.Version = "v1" @@ -1047,6 +1111,38 @@ func (r *SendPaymentRequest) Submit(conn *signald.Signald) (response SendRespons } +// Submit: Sends a sync message to the account's devices +func (r *SendSyncMessageRequest) Submit(conn *signald.Signald) (response JsonSendMessageResult, err error) { + r.Version = "v1" + r.Type = "send_sync_message" + if r.ID == "" { + r.ID = signald.GenerateID() + } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = mkerr(rawResponse) + return + } + + err = json.Unmarshal(rawResponse.Data, &response) + if err != nil { + rawResponseJson, _ := rawResponse.Data.MarshalJSON() + log.Println("signald-go: error unmarshalling response from signald of type", rawResponse.Type, string(rawResponseJson)) + return + } + + return response, nil + +} + // Submit: set this device's name. This will show up on the mobile device on the same account under settings -> linked devices func (r *SetDeviceNameRequest) Submit(conn *signald.Signald) (err error) { r.Version = "v1" @@ -1227,6 +1323,38 @@ func (r *TypingRequest) Submit(conn *signald.Signald) (err error) { } +// Submit: Unbans users from a group. +func (r *UnbanUserRequest) Submit(conn *signald.Signald) (response JsonGroupV2Info, err error) { + r.Version = "v1" + r.Type = "unban_user" + if r.ID == "" { + r.ID = signald.GenerateID() + } + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = mkerr(rawResponse) + return + } + + err = json.Unmarshal(rawResponse.Data, &response) + if err != nil { + rawResponseJson, _ := rawResponse.Data.MarshalJSON() + log.Println("signald-go: error unmarshalling response from signald of type", rawResponse.Type, string(rawResponseJson)) + return + } + + return response, nil + +} + // Submit: See subscribe for more info func (r *UnsubscribeRequest) Submit(conn *signald.Signald) (err error) { r.Version = "v1" diff --git a/signald/client-protocol/v1/structs.go b/signald/client-protocol/v1/structs.go index e589b8e..cf4845f 100644 --- a/signald/client-protocol/v1/structs.go +++ b/signald/client-protocol/v1/structs.go @@ -25,6 +25,7 @@ type Account struct { Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` // The address of this account DeviceId int32 `json:"device_id,omitempty" yaml:"device_id,omitempty"` // The Signal device ID. Official Signal mobile clients (iPhone and Android) have device ID = 1, while linked devices such as Signal Desktop or Signal iPad have higher device IDs. Pending bool `json:"pending,omitempty" yaml:"pending,omitempty"` // indicates the account has not completed registration + Pni string `json:"pni,omitempty" yaml:"pni,omitempty"` } type AccountList struct { @@ -62,6 +63,19 @@ type ApproveMembershipRequest struct { Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // list of requesting members to approve } +// BanUserRequest: Bans users from a group. This works even if the users aren't in the group. If they are currently in the group, they will also be removed. +type BanUserRequest struct { + Request + Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with + GroupId string `json:"group_id,omitempty" yaml:"group_id,omitempty"` + Users []*JsonAddress `json:"users,omitempty" yaml:"users,omitempty"` // List of users to ban +} + +type BannedGroupMember struct { + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` // Timestamp as milliseconds since Unix epoch of when the user was banned. This field is set by the server. + UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` +} + // BooleanMessage: A message containing a single boolean, usually as a response type BooleanMessage struct { Value bool `json:"value,omitempty" yaml:"value,omitempty"` @@ -85,9 +99,10 @@ type Capabilities struct { AnnouncementGroup bool `json:"announcement_group,omitempty" yaml:"announcement_group,omitempty"` ChangeNumber bool `json:"change_number,omitempty" yaml:"change_number,omitempty"` Gv1Migration bool `json:"gv1-migration,omitempty" yaml:"gv1-migration,omitempty"` - Gv2 bool `json:"gv2,omitempty" yaml:"gv2,omitempty"` + Gv2 bool `json:"gv2,omitempty" yaml:"gv2,omitempty"` // this capability is deprecated and will always be true SenderKey bool `json:"sender_key,omitempty" yaml:"sender_key,omitempty"` Storage bool `json:"storage,omitempty" yaml:"storage,omitempty"` + Stories bool `json:"stories,omitempty" yaml:"stories,omitempty"` } // ClientMessageWrapper: Wraps all incoming messages sent to the client after a v1 subscribe request is issued @@ -127,6 +142,7 @@ type DeviceInfo struct { type FinishLinkRequest struct { Request DeviceName string `json:"device_name,omitempty" yaml:"device_name,omitempty"` + Overwrite bool `json:"overwrite,omitempty" yaml:"overwrite,omitempty"` // overwrite existing account data if the phone number conflicts. false by default SessionId string `json:"session_id,omitempty" yaml:"session_id,omitempty"` } @@ -150,6 +166,15 @@ type GetGroupRequest struct { Revision int32 `json:"revision,omitempty" yaml:"revision,omitempty"` // the latest known revision, default value (-1) forces fetch from server } +// GetGroupRevisionPagesRequest: Query the server for group revision history. The history contains information about the changes between each revision and the user that made the change. +type GetGroupRevisionPagesRequest struct { + Request + Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with + FromRevision int32 `json:"from_revision,omitempty" yaml:"from_revision,omitempty"` // The revision to start the pages from. Note that if this is lower than the revision you joined the group, an AuthorizationFailedError is returned. + GroupId string `json:"group_id,omitempty" yaml:"group_id,omitempty"` + IncludeFirstRevision bool `json:"include_first_revision,omitempty" yaml:"include_first_revision,omitempty"` // Whether to include the first state in the returned pages (default false) +} + // GetIdentitiesRequest: Get information about a known keys for a particular address type GetIdentitiesRequest struct { Request @@ -175,6 +200,11 @@ type GetServersRequest struct { Request } +type Gradient struct { + EndColor string `json:"end_color,omitempty" yaml:"end_color,omitempty"` + StartColor string `json:"start_color,omitempty" yaml:"start_color,omitempty"` +} + // GroupAccessControl: group access control settings. Options for each controlled action are: UNKNOWN, ANY, MEMBER, ADMINISTRATOR, UNSATISFIABLE and UNRECOGNIZED type GroupAccessControl struct { Attributes string `json:"attributes,omitempty" yaml:"attributes,omitempty"` // who can edit group info @@ -182,6 +212,42 @@ type GroupAccessControl struct { Members string `json:"members,omitempty" yaml:"members,omitempty"` // who can add members } +// GroupChange: Represents a group change made by a user. This can also represent request link invites. Only the fields relevant to the group change performed will be set. Note that in signald, group changes are currently only received from incoming messages from a message subscription. +type GroupChange struct { + DeleteMembers []*JsonAddress `json:"delete_members,omitempty" yaml:"delete_members,omitempty"` // Represents users that have been removed from the group. This can be from admins removing users, or users choosing to leave the group + DeletePendingMembers []*JsonAddress `json:"delete_pending_members,omitempty" yaml:"delete_pending_members,omitempty"` + DeleteRequestingMembers []*JsonAddress `json:"delete_requesting_members,omitempty" yaml:"delete_requesting_members,omitempty"` + Editor *JsonAddress `json:"editor,omitempty" yaml:"editor,omitempty"` // The user that made the change. + ModifiedProfileKeys []*GroupMember `json:"modified_profile_keys,omitempty" yaml:"modified_profile_keys,omitempty"` // Represents users that have rotated their profile key. Note that signald currently does not expose profile keys to clients. The joined revision property will always be 0 in this list. + ModifyMemberRoles []*GroupMember `json:"modify_member_roles,omitempty" yaml:"modify_member_roles,omitempty"` // Represents users with their new, modified role. + NewAccessControl *GroupAccessControl `json:"new_access_control,omitempty" yaml:"new_access_control,omitempty"` // If not null, then this group change modified one of the access controls. Some of the properties in here will be null. + NewAvatar bool `json:"new_avatar,omitempty" yaml:"new_avatar,omitempty"` // Whether this group change changed the avatar. + NewBannedMembers []*BannedGroupMember `json:"new_banned_members,omitempty" yaml:"new_banned_members,omitempty"` + NewDescription string `json:"new_description,omitempty" yaml:"new_description,omitempty"` + NewInviteLinkPassword bool `json:"new_invite_link_password,omitempty" yaml:"new_invite_link_password,omitempty"` // Whether this group change involved resetting the group invite link. + NewIsAnnouncementGroup string `json:"new_is_announcement_group,omitempty" yaml:"new_is_announcement_group,omitempty"` // Whether this change affected the announcement group setting. Possible values are UNKNOWN, ENABLED or DISABLED + NewMembers []*GroupMember `json:"new_members,omitempty" yaml:"new_members,omitempty"` // Represents users have been added to the group. This can be from group members adding users, or a users joining via a group link that required no approval. + NewPendingMembers []*GroupPendingMember `json:"new_pending_members,omitempty" yaml:"new_pending_members,omitempty"` // Represents a user that has been invited to the group by another user. + NewRequestingMembers []*GroupRequestingMember `json:"new_requesting_members,omitempty" yaml:"new_requesting_members,omitempty"` // Represents users that have requested to join the group via the group link. Note that members requesting to join might not necessarily have the list of users in the group, so they won't be able to send a peer-to-peer group update message to inform users of their request to join. Other users in the group may inform us that the revision has increased, but the members requesting access will have to be obtained from the server instead (which signald will handle). For now, a get_group request has to be made to get the users that have requested to join the group. + NewTimer int32 `json:"new_timer,omitempty" yaml:"new_timer,omitempty"` // New disappearing messages timer value. + NewTitle string `json:"new_title,omitempty" yaml:"new_title,omitempty"` + NewUnbannedMembers []*BannedGroupMember `json:"new_unbanned_members,omitempty" yaml:"new_unbanned_members,omitempty"` + PromotePendingMembers []*GroupMember `json:"promote_pending_members,omitempty" yaml:"promote_pending_members,omitempty"` + PromoteRequestingMembers []*GroupMember `json:"promote_requesting_members,omitempty" yaml:"promote_requesting_members,omitempty"` + Revision int32 `json:"revision,omitempty" yaml:"revision,omitempty"` // The group revision that this change brings the group to. +} + +type GroupHistoryEntry struct { + Change *GroupChange `json:"change,omitempty" yaml:"change,omitempty"` + Group *JsonGroupV2Info `json:"group,omitempty" yaml:"group,omitempty"` +} + +// GroupHistoryPage: The result of fetching a group's history along with paging data. +type GroupHistoryPage struct { + PagingData *PagingData `json:"paging_data,omitempty" yaml:"paging_data,omitempty"` + Results []*GroupHistoryEntry `json:"results,omitempty" yaml:"results,omitempty"` +} + // GroupInfo: A generic type that is used when the group version is not known type GroupInfo struct { V1 *JsonGroupInfo `json:"v1,omitempty" yaml:"v1,omitempty"` @@ -206,6 +272,18 @@ type GroupMember struct { UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` } +type GroupPendingMember struct { + AddedByUuid string `json:"added_by_uuid,omitempty" yaml:"added_by_uuid,omitempty"` + Role string `json:"role,omitempty" yaml:"role,omitempty"` // possible values are: UNKNOWN, DEFAULT, ADMINISTRATOR and UNRECOGNIZED + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` + UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` +} + +type GroupRequestingMember struct { + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` + UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` +} + type HangupMessage struct { DeviceId int32 `json:"device_id,omitempty" yaml:"device_id,omitempty"` ID int64 `json:"id,omitempty" yaml:"id,omitempty"` @@ -244,6 +322,7 @@ type IncomingMessage struct { ServerReceiverTimestamp int64 `json:"server_receiver_timestamp,omitempty" yaml:"server_receiver_timestamp,omitempty"` Source *JsonAddress `json:"source,omitempty" yaml:"source,omitempty"` SourceDevice int32 `json:"source_device,omitempty" yaml:"source_device,omitempty"` + StoryMessage *StoryMessage `json:"story_message,omitempty" yaml:"story_message,omitempty"` SyncMessage *JsonSyncMessage `json:"sync_message,omitempty" yaml:"sync_message,omitempty"` Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` Type string `json:"type,omitempty" yaml:"type,omitempty"` @@ -294,24 +373,26 @@ type JsonBlockedListMessage struct { } type JsonDataMessage struct { - Attachments []*JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` // files attached to the incoming message - Body string `json:"body,omitempty" yaml:"body,omitempty"` // the text body of the incoming message. - Contacts []*SharedContact `json:"contacts,omitempty" yaml:"contacts,omitempty"` // if the incoming message has a shared contact, the contact's information will be here - EndSession bool `json:"endSession,omitempty" yaml:"endSession,omitempty"` - ExpiresInSeconds int32 `json:"expiresInSeconds,omitempty" yaml:"expiresInSeconds,omitempty"` // the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds - Group *JsonGroupInfo `json:"group,omitempty" yaml:"group,omitempty"` // if the incoming message was sent to a v1 group, information about that group will be here - GroupV2 *JsonGroupV2Info `json:"groupV2,omitempty" yaml:"groupV2,omitempty"` // if the incoming message was sent to a v2 group, basic identifying information about that group will be here. If group information changes, JsonGroupV2Info.revision is incremented. If the group revision is higher than previously seen, a client can retrieve the group information by calling get_group. - GroupCallUpdate string `json:"group_call_update,omitempty" yaml:"group_call_update,omitempty"` // the eraId string from a group call message update - Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` // list of mentions in the message - Payment *Payment `json:"payment,omitempty" yaml:"payment,omitempty"` // details about the MobileCoin payment attached to the message, if present - Previews []*JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"` // if the incoming message has a link preview, information about that preview will be here - ProfileKeyUpdate bool `json:"profileKeyUpdate,omitempty" yaml:"profileKeyUpdate,omitempty"` - Quote *JsonQuote `json:"quote,omitempty" yaml:"quote,omitempty"` // if the incoming message is a quote or reply to another message, this will contain information about that message - Reaction *JsonReaction `json:"reaction,omitempty" yaml:"reaction,omitempty"` // if the message adds or removes a reaction to another message, this will indicate what change is being made - RemoteDelete *RemoteDelete `json:"remoteDelete,omitempty" yaml:"remoteDelete,omitempty"` // if the inbound message is deleting a previously sent message, indicates which message should be deleted - Sticker *v0.JsonSticker `json:"sticker,omitempty" yaml:"sticker,omitempty"` // if the incoming message is a sticker, information about the sicker will be here - Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` // the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes. - ViewOnce bool `json:"viewOnce,omitempty" yaml:"viewOnce,omitempty"` // indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image. + Attachments []*JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` // files attached to the incoming message + Body string `json:"body,omitempty" yaml:"body,omitempty"` // the text body of the incoming message. + Contacts []*SharedContact `json:"contacts,omitempty" yaml:"contacts,omitempty"` // if the incoming message has a shared contact, the contact's information will be here + EndSession bool `json:"endSession,omitempty" yaml:"endSession,omitempty"` + ExpiresInSeconds int32 `json:"expiresInSeconds,omitempty" yaml:"expiresInSeconds,omitempty"` // the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds + Group *JsonGroupInfo `json:"group,omitempty" yaml:"group,omitempty"` // if the incoming message was sent to a v1 group, information about that group will be here + GroupV2 *JsonGroupV2Info `json:"groupV2,omitempty" yaml:"groupV2,omitempty"` // if the incoming message was sent to a v2 group, basic identifying information about that group will be here. If group information changes, JsonGroupV2Info.revision is incremented. If the group revision is higher than previously seen, a client can retrieve the group information by calling get_group. + GroupCallUpdate string `json:"group_call_update,omitempty" yaml:"group_call_update,omitempty"` // the eraId string from a group call message update + IsExpirationUpdate bool `json:"is_expiration_update,omitempty" yaml:"is_expiration_update,omitempty"` // whether or not this message changes the expiresInSeconds value for the whole chat. Some messages (remote deletes, reactions, etc) will have expiresInSeconds=0 even though the chat has disappearing messages enabled. + Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` // list of mentions in the message + Payment *Payment `json:"payment,omitempty" yaml:"payment,omitempty"` // details about the MobileCoin payment attached to the message, if present + Previews []*JsonPreview `json:"previews,omitempty" yaml:"previews,omitempty"` // if the incoming message has a link preview, information about that preview will be here + ProfileKeyUpdate bool `json:"profileKeyUpdate,omitempty" yaml:"profileKeyUpdate,omitempty"` + Quote *JsonQuote `json:"quote,omitempty" yaml:"quote,omitempty"` // if the incoming message is a quote or reply to another message, this will contain information about that message + Reaction *JsonReaction `json:"reaction,omitempty" yaml:"reaction,omitempty"` // if the message adds or removes a reaction to another message, this will indicate what change is being made + RemoteDelete *RemoteDelete `json:"remoteDelete,omitempty" yaml:"remoteDelete,omitempty"` // if the inbound message is deleting a previously sent message, indicates which message should be deleted + Sticker *v0.JsonSticker `json:"sticker,omitempty" yaml:"sticker,omitempty"` // if the incoming message is a sticker, information about the sicker will be here + StoryContext *StoryContext `json:"story_context,omitempty" yaml:"story_context,omitempty"` + Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` // the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes. + ViewOnce bool `json:"viewOnce,omitempty" yaml:"viewOnce,omitempty"` // indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image. } // JsonGroupInfo: information about a legacy group @@ -335,21 +416,23 @@ type JsonGroupJoinInfo struct { // JsonGroupV2Info: Information about a Signal group type JsonGroupV2Info struct { - AccessControl *GroupAccessControl `json:"accessControl,omitempty" yaml:"accessControl,omitempty"` // current access control settings for this group - Announcements string `json:"announcements,omitempty" yaml:"announcements,omitempty"` // indicates if the group is an announcements group. Only admins are allowed to send messages to announcements groups. Options are UNKNOWN, ENABLED or DISABLED - Avatar string `json:"avatar,omitempty" yaml:"avatar,omitempty"` // path to the group's avatar on local disk, if available - Description string `json:"description,omitempty" yaml:"description,omitempty"` - ID string `json:"id,omitempty" yaml:"id,omitempty"` - InviteLink string `json:"inviteLink,omitempty" yaml:"inviteLink,omitempty"` // the signal.group link, if applicable - MemberDetail []*GroupMember `json:"memberDetail,omitempty" yaml:"memberDetail,omitempty"` // detailed member list - Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` - PendingMemberDetail []*GroupMember `json:"pendingMemberDetail,omitempty" yaml:"pendingMemberDetail,omitempty"` // detailed pending member list - PendingMembers []*JsonAddress `json:"pendingMembers,omitempty" yaml:"pendingMembers,omitempty"` - Removed bool `json:"removed,omitempty" yaml:"removed,omitempty"` // will be set to true for incoming messages to indicate the user has been removed from the group - RequestingMembers []*JsonAddress `json:"requestingMembers,omitempty" yaml:"requestingMembers,omitempty"` - Revision int32 `json:"revision,omitempty" yaml:"revision,omitempty"` - Timer int32 `json:"timer,omitempty" yaml:"timer,omitempty"` - Title string `json:"title,omitempty" yaml:"title,omitempty"` + AccessControl *GroupAccessControl `json:"accessControl,omitempty" yaml:"accessControl,omitempty"` // current access control settings for this group + Announcements string `json:"announcements,omitempty" yaml:"announcements,omitempty"` // indicates if the group is an announcements group. Only admins are allowed to send messages to announcements groups. Options are UNKNOWN, ENABLED or DISABLED + Avatar string `json:"avatar,omitempty" yaml:"avatar,omitempty"` // path to the group's avatar on local disk, if available + BannedMembers []*BannedGroupMember `json:"banned_members,omitempty" yaml:"banned_members,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + GroupChange *GroupChange `json:"group_change,omitempty" yaml:"group_change,omitempty"` // Represents a peer-to-peer group change done by a user. Will not be set if the group change signature fails verification. This is usually only set inside of incoming messages. + ID string `json:"id,omitempty" yaml:"id,omitempty"` + InviteLink string `json:"inviteLink,omitempty" yaml:"inviteLink,omitempty"` // the signal.group link, if applicable + MemberDetail []*GroupMember `json:"memberDetail,omitempty" yaml:"memberDetail,omitempty"` // detailed member list + Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` + PendingMemberDetail []*GroupMember `json:"pendingMemberDetail,omitempty" yaml:"pendingMemberDetail,omitempty"` // detailed pending member list + PendingMembers []*JsonAddress `json:"pendingMembers,omitempty" yaml:"pendingMembers,omitempty"` + Removed bool `json:"removed,omitempty" yaml:"removed,omitempty"` // will be set to true for incoming messages to indicate the user has been removed from the group + RequestingMembers []*JsonAddress `json:"requestingMembers,omitempty" yaml:"requestingMembers,omitempty"` + Revision int32 `json:"revision,omitempty" yaml:"revision,omitempty"` + Timer int32 `json:"timer,omitempty" yaml:"timer,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` } type JsonMention struct { @@ -358,31 +441,11 @@ type JsonMention struct { UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` // The UUID of the account being mentioned } -type JsonMessageEnvelope struct { - CallMessage *v0.JsonCallMessage `json:"callMessage,omitempty" yaml:"callMessage,omitempty"` - DataMessage *JsonDataMessage `json:"dataMessage,omitempty" yaml:"dataMessage,omitempty"` - HasContent bool `json:"hasContent,omitempty" yaml:"hasContent,omitempty"` - HasLegacyMessage bool `json:"hasLegacyMessage,omitempty" yaml:"hasLegacyMessage,omitempty"` - IsUnidentifiedSender bool `json:"isUnidentifiedSender,omitempty" yaml:"isUnidentifiedSender,omitempty"` - Receipt *v0.JsonReceiptMessage `json:"receipt,omitempty" yaml:"receipt,omitempty"` - Relay string `json:"relay,omitempty" yaml:"relay,omitempty"` - ServerDeliveredTimestamp int64 `json:"serverDeliveredTimestamp,omitempty" yaml:"serverDeliveredTimestamp,omitempty"` - ServerTimestamp int64 `json:"serverTimestamp,omitempty" yaml:"serverTimestamp,omitempty"` - Source *JsonAddress `json:"source,omitempty" yaml:"source,omitempty"` - SourceDevice int32 `json:"sourceDevice,omitempty" yaml:"sourceDevice,omitempty"` - SyncMessage *JsonSyncMessage `json:"syncMessage,omitempty" yaml:"syncMessage,omitempty"` - Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` - TimestampISO string `json:"timestampISO,omitempty" yaml:"timestampISO,omitempty"` - Type string `json:"type,omitempty" yaml:"type,omitempty"` - Typing *v0.JsonTypingMessage `json:"typing,omitempty" yaml:"typing,omitempty"` - Username string `json:"username,omitempty" yaml:"username,omitempty"` - UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` -} - +// JsonMessageRequestResponseMessage: Responses to message requests from unknown users or groups type JsonMessageRequestResponseMessage struct { GroupId string `json:"groupId,omitempty" yaml:"groupId,omitempty"` Person *JsonAddress `json:"person,omitempty" yaml:"person,omitempty"` - Type string `json:"type,omitempty" yaml:"type,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` // One of UNKNOWN, ACCEPT, DELETE, BLOCK, BLOCK_AND_DELETE, UNBLOCK_AND_ACCEPT } // JsonPreview: metadata about one of the links in a message @@ -519,6 +582,11 @@ type OfferMessage struct { Type string `json:"type,omitempty" yaml:"type,omitempty"` } +type PagingData struct { + HasMorePages bool `json:"has_more_pages,omitempty" yaml:"has_more_pages,omitempty"` + NextPageRevision int32 `json:"next_page_revision,omitempty" yaml:"next_page_revision,omitempty"` +} + // Payment: details about a MobileCoin payment type Payment struct { Note string `json:"note,omitempty" yaml:"note,omitempty"` // note attached to the payment @@ -566,6 +634,7 @@ type ReceiptMessage struct { type RefuseMembershipRequest struct { Request Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with + AlsoBan bool `json:"also_ban,omitempty" yaml:"also_ban,omitempty"` GroupId string `json:"group_id,omitempty" yaml:"group_id,omitempty"` Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // list of requesting members to refuse } @@ -658,6 +727,7 @@ type SendPaymentRequest struct { type SendRequest struct { Request + Account string `json:"account,omitempty" yaml:"account,omitempty"` Attachments []*JsonAttachment `json:"attachments,omitempty" yaml:"attachments,omitempty"` Members []*JsonAddress `json:"members,omitempty" yaml:"members,omitempty"` // Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"` @@ -682,6 +752,14 @@ type SendSuccess struct { Unidentified bool `json:"unidentified,omitempty" yaml:"unidentified,omitempty"` } +// SendSyncMessageRequest: Sends a sync message to the account's devices +type SendSyncMessageRequest struct { + Request + Account string `json:"account,omitempty" yaml:"account,omitempty"` + MessageRequestResponse *JsonMessageRequestResponseMessage `json:"message_request_response,omitempty" yaml:"message_request_response,omitempty"` // This can be set to indicate to other devices about a response to an incoming message request from an unknown user or group. Warning: Using the BLOCK and BLOCK_AND_DELETE options relies on other devices to do the blocking, and it does not make you leave the group! + ViewOnceOpenMessage *JsonViewOnceOpenMessage `json:"view_once_open_message,omitempty" yaml:"view_once_open_message,omitempty"` // This can be set to indicate to other devices about having viewed a view-once message. +} + // Server: a Signal server type Server struct { Ca string `json:"ca,omitempty" yaml:"ca,omitempty"` // base64 encoded trust store, password must be 'whisper' @@ -784,6 +862,23 @@ type SharedContactPhone struct { Value string `json:"value,omitempty" yaml:"value,omitempty"` // the phone number } +// StorageChange: Broadcast to subscribed clients when there is a state change from the storage service +type StorageChange struct { + Version int64 `json:"version,omitempty" yaml:"version,omitempty"` // Seems to behave like the group version numbers and increments every time the state changes +} + +type StoryContext struct { + Author string `json:"author,omitempty" yaml:"author,omitempty"` + SentTimestamp int64 `json:"sent_timestamp,omitempty" yaml:"sent_timestamp,omitempty"` +} + +type StoryMessage struct { + AllowReplies bool `json:"allow_replies,omitempty" yaml:"allow_replies,omitempty"` + File *JsonAttachment `json:"file,omitempty" yaml:"file,omitempty"` + Group *JsonGroupV2Info `json:"group,omitempty" yaml:"group,omitempty"` + Text *TextAttachment `json:"text,omitempty" yaml:"text,omitempty"` +} + type SubmitChallengeRequest struct { Request Account string `json:"account,omitempty" yaml:"account,omitempty"` @@ -797,6 +892,16 @@ type SubscribeRequest struct { Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to subscribe to incoming message for } +type TextAttachment struct { + BackgroundColor string `json:"background_color,omitempty" yaml:"background_color,omitempty"` + BackgroundGradient *Gradient `json:"background_gradient,omitempty" yaml:"background_gradient,omitempty"` + Preview *JsonPreview `json:"preview,omitempty" yaml:"preview,omitempty"` + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Text string `json:"text,omitempty" yaml:"text,omitempty"` + TextBackgroundColor string `json:"text_background_color,omitempty" yaml:"text_background_color,omitempty"` + TextForegroundColor string `json:"text_foreground_color,omitempty" yaml:"text_foreground_color,omitempty"` +} + // TrustRequest: Trust another user's safety number using either the QR code data or the safety number text type TrustRequest struct { Request @@ -823,6 +928,14 @@ type TypingRequest struct { When int64 `json:"when,omitempty" yaml:"when,omitempty"` } +// UnbanUserRequest: Unbans users from a group. +type UnbanUserRequest struct { + Request + Account string `json:"account,omitempty" yaml:"account,omitempty"` // The account to interact with + GroupId string `json:"group_id,omitempty" yaml:"group_id,omitempty"` + Users []*JsonAddress `json:"users,omitempty" yaml:"users,omitempty"` // List of users to unban +} + // UnsubscribeRequest: See subscribe for more info type UnsubscribeRequest struct { Request From 12874dd6dcdff2e667383601016ec85ff6f50b34 Mon Sep 17 00:00:00 2001 From: finn Date: Mon, 27 Jun 2022 10:00:11 -0700 Subject: [PATCH 61/73] fix migrations, closes !10 --- cmd/signaldctl/cmd/db/migrate.go | 7 ++++--- cmd/signaldctl/cmd/db/postgres.go | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 5bac893..7d92f2d 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -32,6 +32,7 @@ var ( {InstalledRank: 3, Version: "13", Description: "recipient registration status", Script: "V13__recipient_registration_status.sql", Checksum: 405376321}, {InstalledRank: 4, Version: "14", Description: "multiple identity keys per account", Script: "V14__multiple_identity_keys_per_account.sql", Checksum: -1635788950}, {InstalledRank: 5, Version: "15", Description: "profiles tables", Script: "V15__profiles_tables.sql", Checksum: 809686180}, + {InstalledRank: 6, Version: "16", Description: "destination uuid in envelope", Script: "V16__destination_uuid_in_envelope.sql", Checksum: -566452172}, } sqlitePath string @@ -269,7 +270,7 @@ func moveAccounts(source *sql.DB, dest *sql.DB) error { if err != nil { return err } - _, err = dest.Exec("INSERT INTO signald_accounts (uuid, e164, server) VALUES ($1, $2, $3, $4)", accountUUID, e164, server) + _, err = dest.Exec("INSERT INTO signald_accounts (uuid, e164, server) VALUES ($1, $2, $3)", accountUUID, e164, server) if err != nil { return err } @@ -668,7 +669,7 @@ func moveProfileKeys(source *sql.DB, dest *sql.DB) error { _, err = dest.Exec(` INSERT INTO signald_profile_keys (account_uuid, recipient, profile_key, profile_key_credential, request_pending, unidentified_access_mode) - VALUES ($1, $2, $3, $4, $5, $6, $7) + VALUES ($1, $2, $3, $4, $5, $6) `, accountUUID, recipient, profile_key, profile_key_credential, request_pending, unidentified_access_mode) if err != nil { return err @@ -694,7 +695,7 @@ func moveProfiles(source *sql.DB, dest *sql.DB) error { about string emoji string payment_address []byte - badges string + badges sql.NullString ) err = rows.Scan(&accountUUID, &recipient, &last_update, &given_name, &family_name, &about, &emoji, &payment_address, &badges) if err != nil { diff --git a/cmd/signaldctl/cmd/db/postgres.go b/cmd/signaldctl/cmd/db/postgres.go index 5f7c2b3..abc8594 100644 --- a/cmd/signaldctl/cmd/db/postgres.go +++ b/cmd/signaldctl/cmd/db/postgres.go @@ -29,7 +29,8 @@ var ( legacy_message BYTEA, server_received_timestamp BIGINT, server_delivered_timestamp BIGINT, - server_uuid UUID + server_uuid UUID, + destination_uuid TEXT ); CREATE TABLE signald_servers ( From 9928e5ffc2caafb41fe8d81f531e70076f0b35a5 Mon Sep 17 00:00:00 2001 From: finn Date: Mon, 27 Jun 2022 11:46:58 -0700 Subject: [PATCH 62/73] fix migration 16 checksum --- cmd/signaldctl/cmd/db/migrate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 7d92f2d..122e499 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -32,7 +32,7 @@ var ( {InstalledRank: 3, Version: "13", Description: "recipient registration status", Script: "V13__recipient_registration_status.sql", Checksum: 405376321}, {InstalledRank: 4, Version: "14", Description: "multiple identity keys per account", Script: "V14__multiple_identity_keys_per_account.sql", Checksum: -1635788950}, {InstalledRank: 5, Version: "15", Description: "profiles tables", Script: "V15__profiles_tables.sql", Checksum: 809686180}, - {InstalledRank: 6, Version: "16", Description: "destination uuid in envelope", Script: "V16__destination_uuid_in_envelope.sql", Checksum: -566452172}, + {InstalledRank: 6, Version: "16", Description: "destination uuid in envelope", Script: "V16__destination_uuid_in_envelope.sql", Checksum: 357656854}, } sqlitePath string From dd208b6e82003235c68db13ee3e13995a3462abe Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 11 Aug 2022 10:44:37 -0400 Subject: [PATCH 63/73] Exclude missing accounts from contact migration Otherwise, moveContacts fails with a foreign key constraint violation. --- cmd/signaldctl/cmd/db/migrate.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 122e499..86858e9 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -614,7 +614,9 @@ func moveGroupCredentials(source *sql.DB, dest *sql.DB) error { } func moveContacts(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, recipient, name, color, profile_key, message_expiration_time, inbox_position FROM contacts") + rows, err := source.Query("SELECT account_uuid, recipient, name, color, profile_key, message_expiration_time, inbox_position FROM contacts" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + ) if err != nil { return err } From 67a7576e24cb9b7a0854d6699207786b104ac6c2 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 11 Aug 2022 10:42:43 -0400 Subject: [PATCH 64/73] Refactor migration functions & messages Ensure that success & error migration logs use the same description text for the migration step that they refer to. --- cmd/signaldctl/cmd/db/migrate.go | 102 ++++++++----------------------- 1 file changed, 26 insertions(+), 76 deletions(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 86858e9..197a651 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -60,7 +60,7 @@ var ( } return nil }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) (err error) { source, err := sql.Open("sqlite3", sqlitePath) if err != nil { return err @@ -93,83 +93,33 @@ var ( } log.Println("created schema") - if err := moveAccounts(source, dest); err != nil { - log.Println("error moving accounts table") - return err + migrate := func(fn func(*sql.DB, *sql.DB) error, targetName string) { + if err = fn(source, dest); err != nil { + log.Println("error moving", targetName) + panic(err) + } + log.Println("moved", targetName) } - log.Println("moved accounts table") + defer func() { + if r := recover(); r != nil && r != err { + // If r is something other than the error returned via the named return, re-panic it + panic(r) + } + }() - if err := moveRecipients(source, dest); err != nil { - log.Println("error moving recipients table") - return err - } - log.Println("moved recipients table") - - if err := movePrekeys(source, dest); err != nil { - log.Println("error moving prekeys table") - return err - } - log.Println("moved prekeys table") - - if err := moveSessions(source, dest); err != nil { - log.Println("error moving sessions table") - return err - } - log.Println("moved sessions table") - - if err := moveSignedPrekeys(source, dest); err != nil { - log.Println("error moving signed prekeys table") - return err - } - log.Println("moved signed prekeys table") - - if err := moveIdentityKeys(source, dest); err != nil { - log.Println("error moving identity keys table") - return err - } - log.Println("moved identity keys table") - - if err := moveAccountData(source, dest); err != nil { - log.Println("error moving account data") - return err - } - log.Println("moved account data table") - - if err := movePendingAccountData(source, dest); err != nil { - log.Println("error moving pending account data tabe") - return err - } - log.Println("moved pending account data table") - - if err := moveSenderKeys(source, dest); err != nil { - log.Println("error moving sender keys table") - return err - } - log.Println("moved sender keys table") - - if err := moveSenderKeyShared(source, dest); err != nil { - log.Println("error moving sender key shared table") - return err - } - log.Println("moved sender key shared table") - - if err := moveGroups(source, dest); err != nil { - log.Println("error moving groups table") - return err - } - log.Println("moved groups table") - - if err := moveGroupCredentials(source, dest); err != nil { - log.Println("error moving group credentials table") - return err - } - log.Println("moved group credentials table") - - if err := moveContacts(source, dest); err != nil { - log.Println("error moving group credentials table") - return err - } - log.Println("moved contacts table") + migrate(moveAccounts, "accounts table") + migrate(moveRecipients, "recipients table") + migrate(movePrekeys, "prekeys table") + migrate(moveSessions, "sessions table") + migrate(moveSignedPrekeys, "signed prekeys table") + migrate(moveIdentityKeys, "identity keys table") + migrate(moveAccountData, "account data") + migrate(movePendingAccountData, "pending account data table") + migrate(moveSenderKeys, "sender keys table") + migrate(moveSenderKeyShared, "sender key shared table") + migrate(moveGroups, "groups table") + migrate(moveGroupCredentials, "group credentials table") + migrate(moveContacts, "contacts table") if err := moveProfileKeys(source, dest); err != nil { log.Println("error moving profile keys table") From 4178a582e898898ccd7934391a21edb176490219 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Mon, 15 Aug 2022 17:36:01 -0400 Subject: [PATCH 65/73] Refactor more migration functions --- cmd/signaldctl/cmd/db/migrate.go | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 197a651..56e54e9 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -120,30 +120,10 @@ var ( migrate(moveGroups, "groups table") migrate(moveGroupCredentials, "group credentials table") migrate(moveContacts, "contacts table") - - if err := moveProfileKeys(source, dest); err != nil { - log.Println("error moving profile keys table") - return err - } - log.Println("moved profile keys table") - - if err := moveProfiles(source, dest); err != nil { - log.Println("error moving profiles tables") - return err - } - log.Println("moved profiles tables") - - if err := moveProfileCapabilities(source, dest); err != nil { - log.Println("error moving profile capabilities tables") - return err - } - log.Println("moved profile capabilities tables") - - if err := moveProfileBadges(source, dest); err != nil { - log.Println("error moving profile badges tables") - return err - } - log.Println("moved profile badges tables") + migrate(moveProfileKeys, "profile keys table") + migrate(moveProfiles, "profiles tables") + migrate(moveProfileCapabilities, "profile capabilities tables") + migrate(moveProfileBadges, "profile badges tables") if err := os.Remove(sqlitePath); err != nil { log.Println("error deleting sqlite file") From 5e52e5f3bd8e062a6bc6fcf3618144a6023b1915 Mon Sep 17 00:00:00 2001 From: finn Date: Tue, 16 Aug 2022 09:32:36 -0700 Subject: [PATCH 66/73] deb build cut first character (v) out of version string --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b9cba03..14a803a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -71,7 +71,7 @@ test sqlite to postgres: - go run ./cmd/signaldctl doc -o man - go run ./cmd/signaldctl completion bash > debian/package.bash-completion - ls *.1 > debian/manpages - - gbp dch --ignore-branch --debian-tag="%(version)s" --git-author --new-version="$(./version.sh)" + - gbp dch --ignore-branch --debian-tag="%(version)s" --git-author --new-version="$(./version.sh | cut -c2-)" - dpkg-buildpackage -us -uc -b - mv ../*.deb . needs: From 1ced8e01b2a462ca0e137fa3598c715593d38943 Mon Sep 17 00:00:00 2001 From: finn Date: Tue, 16 Aug 2022 09:57:10 -0700 Subject: [PATCH 67/73] bump protocol --- protocol.json | 5525 +------------------------ signald/client-protocol/v1/errors.go | 16 + signald/client-protocol/v1/structs.go | 6 +- 3 files changed, 21 insertions(+), 5526 deletions(-) diff --git a/protocol.json b/protocol.json index edf0bcb..807e84d 100644 --- a/protocol.json +++ b/protocol.json @@ -1,5524 +1 @@ -{ - "doc_version": "v1", - "version": { - "name": "signald", - "version": "0.18.3", - "branch": "main", - "commit": "2d898baa70a20c44ae918d2ce6e1f108579ee271" - }, - "info": "This document describes objects that may be used when communicating with signald.", - "types": { - "v0": { - "JsonAccountList": { - "fields": { - "accounts": { - "list": true, - "type": "JsonAccount", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonMessageEnvelope": { - "fields": { - "username": { - "type": "String", - "example": "\"+12024561414\"" - }, - "uuid": { - "type": "String", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"" - }, - "source": { - "type": "JsonAddress", - "version": "v0" - }, - "sourceDevice": { - "type": "int" - }, - "type": { - "type": "String" - }, - "relay": { - "type": "String", - "doc": "this field is no longer available and will never be populated" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - }, - "timestampISO": { - "type": "String" - }, - "serverTimestamp": { - "type": "long" - }, - "serverDeliveredTimestamp": { - "type": "long", - "example": "161557644247580" - }, - "hasLegacyMessage": { - "type": "boolean" - }, - "hasContent": { - "type": "boolean" - }, - "isUnidentifiedSender": { - "type": "boolean" - }, - "dataMessage": { - "type": "JsonDataMessage", - "version": "v0" - }, - "syncMessage": { - "type": "JsonSyncMessage", - "version": "v0" - }, - "callMessage": { - "type": "JsonCallMessage", - "version": "v0" - }, - "receipt": { - "type": "JsonReceiptMessage", - "version": "v0" - }, - "typing": { - "type": "JsonTypingMessage", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonAccount": { - "fields": { - "deviceId": { - "type": "int" - }, - "username": { - "type": "String" - }, - "filename": { - "type": "String" - }, - "uuid": { - "type": "String" - }, - "registered": { - "type": "boolean" - }, - "has_keys": { - "type": "boolean" - }, - "subscribed": { - "type": "boolean" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonAddress": { - "fields": { - "number": { - "type": "String" - }, - "uuid": { - "type": "UUID" - }, - "relay": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonDataMessage": { - "fields": { - "timestamp": { - "type": "long", - "doc": "the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes.", - "example": "1615576442475" - }, - "attachments": { - "list": true, - "type": "JsonAttachment", - "version": "v0", - "doc": "files attached to the incoming message" - }, - "body": { - "type": "String", - "doc": "the text body of the incoming message.", - "example": "\"hello\"" - }, - "group": { - "type": "JsonGroupInfo", - "version": "v0", - "doc": "if the incoming message was sent to a v1 group, information about that group will be here" - }, - "groupV2": { - "type": "JsonGroupV2Info", - "version": "v0", - "doc": "is the incoming message was sent to a v2 group, basic identifying information about that group will be here. For full information, use list_groups" - }, - "endSession": { - "type": "boolean" - }, - "expiresInSeconds": { - "type": "int", - "doc": "the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds" - }, - "profileKeyUpdate": { - "type": "boolean" - }, - "quote": { - "type": "JsonQuote", - "version": "v0", - "doc": "if the incoming message is a quote or reply to another message, this will contain information about that message" - }, - "contacts": { - "list": true, - "type": "SharedContact", - "version": "v0", - "doc": "if the incoming message has a shared contact, the contact's information will be here" - }, - "previews": { - "list": true, - "type": "JsonPreview", - "version": "v0", - "doc": "if the incoming message has a link preview, information about that preview will be here" - }, - "sticker": { - "type": "JsonSticker", - "version": "v0", - "doc": "if the incoming message is a sticker, information about the sicker will be here" - }, - "viewOnce": { - "type": "boolean", - "doc": "indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image." - }, - "reaction": { - "type": "JsonReaction", - "version": "v0", - "doc": "if the message adds or removes a reaction to another message, this will indicate what change is being made" - }, - "remoteDelete": { - "type": "RemoteDelete", - "version": "v0", - "doc": "if the inbound message is deleting a previously sent message, indicates which message should be deleted" - }, - "mentions": { - "list": true, - "type": "JsonMention", - "version": "v0", - "doc": "list of mentions in the message" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonSyncMessage": { - "fields": { - "sent": { - "type": "JsonSentTranscriptMessage", - "version": "v0" - }, - "contacts": { - "type": "JsonAttachment", - "version": "v0" - }, - "contactsComplete": { - "type": "boolean" - }, - "groups": { - "type": "JsonAttachment", - "version": "v0" - }, - "blockedList": { - "type": "JsonBlockedListMessage", - "version": "v0" - }, - "request": { - "type": "String" - }, - "readMessages": { - "list": true, - "type": "JsonReadMessage", - "version": "v0" - }, - "viewOnceOpen": { - "type": "JsonViewOnceOpenMessage", - "version": "v0" - }, - "verified": { - "type": "JsonVerifiedMessage", - "version": "v0" - }, - "configuration": { - "type": "ConfigurationMessage", - "version": "v0" - }, - "stickerPackOperations": { - "list": true, - "type": "JsonStickerPackOperationMessage", - "version": "v0" - }, - "fetchType": { - "type": "String" - }, - "messageRequestResponse": { - "type": "JsonMessageRequestResponseMessage", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonCallMessage": { - "fields": { - "offerMessage": { - "type": "OfferMessage", - "version": "v0" - }, - "answerMessage": { - "type": "AnswerMessage", - "version": "v0" - }, - "busyMessage": { - "type": "BusyMessage", - "version": "v0" - }, - "hangupMessage": { - "type": "HangupMessage", - "version": "v0" - }, - "iceUpdateMessages": { - "list": true, - "type": "IceUpdateMessage", - "version": "v0" - }, - "destinationDeviceId": { - "type": "int" - }, - "isMultiRing": { - "type": "boolean" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonReceiptMessage": { - "fields": { - "type": { - "type": "String" - }, - "timestamps": { - "list": true, - "type": "Long" - }, - "when": { - "type": "long" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonTypingMessage": { - "fields": { - "action": { - "type": "String" - }, - "timestamp": { - "type": "long" - }, - "groupId": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonAttachment": { - "fields": { - "contentType": { - "type": "String" - }, - "id": { - "type": "String" - }, - "size": { - "type": "int" - }, - "storedFilename": { - "type": "String" - }, - "filename": { - "type": "String" - }, - "customFilename": { - "type": "String" - }, - "caption": { - "type": "String" - }, - "width": { - "type": "int" - }, - "height": { - "type": "int" - }, - "voiceNote": { - "type": "boolean" - }, - "key": { - "type": "String" - }, - "digest": { - "type": "String" - }, - "blurhash": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonGroupInfo": { - "fields": { - "groupId": { - "type": "String" - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v0" - }, - "name": { - "type": "String" - }, - "type": { - "type": "String" - }, - "avatarId": { - "type": "long" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonGroupV2Info": { - "fields": { - "id": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "revision": { - "type": "int", - "example": "5" - }, - "title": { - "type": "String", - "example": "\"Parkdale Run Club\"" - }, - "description": { - "type": "String" - }, - "avatar": { - "type": "String", - "doc": "path to the group's avatar on local disk, if available", - "example": "\"/var/lib/signald/avatars/group-EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "timer": { - "type": "int", - "example": "604800" - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v0" - }, - "pendingMembers": { - "list": true, - "type": "JsonAddress", - "version": "v0" - }, - "requestingMembers": { - "list": true, - "type": "JsonAddress", - "version": "v0" - }, - "inviteLink": { - "type": "String", - "doc": "the signal.group link, if applicable" - }, - "accessControl": { - "type": "GroupAccessControl", - "version": "v0", - "doc": "current access control settings for this group" - }, - "memberDetail": { - "list": true, - "type": "GroupMember", - "version": "v0", - "doc": "detailed member list" - }, - "pendingMemberDetail": { - "list": true, - "type": "GroupMember", - "version": "v0", - "doc": "detailed pending member list" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonQuote": { - "fields": { - "id": { - "type": "long", - "doc": "the client timestamp of the message being quoted", - "example": "1615576442475" - }, - "author": { - "type": "JsonAddress", - "version": "v0", - "doc": "the author of the message being quoted" - }, - "text": { - "type": "String", - "doc": "the body of the message being quoted", - "example": "\"hey  what's up?\"" - }, - "attachments": { - "list": true, - "type": "JsonQuotedAttachment", - "version": "v0", - "doc": "list of files attached to the quoted message" - }, - "mentions": { - "list": true, - "type": "JsonMention", - "version": "v0", - "doc": "list of mentions in the quoted message" - } - }, - "doc": "A quote is a reply to a previous message. ID is the sent time of the message being replied to", - "deprecated": true, - "removal_date": 1641027661 - }, - "SharedContact": { - "fields": { - "name": { - "type": "Name", - "version": "v0" - }, - "avatar": { - "type": "Optional", - "version": "v0" - }, - "phone": { - "type": "Optional", - "version": "v0" - }, - "email": { - "type": "Optional", - "version": "v0" - }, - "address": { - "type": "Optional", - "version": "v0" - }, - "organization": { - "type": "Optional", - "version": "v0" - } - } - }, - "JsonPreview": { - "fields": { - "url": { - "type": "String" - }, - "title": { - "type": "String" - }, - "attachment": { - "type": "JsonAttachment", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonSticker": { - "fields": { - "packID": { - "type": "String" - }, - "packKey": { - "type": "String" - }, - "stickerID": { - "type": "int" - }, - "attachment": { - "type": "JsonAttachment", - "version": "v0" - }, - "image": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonReaction": { - "fields": { - "emoji": { - "type": "String", - "doc": "the emoji to react with", - "example": "\"👍\"" - }, - "remove": { - "type": "boolean", - "doc": "set to true to remove the reaction. requires emoji be set to previously reacted emoji" - }, - "targetAuthor": { - "type": "JsonAddress", - "version": "v0", - "doc": "the author of the message being reacted to" - }, - "targetSentTimestamp": { - "type": "long", - "doc": "the client timestamp of the message being reacted to", - "example": "1615576442475" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "RemoteDelete": { - "fields": { - "targetSentTimestamp": { - "type": "long" - } - } - }, - "JsonMention": { - "fields": { - "uuid": { - "type": "String", - "doc": "The UUID of the account being mentioned", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" - }, - "start": { - "type": "int", - "doc": "The number of characters in that the mention starts at. Note that due to a quirk of how signald encodes JSON, if this value is 0 (for example if the first character in the message is the mention) the field won't show up.", - "example": "4" - }, - "length": { - "type": "int", - "doc": "The length of the mention represented in the message. Seems to always be 1 but included here in case that changes.", - "example": "1" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonSentTranscriptMessage": { - "fields": { - "destination": { - "type": "JsonAddress", - "version": "v0" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - }, - "expirationStartTimestamp": { - "type": "long" - }, - "message": { - "type": "JsonDataMessage", - "version": "v0" - }, - "unidentifiedStatus": { - "type": "Map" - }, - "isRecipientUpdate": { - "type": "boolean" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonBlockedListMessage": { - "fields": { - "addresses": { - "list": true, - "type": "JsonAddress", - "version": "v0" - }, - "groupIds": { - "list": true, - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonReadMessage": { - "fields": { - "sender": { - "type": "JsonAddress", - "version": "v0" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonViewOnceOpenMessage": { - "fields": { - "sender": { - "type": "JsonAddress", - "version": "v0" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonVerifiedMessage": { - "fields": { - "destination": { - "type": "JsonAddress", - "version": "v0" - }, - "identityKey": { - "type": "String" - }, - "verified": { - "type": "String" - }, - "timestamp": { - "type": "long" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "ConfigurationMessage": { - "fields": { - "readReceipts": { - "type": "Optional", - "version": "v0" - }, - "unidentifiedDeliveryIndicators": { - "type": "Optional", - "version": "v0" - }, - "typingIndicators": { - "type": "Optional", - "version": "v0" - }, - "linkPreviews": { - "type": "Optional", - "version": "v0" - } - } - }, - "JsonStickerPackOperationMessage": { - "fields": { - "packID": { - "type": "String" - }, - "packKey": { - "type": "String" - }, - "type": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "JsonMessageRequestResponseMessage": { - "fields": { - "person": { - "type": "JsonAddress", - "version": "v0" - }, - "groupId": { - "type": "String" - }, - "type": { - "type": "String" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "OfferMessage": { - "fields": { - "id": { - "type": "long" - }, - "sdp": { - "type": "String" - }, - "type": { - "type": "Type", - "version": "v0" - }, - "opaque": { - "type": "String" - } - } - }, - "AnswerMessage": { - "fields": { - "id": { - "type": "long" - }, - "sdp": { - "type": "String" - }, - "opaque": { - "type": "String" - } - } - }, - "BusyMessage": { - "fields": { - "id": { - "type": "long" - } - } - }, - "HangupMessage": { - "fields": { - "id": { - "type": "long" - }, - "type": { - "type": "Type", - "version": "v0" - }, - "deviceId": { - "type": "int" - }, - "legacy": { - "type": "boolean" - } - } - }, - "IceUpdateMessage": { - "fields": { - "id": { - "type": "long" - }, - "opaque": { - "type": "String" - }, - "sdp": { - "type": "String" - } - } - }, - "JsonQuotedAttachment": { - "fields": { - "contentType": { - "type": "String" - }, - "fileName": { - "type": "String" - }, - "thumbnail": { - "type": "JsonAttachment", - "version": "v0" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "GroupAccessControl": { - "fields": { - "link": { - "type": "String", - "doc": "UNSATISFIABLE when the group link is disabled, ADMINISTRATOR when the group link is enabled but an administrator must approve new members, ANY when the group link is enabled and no approval is required", - "example": "\"ANY\"" - }, - "attributes": { - "type": "String", - "doc": "who can edit group info" - }, - "members": { - "type": "String", - "doc": "who can add members" - } - }, - "doc": "group access control settings. Options for each controlled action are: UNKNOWN, ANY, MEMBER, ADMINISTRATOR, UNSATISFIABLE and UNRECOGNIZED", - "deprecated": true, - "removal_date": 1641027661 - }, - "GroupMember": { - "fields": { - "uuid": { - "type": "String", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" - }, - "role": { - "type": "String", - "doc": "possible values are: UNKNOWN, DEFAULT, ADMINISTRATOR and UNRECOGNIZED", - "example": "\"DEFAULT\"" - }, - "joined_revision": { - "type": "int" - } - }, - "deprecated": true, - "removal_date": 1641027661 - }, - "Name": { - "fields": { - "display": { - "type": "Optional", - "version": "v0" - }, - "given": { - "type": "Optional", - "version": "v0" - }, - "family": { - "type": "Optional", - "version": "v0" - }, - "prefix": { - "type": "Optional", - "version": "v0" - }, - "suffix": { - "type": "Optional", - "version": "v0" - }, - "middle": { - "type": "Optional", - "version": "v0" - } - } - }, - "Optional": { - "fields": { - "empty": { - "type": "boolean" - }, - "present": { - "type": "boolean" - } - } - }, - "Type": { - "fields": {} - } - }, - "v1": { - "ClientMessageWrapper": { - "fields": { - "type": { - "type": "String", - "doc": "the type of object to expect in the `data` field" - }, - "version": { - "type": "String", - "doc": "the version of the object in the `data` field" - }, - "data": { - "type": "Object", - "doc": "the incoming object. The structure will vary from message to message, see `type` and `version` fields" - }, - "error": { - "type": "Boolean", - "doc": "true if the incoming message represents an error" - }, - "account": { - "type": "String", - "doc": "the account this message is from" - } - }, - "doc": "Wraps all incoming messages sent to the client after a v1 subscribe request is issued" - }, - "ProtocolInvalidMessageError": { - "fields": { - "sender": { - "type": "String" - }, - "timestamp": { - "type": "long" - }, - "message": { - "type": "String" - }, - "sender_device": { - "type": "int" - }, - "content_hint": { - "type": "int" - }, - "group_id": { - "type": "String" - } - }, - "error": true - }, - "UntrustedIdentityError": { - "fields": { - "identifier": { - "type": "String" - }, - "message": { - "type": "String" - }, - "identity_key": { - "type": "IdentityKey", - "version": "v1" - } - }, - "error": true - }, - "ProtocolNoSessionError": { - "fields": { - "sender": { - "type": "String" - }, - "timestamp": { - "type": "long" - }, - "message": { - "type": "String" - }, - "sender_device": { - "type": "int" - }, - "content_hint": { - "type": "int" - }, - "group_id": { - "type": "String" - } - }, - "error": true - }, - "ProtocolInvalidKeyIdError": { - "fields": { - "sender": { - "type": "String" - }, - "timestamp": { - "type": "long" - }, - "message": { - "type": "String" - }, - "sender_device": { - "type": "int" - }, - "content_hint": { - "type": "int" - }, - "group_id": { - "type": "String" - } - }, - "error": true - }, - "DuplicateMessageError": { - "fields": { - "timestamp": { - "type": "long" - }, - "message": { - "type": "String" - } - }, - "error": true - }, - "IncomingMessage": { - "fields": { - "account": { - "type": "String", - "example": "\"+12024561414\"" - }, - "source": { - "type": "JsonAddress", - "version": "v1" - }, - "type": { - "type": "String" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - }, - "source_device": { - "type": "int" - }, - "server_receiver_timestamp": { - "type": "long", - "example": "1615576442475" - }, - "server_deliver_timestamp": { - "type": "long", - "example": "1615576442475" - }, - "has_legacy_message": { - "type": "boolean" - }, - "has_content": { - "type": "boolean" - }, - "unidentified_sender": { - "type": "boolean" - }, - "data_message": { - "type": "JsonDataMessage", - "version": "v1" - }, - "sync_message": { - "type": "JsonSyncMessage", - "version": "v1" - }, - "call_message": { - "type": "CallMessage", - "version": "v1" - }, - "receipt_message": { - "type": "ReceiptMessage", - "version": "v1" - }, - "typing_message": { - "type": "TypingMessage", - "version": "v1" - }, - "story_message": { - "type": "StoryMessage", - "version": "v1" - }, - "server_guid": { - "type": "String" - } - } - }, - "ListenerState": { - "fields": { - "connected": { - "type": "boolean" - } - }, - "doc": "prior attempt to indicate signald connectivity state. WebSocketConnectionState messages will be delivered at the same time as well as in other parts of the websocket lifecycle." - }, - "WebSocketConnectionState": { - "fields": { - "state": { - "type": "String", - "doc": "One of: DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, DISCONNECTING, AUTHENTICATION_FAILED, FAILED" - }, - "socket": { - "type": "String", - "doc": "One of: UNIDENTIFIED, IDENTIFIED" - } - }, - "doc": "indicates when the websocket connection state to the signal server has changed" - }, - "StorageChange": { - "fields": { - "version": { - "type": "long", - "doc": "Seems to behave like the group version numbers and increments every time the state changes" - } - }, - "doc": "Broadcast to subscribed clients when there is a state change from the storage service" - }, - "SendRequest": { - "fields": { - "username": { - "type": "String", - "example": "\"+12024561414\"" - }, - "account": { - "type": "String", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"" - }, - "recipientAddress": { - "type": "JsonAddress", - "version": "v1" - }, - "recipientGroupId": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "messageBody": { - "type": "String", - "example": "\"hello\"" - }, - "attachments": { - "list": true, - "type": "JsonAttachment", - "version": "v1" - }, - "quote": { - "type": "JsonQuote", - "version": "v1" - }, - "timestamp": { - "type": "Long" - }, - "mentions": { - "list": true, - "type": "JsonMention", - "version": "v1" - }, - "previews": { - "list": true, - "type": "JsonPreview", - "version": "v1" - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v1", - "doc": "Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified" - } - } - }, - "SendResponse": { - "fields": { - "results": { - "list": true, - "type": "JsonSendMessageResult", - "version": "v1" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - } - } - }, - "NoSuchAccountError": { - "fields": { - "account": { - "type": "String" - }, - "message": { - "type": "String" - } - }, - "error": true - }, - "ServerNotFoundError": { - "fields": { - "uuid": { - "type": "String" - }, - "message": { - "type": "String" - } - }, - "error": true - }, - "InvalidProxyError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "NoSendPermissionError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "InvalidAttachmentError": { - "fields": { - "filename": { - "type": "String" - }, - "message": { - "type": "String" - } - }, - "error": true - }, - "InternalError": { - "fields": { - "exceptions": { - "list": true, - "type": "String" - }, - "message": { - "type": "String" - } - }, - "doc": "an internal error in signald has occurred. typically these are things that \"should never happen\" such as issues saving to the local disk, but it is also the default error type and may catch some things that should have their own error type. If you find tht your code is depending on the exception list for any particular behavior, please file an issue so we can pull those errors out to a separate error type: https://gitlab.com/signald/signald/-/issues/new", - "error": true - }, - "InvalidRequestError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "UnknownGroupError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "RateLimitError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "InvalidRecipientError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "AttachmentTooLargeError": { - "fields": { - "filename": { - "type": "String" - }, - "message": { - "type": "String" - } - }, - "error": true - }, - "AuthorizationFailedError": { - "fields": { - "message": { - "type": "String" - } - }, - "doc": "Indicates the server rejected our credentials or a failed group update. Typically means the linked device was removed by the primary device, or that the account was re-registered. For group updates, this can indicate that we lack permissions.", - "error": true - }, - "SQLError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "ReactRequest": { - "fields": { - "username": { - "type": "String", - "example": "\"+12024561414\"", - "required": true - }, - "recipientAddress": { - "type": "JsonAddress", - "version": "v1" - }, - "recipientGroupId": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "reaction": { - "type": "JsonReaction", - "version": "v1", - "required": true - }, - "timestamp": { - "type": "long" - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v1", - "doc": "Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified" - } - }, - "doc": "react to a previous message" - }, - "UnregisteredUserError": { - "fields": { - "message": { - "type": "String" - }, - "e164_number": { - "type": "String" - } - }, - "error": true - }, - "VersionRequest": { - "fields": {} - }, - "JsonVersionMessage": { - "fields": { - "name": { - "type": "String", - "example": "\"signald\"" - }, - "version": { - "type": "String", - "example": "\"0.18.3\"" - }, - "branch": { - "type": "String", - "example": "\"main\"" - }, - "commit": { - "type": "String", - "example": "\"2d898baa70a20c44ae918d2ce6e1f108579ee271\"" - } - } - }, - "AcceptInvitationRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "groupID": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", - "required": true - } - }, - "doc": "Accept a v2 group invitation. Note that you must have a profile name set to join groups." - }, - "JsonGroupV2Info": { - "fields": { - "id": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "revision": { - "type": "int", - "example": "5" - }, - "title": { - "type": "String", - "example": "\"Parkdale Run Club\"" - }, - "description": { - "type": "String" - }, - "avatar": { - "type": "String", - "doc": "path to the group's avatar on local disk, if available", - "example": "\"/var/lib/signald/avatars/group-EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "timer": { - "type": "int", - "example": "604800" - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v1" - }, - "pendingMembers": { - "list": true, - "type": "JsonAddress", - "version": "v1" - }, - "requestingMembers": { - "list": true, - "type": "JsonAddress", - "version": "v1" - }, - "inviteLink": { - "type": "String", - "doc": "the signal.group link, if applicable" - }, - "accessControl": { - "type": "GroupAccessControl", - "version": "v1", - "doc": "current access control settings for this group" - }, - "memberDetail": { - "list": true, - "type": "GroupMember", - "version": "v1", - "doc": "detailed member list" - }, - "pendingMemberDetail": { - "list": true, - "type": "GroupMember", - "version": "v1", - "doc": "detailed pending member list" - }, - "announcements": { - "type": "String", - "doc": "indicates if the group is an announcements group. Only admins are allowed to send messages to announcements groups. Options are UNKNOWN, ENABLED or DISABLED" - }, - "removed": { - "type": "boolean", - "doc": "will be set to true for incoming messages to indicate the user has been removed from the group" - }, - "banned_members": { - "list": true, - "type": "BannedGroupMember", - "version": "v1" - }, - "group_change": { - "type": "GroupChange", - "version": "v1", - "doc": "Represents a peer-to-peer group change done by a user. Will not be set if the group change signature fails verification. This is usually only set inside of incoming messages." - } - }, - "doc": "Information about a Signal group" - }, - "OwnProfileKeyDoesNotExistError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "GroupPatchNotAcceptedError": { - "fields": { - "message": { - "type": "String" - } - }, - "doc": "Indicates the server rejected our group update. This can be due to errors such as trying to add a user that's already in the group.", - "error": true - }, - "ApproveMembershipRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "groupID": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", - "required": true - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v1", - "doc": "list of requesting members to approve", - "required": true - } - }, - "doc": "approve a request to join a group" - }, - "GroupVerificationError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "GetGroupRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "groupID": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", - "required": true - }, - "revision": { - "type": "int", - "doc": "the latest known revision, default value (-1) forces fetch from server" - } - }, - "doc": "Query the server for the latest state of a known group. If the account is not a member of the group, an UnknownGroupError is returned." - }, - "InvalidGroupStateError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "GetLinkedDevicesRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - } - }, - "doc": "list all linked devices on a Signal account" - }, - "LinkedDevices": { - "fields": { - "devices": { - "list": true, - "type": "DeviceInfo", - "version": "v1" - } - } - }, - "JoinGroupRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "uri": { - "type": "String", - "doc": "The signal.group URL", - "example": "\"https://signal.group/#CjQKINH_GZhXhfifTcnBkaKTNRxW-hHKnGSq-cJNyPVqHRp8EhDUB7zjKNEl0NaULhsqJCX3\"", - "required": true - } - }, - "doc": "Join a group using the a signal.group URL. Note that you must have a profile name set to join groups." - }, - "JsonGroupJoinInfo": { - "fields": { - "groupID": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "title": { - "type": "String", - "example": "\"Parkdale Run Club\"" - }, - "description": { - "type": "String", - "example": "\"A club for running in Parkdale\"" - }, - "memberCount": { - "type": "int", - "example": "3" - }, - "addFromInviteLink": { - "type": "int", - "doc": "The access level required in order to join the group from the invite link, as an AccessControl.AccessRequired enum from the upstream Signal groups.proto file. This is UNSATISFIABLE (4) when the group link is disabled; ADMINISTRATOR (3) when the group link is enabled, but an administrator must approve new members; and ANY (1) when the group link is enabled and no approval is required. See theGroupAccessControl structure and the upstream enum ordinals." - }, - "revision": { - "type": "int", - "doc": "The Group V2 revision. This is incremented by clients whenever they update group information, and it is often used by clients to determine if the local group state is out-of-date with the server's revision.", - "example": "5" - }, - "pendingAdminApproval": { - "type": "boolean", - "doc": "Whether the account is waiting for admin approval in order to be added to the group." - } - } - }, - "InvalidInviteURIError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "GroupNotActiveError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "RemoveLinkedDeviceRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"+12024561414\"", - "required": true - }, - "deviceId": { - "type": "long", - "doc": "the ID of the device to unlink", - "example": "3", - "required": true - } - }, - "doc": "Remove a linked device from the Signal account. Only allowed when the local device id is 1" - }, - "UpdateGroupRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The identifier of the account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "groupID": { - "type": "String", - "doc": "the ID of the group to update", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", - "required": true - }, - "title": { - "type": "String", - "example": "\"Parkdale Run Club\"" - }, - "description": { - "type": "String", - "doc": "A new group description. Set to empty string to remove an existing description.", - "example": "\"A club for running in Parkdale\"" - }, - "avatar": { - "type": "String", - "example": "\"/tmp/image.jpg\"" - }, - "updateTimer": { - "type": "int", - "doc": "update the group timer." - }, - "addMembers": { - "list": true, - "type": "JsonAddress", - "version": "v1" - }, - "removeMembers": { - "list": true, - "type": "JsonAddress", - "version": "v1" - }, - "updateRole": { - "type": "GroupMember", - "version": "v1" - }, - "updateAccessControl": { - "type": "GroupAccessControl", - "version": "v1", - "doc": "note that only one of the access controls may be updated per request" - }, - "resetLink": { - "type": "boolean", - "doc": "regenerate the group link password, invalidating the old one" - }, - "announcements": { - "type": "String", - "doc": "ENABLED to only allow admins to post messages, DISABLED to allow anyone to post" - } - }, - "doc": "modify a group. Note that only one modification action may be performed at once" - }, - "GroupInfo": { - "fields": { - "v1": { - "type": "JsonGroupInfo", - "version": "v1" - }, - "v2": { - "type": "JsonGroupV2Info", - "version": "v1" - } - }, - "doc": "A generic type that is used when the group version is not known" - }, - "SetProfile": { - "fields": { - "account": { - "type": "String", - "doc": "The phone number of the account to use", - "example": "\"+12024561414\"", - "required": true - }, - "name": { - "type": "String", - "doc": "Change the profile name", - "example": "\"signald user\"" - }, - "avatarFile": { - "type": "String", - "doc": "Path to new profile avatar file. If unset or null, unset the profile avatar", - "example": "\"/tmp/image.jpg\"" - }, - "about": { - "type": "String", - "doc": "Change the 'about' profile field" - }, - "emoji": { - "type": "String", - "doc": "Change the profile emoji" - }, - "mobilecoin_address": { - "type": "String", - "doc": "Change the profile payment address. Payment address must be a *base64-encoded* MobileCoin address. Note that this is not the traditional MobileCoin address encoding, which is custom. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side." - }, - "visible_badge_ids": { - "list": true, - "type": "String", - "doc": "configure visible badge IDs" - } - } - }, - "InvalidBase64Error": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "ResolveAddressRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The signal account to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "partial": { - "type": "JsonAddress", - "version": "v1", - "doc": "The partial address, missing fields", - "required": true - } - }, - "doc": "Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns." - }, - "JsonAddress": { - "fields": { - "number": { - "type": "String", - "doc": "An e164 phone number, starting with +. Currently the only available user-facing Signal identifier.", - "example": "\"+13215551234\"" - }, - "uuid": { - "type": "UUID", - "doc": "A UUID, the unique identifier for a particular Signal account." - }, - "relay": { - "type": "String" - } - } - }, - "MarkReadRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "to": { - "type": "JsonAddress", - "version": "v1", - "doc": "The address that sent the message being marked as read", - "required": true - }, - "timestamps": { - "list": true, - "type": "Long", - "doc": "List of messages to mark as read", - "example": "1615576442475", - "required": true - }, - "when": { - "type": "Long" - } - } - }, - "GetProfileRequest": { - "fields": { - "account": { - "type": "String", - "doc": "the signald account to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "async": { - "type": "boolean", - "doc": "if true, return results from local store immediately, refreshing from server in the background if needed. if false (default), block until profile can be retrieved from server" - }, - "address": { - "type": "JsonAddress", - "version": "v1", - "doc": "the address to look up", - "required": true - } - }, - "doc": "Get all information available about a user" - }, - "Profile": { - "fields": { - "name": { - "type": "String", - "doc": "The user's name from local contact names if available, or if not in contact list their Signal profile name" - }, - "avatar": { - "type": "String", - "doc": "path to avatar on local disk" - }, - "address": { - "type": "JsonAddress", - "version": "v1" - }, - "capabilities": { - "type": "Capabilities", - "version": "v1" - }, - "color": { - "type": "String", - "doc": "color of the chat with this user" - }, - "about": { - "type": "String" - }, - "emoji": { - "type": "String" - }, - "profile_name": { - "type": "String", - "doc": "The user's Signal profile name" - }, - "inbox_position": { - "type": "Integer" - }, - "expiration_time": { - "type": "int" - }, - "mobilecoin_address": { - "type": "String", - "doc": "*base64-encoded* mobilecoin address. Note that this is not the traditional MobileCoin address encoding. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. If unset, null or an empty string, will empty the profile payment address" - }, - "visible_badge_ids": { - "list": true, - "type": "String", - "doc": "currently unclear how these work, as they are not available in the production Signal apps" - } - }, - "doc": "Information about a Signal user" - }, - "ProfileUnavailableError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "ListGroupsRequest": { - "fields": { - "account": { - "type": "String", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - } - } - }, - "GroupList": { - "fields": { - "groups": { - "list": true, - "type": "JsonGroupV2Info", - "version": "v1" - }, - "legacyGroups": { - "list": true, - "type": "JsonGroupInfo", - "version": "v1" - } - } - }, - "ListContactsRequest": { - "fields": { - "account": { - "type": "String", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "async": { - "type": "boolean", - "doc": "return results from local store immediately, refreshing from server afterward if needed. If false (default), block until all pending profiles have been retrieved." - } - } - }, - "ProfileList": { - "fields": { - "profiles": { - "list": true, - "type": "Profile", - "version": "v1" - } - } - }, - "CreateGroupRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "title": { - "type": "String", - "example": "\"Parkdale Run Club\"", - "required": true - }, - "avatar": { - "type": "String", - "example": "\"/tmp/image.jpg\"" - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v1", - "required": true - }, - "timer": { - "type": "int", - "doc": "the message expiration timer" - }, - "member_role": { - "type": "String", - "doc": "The role of all members other than the group creator. Options are ADMINISTRATOR or DEFAULT (case insensitive)", - "example": "\"ADMINISTRATOR\"" - } - } - }, - "NoKnownUUIDError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "LeaveGroupRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "groupID": { - "type": "String", - "doc": "The group to leave", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", - "required": true - } - } - }, - "GenerateLinkingURIRequest": { - "fields": { - "server": { - "type": "String", - "doc": "The identifier of the server to use. Leave blank for default (usually Signal production servers but configurable at build time)" - } - }, - "doc": "Generate a linking URI. Typically this is QR encoded and scanned by the primary device. Submit the returned session_id with a finish_link request." - }, - "LinkingURI": { - "fields": { - "uri": { - "type": "String" - }, - "session_id": { - "type": "String" - } - } - }, - "FinishLinkRequest": { - "fields": { - "overwrite": { - "type": "boolean", - "doc": "overwrite existing account data if the phone number conflicts. false by default" - }, - "device_name": { - "type": "String" - }, - "session_id": { - "type": "String" - } - }, - "doc": "After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device and the new account is setup. Note that the account setup process can sometimes take some time, if rapid userfeedback is required after scanning, use wait_for_scan first, then finish setup with finish_link." - }, - "Account": { - "fields": { - "address": { - "type": "JsonAddress", - "version": "v1", - "doc": "The address of this account" - }, - "pending": { - "type": "Boolean", - "doc": "indicates the account has not completed registration" - }, - "pni": { - "type": "String" - }, - "device_id": { - "type": "int", - "doc": "The Signal device ID. Official Signal mobile clients (iPhone and Android) have device ID = 1, while linked devices such as Signal Desktop or Signal iPad have higher device IDs." - }, - "account_id": { - "type": "String", - "doc": "The primary identifier on the account, included with all requests to signald for this account. Previously called 'username'" - } - }, - "doc": "A local account in signald" - }, - "NoSuchSessionError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "UserAlreadyExistsError": { - "fields": { - "uuid": { - "type": "UUID" - }, - "message": { - "type": "String" - } - }, - "error": true - }, - "ScanTimeoutError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "AddLinkedDeviceRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "uri": { - "type": "String", - "doc": "the sgnl://linkdevice uri provided (typically in qr code form) by the new device", - "example": "\"sgnl://linkdevice?uuid=jAaZ5lxLfh7zVw5WELd6-Q&pub_key=BfFbjSwmAgpVJBXUdfmSgf61eX3a%2Bq9AoxAVpl1HUap9\"", - "required": true - } - }, - "doc": "Link a new device to a local Signal account" - }, - "RegisterRequest": { - "fields": { - "account": { - "type": "String", - "doc": "the e164 phone number to register with", - "example": "\"+12024561414\"", - "required": true - }, - "voice": { - "type": "boolean", - "doc": "set to true to request a voice call instead of an SMS for verification" - }, - "captcha": { - "type": "String", - "doc": "See https://signald.org/articles/captcha/" - }, - "server": { - "type": "String", - "doc": "The identifier of the server to use. Leave blank for default (usually Signal production servers but configurable at build time)" - } - }, - "doc": "begin the account registration process by requesting a phone number verification code. when the code is received, submit it with a verify request" - }, - "CaptchaRequiredError": { - "fields": { - "more": { - "type": "String" - }, - "message": { - "type": "String" - } - }, - "error": true - }, - "VerifyRequest": { - "fields": { - "account": { - "type": "String", - "doc": "the e164 phone number being verified", - "example": "\"+12024561414\"", - "required": true - }, - "code": { - "type": "String", - "doc": "the verification code, dash (-) optional", - "example": "\"555555\"", - "required": true - } - }, - "doc": "verify an account's phone number with a code after registering, completing the account creation process" - }, - "AccountHasNoKeysError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "AccountAlreadyVerifiedError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "AccountLockedError": { - "fields": { - "more": { - "type": "String" - }, - "message": { - "type": "String" - } - }, - "error": true - }, - "GetIdentitiesRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "address": { - "type": "JsonAddress", - "version": "v1", - "doc": "address to get keys for", - "required": true - } - }, - "doc": "Get information about a known keys for a particular address" - }, - "IdentityKeyList": { - "fields": { - "address": { - "type": "JsonAddress", - "version": "v1" - }, - "identities": { - "list": true, - "type": "IdentityKey", - "version": "v1" - } - }, - "doc": "a list of identity keys associated with a particular address" - }, - "TrustRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "address": { - "type": "JsonAddress", - "version": "v1", - "doc": "The user to query identity keys for", - "required": true - }, - "safety_number": { - "type": "String", - "doc": "required if qr_code_data is absent", - "example": "\"373453558586758076680580548714989751943247272727416091564451\"" - }, - "qr_code_data": { - "type": "String", - "doc": "base64-encoded QR code data. required if safety_number is absent" - }, - "trust_level": { - "type": "String", - "doc": "One of TRUSTED_UNVERIFIED, TRUSTED_VERIFIED or UNTRUSTED. Default is TRUSTED_VERIFIED", - "example": "\"TRUSTED_VERIFIED\"" - } - }, - "doc": "Trust another user's safety number using either the QR code data or the safety number text" - }, - "FingerprintVersionMismatchError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "UnknownIdentityKeyError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "InvalidFingerprintError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "DeleteAccountRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to delete", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "server": { - "type": "boolean", - "doc": "delete account information from the server as well (default false)" - } - }, - "doc": "delete all account data signald has on disk, and optionally delete the account from the server as well. Note that this is not \"unlink\" and will delete the entire account, even from a linked device." - }, - "TypingRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "address": { - "type": "JsonAddress", - "version": "v1" - }, - "group": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "typing": { - "type": "boolean", - "example": "true", - "required": true - }, - "when": { - "type": "long" - } - }, - "doc": "send a typing started or stopped message" - }, - "InvalidGroupError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "ResetSessionRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "address": { - "type": "JsonAddress", - "version": "v1", - "doc": "the user to reset session with", - "required": true - }, - "timestamp": { - "type": "Long" - } - }, - "doc": "reset a session with a particular user" - }, - "RequestSyncRequest": { - "fields": { - "groups": { - "type": "boolean", - "doc": "request group sync (default true)" - }, - "configuration": { - "type": "boolean", - "doc": "request configuration sync (default true)" - }, - "contacts": { - "type": "boolean", - "doc": "request contact sync (default true)" - }, - "blocked": { - "type": "boolean", - "doc": "request block list sync (default true)" - }, - "keys": { - "type": "boolean", - "doc": "request storage service keys" - }, - "account": { - "type": "String", - "doc": "The account to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - } - }, - "doc": "Request other devices on the account send us their group list, syncable config and contact list." - }, - "ListAccountsRequest": { - "fields": {}, - "doc": "return all local accounts" - }, - "AccountList": { - "fields": { - "accounts": { - "list": true, - "type": "Account", - "version": "v1" - } - } - }, - "GroupLinkInfoRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "uri": { - "type": "String", - "doc": "the signald.group link", - "example": "\"https://signal.group/#CjQKINH_GZhXhfifTcnBkaKTNRxW-hHKnGSq-cJNyPVqHRp8EhDUB7zjKNEl0NaULhsqJCX3\"", - "required": true - } - }, - "doc": "Get information about a group from a signal.group link" - }, - "GroupLinkNotActiveError": { - "fields": { - "message": { - "type": "String" - } - }, - "error": true - }, - "UpdateContactRequest": { - "fields": { - "account": { - "type": "String", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "address": { - "type": "JsonAddress", - "version": "v1", - "required": true - }, - "name": { - "type": "String" - }, - "color": { - "type": "String" - }, - "inbox_position": { - "type": "Integer" - } - }, - "doc": "update information about a local contact" - }, - "SetExpirationRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "address": { - "type": "JsonAddress", - "version": "v1" - }, - "group": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "expiration": { - "type": "int", - "example": "604800", - "required": true - } - }, - "doc": "Set the message expiration timer for a thread. Expiration must be specified in seconds, set to 0 to disable timer" - }, - "SetDeviceNameRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to set the device name of", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "device_name": { - "type": "String", - "doc": "The device name" - } - }, - "doc": "set this device's name. This will show up on the mobile device on the same account under settings -> linked devices" - }, - "GetAllIdentities": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"+12024561414\"", - "required": true - } - }, - "doc": "get all known identity keys" - }, - "AllIdentityKeyList": { - "fields": { - "identity_keys": { - "list": true, - "type": "IdentityKeyList", - "version": "v1" - } - } - }, - "SubscribeRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to subscribe to incoming message for", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - } - }, - "doc": "receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages." - }, - "UnsubscribeRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to unsubscribe from", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - } - }, - "doc": "See subscribe for more info" - }, - "RemoteDeleteRequest": { - "fields": { - "account": { - "type": "String", - "doc": "the account to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "address": { - "type": "JsonAddress", - "version": "v1", - "doc": "the address to send the delete message to. should match address the message to be deleted was sent to. required if group is not set." - }, - "group": { - "type": "String", - "doc": "the group to send the delete message to. should match group the message to be deleted was sent to. required if address is not set.", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"" - }, - "timestamp": { - "type": "long", - "required": true - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v1", - "doc": "Optionally set to a sub-set of group members. Ignored if group isn't specified" - } - }, - "doc": "delete a message previously sent" - }, - "AddServerRequest": { - "fields": { - "server": { - "type": "Server", - "version": "v1", - "required": true - } - }, - "doc": "add a new server to connect to. Returns the new server's UUID." - }, - "GetServersRequest": { - "fields": {} - }, - "ServerList": { - "fields": { - "servers": { - "list": true, - "type": "Server", - "version": "v1" - } - } - }, - "RemoveServerRequest": { - "fields": { - "uuid": { - "type": "String" - } - } - }, - "SendPaymentRequest": { - "fields": { - "account": { - "type": "String", - "doc": "the account to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "address": { - "type": "JsonAddress", - "version": "v1", - "doc": "the address to send the payment message to", - "required": true - }, - "payment": { - "type": "Payment", - "version": "v1", - "required": true - }, - "when": { - "type": "Long" - } - }, - "doc": "send a mobilecoin payment" - }, - "RemoteConfigRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to use to retrieve the remote config", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - } - }, - "doc": "Retrieves the remote config (feature flags) from the server." - }, - "RemoteConfigList": { - "fields": { - "config": { - "list": true, - "type": "RemoteConfig", - "version": "v1" - } - } - }, - "RefuseMembershipRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v1", - "doc": "list of requesting members to refuse", - "required": true - }, - "group_id": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", - "required": true - }, - "also_ban": { - "type": "boolean" - } - }, - "doc": "deny a request to join a group" - }, - "SubmitChallengeRequest": { - "fields": { - "account": { - "type": "String", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "challenge": { - "type": "String", - "required": true - }, - "captcha_token": { - "type": "String" - } - } - }, - "IsIdentifierRegisteredRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to use to use", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "identifier": { - "type": "String", - "doc": "The UUID of an identifier to check if it is registered on Signal. This UUID is either a Phone Number Identity (PNI) or an Account Identity (ACI).", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"", - "required": true - } - }, - "doc": "Determine whether an account identifier is registered on the Signal service." - }, - "BooleanMessage": { - "fields": { - "value": { - "type": "boolean" - } - }, - "doc": "A message containing a single boolean, usually as a response" - }, - "WaitForScanRequest": { - "fields": { - "session_id": { - "type": "String" - } - }, - "doc": "An optional part of the linking process. Intended to be called after displaying the QR code, will return quickly after the user scans the QR code. finish_link must be called after wait_for_scan returns a non-error" - }, - "GetGroupRevisionPagesRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "group_id": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", - "required": true - }, - "from_revision": { - "type": "int", - "doc": "The revision to start the pages from. Note that if this is lower than the revision you joined the group, an AuthorizationFailedError is returned.", - "required": true - }, - "include_first_revision": { - "type": "boolean", - "doc": "Whether to include the first state in the returned pages (default false)" - } - }, - "doc": "Query the server for group revision history. The history contains information about the changes between each revision and the user that made the change." - }, - "GroupHistoryPage": { - "fields": { - "results": { - "list": true, - "type": "GroupHistoryEntry", - "version": "v1" - }, - "paging_data": { - "type": "PagingData", - "version": "v1" - } - }, - "doc": "The result of fetching a group's history along with paging data." - }, - "SendSyncMessageRequest": { - "fields": { - "account": { - "type": "String", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "view_once_open_message": { - "type": "JsonViewOnceOpenMessage", - "version": "v1", - "doc": "This can be set to indicate to other devices about having viewed a view-once message." - }, - "message_request_response": { - "type": "JsonMessageRequestResponseMessage", - "version": "v1", - "doc": "This can be set to indicate to other devices about a response to an incoming message request from an unknown user or group. Warning: Using the BLOCK and BLOCK_AND_DELETE options relies on other devices to do the blocking, and it does not make you leave the group!" - } - }, - "doc": "Sends a sync message to the account's devices" - }, - "JsonSendMessageResult": { - "fields": { - "address": { - "type": "JsonAddress", - "version": "v1" - }, - "success": { - "type": "SendSuccess", - "version": "v1" - }, - "networkFailure": { - "type": "boolean", - "example": "false" - }, - "unregisteredFailure": { - "type": "boolean", - "example": "false" - }, - "identityFailure": { - "type": "String" - }, - "proof_required_failure": { - "type": "ProofRequiredError", - "version": "v1" - } - } - }, - "BanUserRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "group_id": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", - "required": true - }, - "users": { - "list": true, - "type": "JsonAddress", - "version": "v1", - "doc": "List of users to ban", - "required": true - } - }, - "doc": "Bans users from a group. This works even if the users aren't in the group. If they are currently in the group, they will also be removed." - }, - "UnbanUserRequest": { - "fields": { - "account": { - "type": "String", - "doc": "The account to interact with", - "example": "\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"", - "required": true - }, - "group_id": { - "type": "String", - "example": "\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"", - "required": true - }, - "users": { - "list": true, - "type": "JsonAddress", - "version": "v1", - "doc": "List of users to unban", - "required": true - } - }, - "doc": "Unbans users from a group." - }, - "IdentityKey": { - "fields": { - "added": { - "type": "long", - "doc": "the first time this identity key was seen" - }, - "safety_number": { - "type": "String", - "example": "\"373453558586758076680580548714989751943247272727416091564451\"" - }, - "qr_code_data": { - "type": "String", - "doc": "base64-encoded QR code data" - }, - "trust_level": { - "type": "String", - "doc": "One of TRUSTED_UNVERIFIED, TRUSTED_VERIFIED or UNTRUSTED" - } - } - }, - "JsonDataMessage": { - "fields": { - "timestamp": { - "type": "long", - "doc": "the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes.", - "example": "1615576442475" - }, - "attachments": { - "list": true, - "type": "JsonAttachment", - "version": "v1", - "doc": "files attached to the incoming message" - }, - "body": { - "type": "String", - "doc": "the text body of the incoming message.", - "example": "\"hello\"" - }, - "group": { - "type": "JsonGroupInfo", - "version": "v1", - "doc": "if the incoming message was sent to a v1 group, information about that group will be here" - }, - "groupV2": { - "type": "JsonGroupV2Info", - "version": "v1", - "doc": "if the incoming message was sent to a v2 group, basic identifying information about that group will be here. If group information changes, JsonGroupV2Info.revision is incremented. If the group revision is higher than previously seen, a client can retrieve the group information by calling get_group." - }, - "endSession": { - "type": "boolean" - }, - "expiresInSeconds": { - "type": "int", - "doc": "the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds" - }, - "profileKeyUpdate": { - "type": "boolean" - }, - "quote": { - "type": "JsonQuote", - "version": "v1", - "doc": "if the incoming message is a quote or reply to another message, this will contain information about that message" - }, - "contacts": { - "list": true, - "type": "SharedContact", - "version": "v1", - "doc": "if the incoming message has a shared contact, the contact's information will be here" - }, - "previews": { - "list": true, - "type": "JsonPreview", - "version": "v1", - "doc": "if the incoming message has a link preview, information about that preview will be here" - }, - "sticker": { - "type": "JsonSticker", - "version": "v0", - "doc": "if the incoming message is a sticker, information about the sicker will be here" - }, - "viewOnce": { - "type": "boolean", - "doc": "indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image." - }, - "reaction": { - "type": "JsonReaction", - "version": "v1", - "doc": "if the message adds or removes a reaction to another message, this will indicate what change is being made" - }, - "remoteDelete": { - "type": "RemoteDelete", - "version": "v1", - "doc": "if the inbound message is deleting a previously sent message, indicates which message should be deleted" - }, - "mentions": { - "list": true, - "type": "JsonMention", - "version": "v1", - "doc": "list of mentions in the message" - }, - "payment": { - "type": "Payment", - "version": "v1", - "doc": "details about the MobileCoin payment attached to the message, if present" - }, - "is_expiration_update": { - "type": "boolean", - "doc": "whether or not this message changes the expiresInSeconds value for the whole chat. Some messages (remote deletes, reactions, etc) will have expiresInSeconds=0 even though the chat has disappearing messages enabled." - }, - "group_call_update": { - "type": "String", - "doc": "the eraId string from a group call message update" - }, - "story_context": { - "type": "StoryContext", - "version": "v1" - } - } - }, - "JsonSyncMessage": { - "fields": { - "sent": { - "type": "JsonSentTranscriptMessage", - "version": "v1" - }, - "contacts": { - "type": "JsonAttachment", - "version": "v1" - }, - "contactsComplete": { - "type": "boolean" - }, - "groups": { - "type": "JsonAttachment", - "version": "v1" - }, - "blockedList": { - "type": "JsonBlockedListMessage", - "version": "v1" - }, - "request": { - "type": "String" - }, - "readMessages": { - "list": true, - "type": "JsonReadMessage", - "version": "v1" - }, - "viewOnceOpen": { - "type": "JsonViewOnceOpenMessage", - "version": "v1" - }, - "verified": { - "type": "JsonVerifiedMessage", - "version": "v1" - }, - "configuration": { - "type": "ConfigurationMessage", - "version": "v0" - }, - "stickerPackOperations": { - "list": true, - "type": "JsonStickerPackOperationMessage", - "version": "v0" - }, - "fetchType": { - "type": "String" - }, - "messageRequestResponse": { - "type": "JsonMessageRequestResponseMessage", - "version": "v1" - } - } - }, - "CallMessage": { - "fields": { - "offer_message": { - "type": "OfferMessage", - "version": "v1" - }, - "answer_message": { - "type": "AnswerMessage", - "version": "v1" - }, - "busy_message": { - "type": "BusyMessage", - "version": "v1" - }, - "hangup_message": { - "type": "HangupMessage", - "version": "v1" - }, - "ice_update_message": { - "list": true, - "type": "IceUpdateMessage", - "version": "v1" - }, - "destination_device_id": { - "type": "Integer" - }, - "multi_ring": { - "type": "boolean" - } - } - }, - "ReceiptMessage": { - "fields": { - "type": { - "type": "String", - "doc": "options: UNKNOWN, DELIVERY, READ, VIEWED" - }, - "timestamps": { - "list": true, - "type": "Long" - }, - "when": { - "type": "long" - } - } - }, - "TypingMessage": { - "fields": { - "action": { - "type": "String" - }, - "timestamp": { - "type": "long" - }, - "group_id": { - "type": "String" - } - } - }, - "StoryMessage": { - "fields": { - "group": { - "type": "JsonGroupV2Info", - "version": "v1" - }, - "file": { - "type": "JsonAttachment", - "version": "v1" - }, - "text": { - "type": "TextAttachment", - "version": "v1" - }, - "allow_replies": { - "type": "Boolean" - } - } - }, - "JsonAttachment": { - "fields": { - "contentType": { - "type": "String" - }, - "id": { - "type": "String" - }, - "size": { - "type": "int" - }, - "storedFilename": { - "type": "String", - "doc": "when receiving, the path that file has been downloaded to" - }, - "filename": { - "type": "String", - "doc": "when sending, the path to the local file to upload" - }, - "customFilename": { - "type": "String", - "doc": "the original name of the file" - }, - "caption": { - "type": "String" - }, - "width": { - "type": "int" - }, - "height": { - "type": "int" - }, - "voiceNote": { - "type": "boolean" - }, - "key": { - "type": "String" - }, - "digest": { - "type": "String" - }, - "blurhash": { - "type": "String" - } - }, - "doc": "represents a file attached to a message. When sending, only `filename` is required." - }, - "JsonQuote": { - "fields": { - "id": { - "type": "long", - "doc": "the client timestamp of the message being quoted", - "example": "1615576442475" - }, - "author": { - "type": "JsonAddress", - "version": "v1", - "doc": "the author of the message being quoted" - }, - "text": { - "type": "String", - "doc": "the body of the message being quoted", - "example": "\"hey  what's up?\"" - }, - "attachments": { - "list": true, - "type": "JsonQuotedAttachment", - "version": "v0", - "doc": "list of files attached to the quoted message" - }, - "mentions": { - "list": true, - "type": "JsonMention", - "version": "v1", - "doc": "list of mentions in the quoted message" - } - }, - "doc": "A quote is a reply to a previous message. ID is the sent time of the message being replied to" - }, - "JsonMention": { - "fields": { - "uuid": { - "type": "String", - "doc": "The UUID of the account being mentioned", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" - }, - "start": { - "type": "int", - "doc": "The number of characters in that the mention starts at. Note that due to a quirk of how signald encodes JSON, if this value is 0 (for example if the first character in the message is the mention) the field won't show up.", - "example": "4" - }, - "length": { - "type": "int", - "doc": "The length of the mention represented in the message. Seems to always be 1 but included here in case that changes.", - "example": "1" - } - } - }, - "JsonPreview": { - "fields": { - "url": { - "type": "String" - }, - "title": { - "type": "String" - }, - "description": { - "type": "String" - }, - "date": { - "type": "long" - }, - "attachment": { - "type": "JsonAttachment", - "version": "v1", - "doc": "an optional image file attached to the preview" - } - }, - "doc": "metadata about one of the links in a message" - }, - "JsonReaction": { - "fields": { - "emoji": { - "type": "String", - "doc": "the emoji to react with", - "example": "\"👍\"" - }, - "remove": { - "type": "boolean", - "doc": "set to true to remove the reaction. requires emoji be set to previously reacted emoji" - }, - "targetAuthor": { - "type": "JsonAddress", - "version": "v1", - "doc": "the author of the message being reacted to" - }, - "targetSentTimestamp": { - "type": "long", - "doc": "the client timestamp of the message being reacted to", - "example": "1615576442475" - } - } - }, - "GroupAccessControl": { - "fields": { - "link": { - "type": "String", - "doc": "UNSATISFIABLE when the group link is disabled, ADMINISTRATOR when the group link is enabled but an administrator must approve new members, ANY when the group link is enabled and no approval is required", - "example": "\"ANY\"" - }, - "attributes": { - "type": "String", - "doc": "who can edit group info" - }, - "members": { - "type": "String", - "doc": "who can add members" - } - }, - "doc": "group access control settings. Options for each controlled action are: UNKNOWN, ANY, MEMBER, ADMINISTRATOR, UNSATISFIABLE and UNRECOGNIZED" - }, - "GroupMember": { - "fields": { - "uuid": { - "type": "String", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" - }, - "role": { - "type": "String", - "doc": "possible values are: UNKNOWN, DEFAULT, ADMINISTRATOR and UNRECOGNIZED", - "example": "\"DEFAULT\"" - }, - "joined_revision": { - "type": "int" - } - } - }, - "BannedGroupMember": { - "fields": { - "uuid": { - "type": "String", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" - }, - "timestamp": { - "type": "long", - "doc": "Timestamp as milliseconds since Unix epoch of when the user was banned. This field is set by the server." - } - } - }, - "GroupChange": { - "fields": { - "editor": { - "type": "JsonAddress", - "version": "v1", - "doc": "The user that made the change." - }, - "revision": { - "type": "Integer", - "doc": "The group revision that this change brings the group to." - }, - "new_members": { - "list": true, - "type": "GroupMember", - "version": "v1", - "doc": "Represents users have been added to the group. This can be from group members adding users, or a users joining via a group link that required no approval." - }, - "delete_members": { - "list": true, - "type": "JsonAddress", - "version": "v1", - "doc": "Represents users that have been removed from the group. This can be from admins removing users, or users choosing to leave the group" - }, - "modify_member_roles": { - "list": true, - "type": "GroupMember", - "version": "v1", - "doc": "Represents users with their new, modified role." - }, - "modified_profile_keys": { - "list": true, - "type": "GroupMember", - "version": "v1", - "doc": "Represents users that have rotated their profile key. Note that signald currently does not expose profile keys to clients. The joined revision property will always be 0 in this list." - }, - "new_pending_members": { - "list": true, - "type": "GroupPendingMember", - "version": "v1", - "doc": "Represents a user that has been invited to the group by another user." - }, - "delete_pending_members": { - "list": true, - "type": "JsonAddress", - "version": "v1" - }, - "promote_pending_members": { - "list": true, - "type": "GroupMember", - "version": "v1" - }, - "new_banned_members": { - "list": true, - "type": "BannedGroupMember", - "version": "v1" - }, - "new_unbanned_members": { - "list": true, - "type": "BannedGroupMember", - "version": "v1" - }, - "new_title": { - "type": "String" - }, - "new_avatar": { - "type": "Boolean", - "doc": "Whether this group change changed the avatar." - }, - "new_timer": { - "type": "Integer", - "doc": "New disappearing messages timer value." - }, - "new_access_control": { - "type": "GroupAccessControl", - "version": "v1", - "doc": "If not null, then this group change modified one of the access controls. Some of the properties in here will be null." - }, - "new_requesting_members": { - "list": true, - "type": "GroupRequestingMember", - "version": "v1", - "doc": "Represents users that have requested to join the group via the group link. Note that members requesting to join might not necessarily have the list of users in the group, so they won't be able to send a peer-to-peer group update message to inform users of their request to join. Other users in the group may inform us that the revision has increased, but the members requesting access will have to be obtained from the server instead (which signald will handle). For now, a get_group request has to be made to get the users that have requested to join the group." - }, - "delete_requesting_members": { - "list": true, - "type": "JsonAddress", - "version": "v1" - }, - "promote_requesting_members": { - "list": true, - "type": "GroupMember", - "version": "v1" - }, - "new_invite_link_password": { - "type": "Boolean", - "doc": "Whether this group change involved resetting the group invite link." - }, - "new_description": { - "type": "String" - }, - "new_is_announcement_group": { - "type": "String", - "doc": "Whether this change affected the announcement group setting. Possible values are UNKNOWN, ENABLED or DISABLED" - } - }, - "doc": "Represents a group change made by a user. This can also represent request link invites. Only the fields relevant to the group change performed will be set. Note that in signald, group changes are currently only received from incoming messages from a message subscription." - }, - "DeviceInfo": { - "fields": { - "id": { - "type": "long" - }, - "name": { - "type": "String" - }, - "created": { - "type": "long" - }, - "lastSeen": { - "type": "long" - } - } - }, - "JsonGroupInfo": { - "fields": { - "groupId": { - "type": "String" - }, - "members": { - "list": true, - "type": "JsonAddress", - "version": "v1" - }, - "name": { - "type": "String" - }, - "type": { - "type": "String" - }, - "avatarId": { - "type": "long" - } - }, - "doc": "information about a legacy group" - }, - "Capabilities": { - "fields": { - "gv2": { - "type": "boolean", - "doc": "this capability is deprecated and will always be true" - }, - "storage": { - "type": "boolean" - }, - "stories": { - "type": "boolean" - }, - "gv1-migration": { - "type": "boolean" - }, - "sender_key": { - "type": "boolean" - }, - "announcement_group": { - "type": "boolean" - }, - "change_number": { - "type": "boolean" - } - } - }, - "Server": { - "fields": { - "uuid": { - "type": "UUID", - "doc": "A unique identifier for the server, referenced when adding accounts. Must be a valid UUID. Will be generated if not specified when creating." - }, - "proxy": { - "type": "String" - }, - "ca": { - "type": "String", - "doc": "base64 encoded trust store, password must be 'whisper'" - }, - "service_url": { - "type": "String" - }, - "cdn_urls": { - "list": true, - "type": "ServerCDN", - "version": "v1" - }, - "contact_discovery_url": { - "type": "String" - }, - "key_backup_url": { - "type": "String" - }, - "storage_url": { - "type": "String" - }, - "zk_param": { - "type": "String", - "doc": "base64 encoded ZKGROUP_SERVER_PUBLIC_PARAMS value" - }, - "unidentified_sender_root": { - "type": "String", - "doc": "base64 encoded" - }, - "key_backup_service_name": { - "type": "String" - }, - "key_backup_service_id": { - "type": "String", - "doc": "base64 encoded" - }, - "key_backup_mrenclave": { - "type": "String" - }, - "cds_mrenclave": { - "type": "String" - }, - "ias_ca": { - "type": "String", - "doc": "base64 encoded trust store, password must be 'whisper'" - } - }, - "doc": "a Signal server" - }, - "Payment": { - "fields": { - "receipt": { - "type": "String", - "doc": "base64 encoded payment receipt data. This is a protobuf value which can be decoded as the Receipt object described in https://github.com/mobilecoinfoundation/mobilecoin/blob/master/api/proto/external.proto" - }, - "note": { - "type": "String", - "doc": "note attached to the payment" - } - }, - "doc": "details about a MobileCoin payment" - }, - "RemoteConfig": { - "fields": { - "name": { - "type": "String", - "doc": "The name of this remote config entry. These names may be prefixed with the platform type (\"android.\", \"ios.\", \"desktop.\", etc.) Typically, clients only handle the relevant configs for its platform, hardcoding the names it cares about handling and ignoring the rest.", - "example": "desktop.mediaQuality.levels" - }, - "value": { - "type": "String", - "doc": "The value for this remote config entry. Even though this is a string, it could be a boolean as a string, an integer/long value, a comma-delimited list, etc. Clients usually consume this by hardcoding the feature flagsit should track in the app and assuming that the server will send the type that the client expects. If an unexpected type occurs, it falls back to a default value.", - "example": "1:2,61:2,81:2,82:2,65:2,31:2,47:2,41:2,32:2,385:2,971:2,974:2,49:2,33:2,*:1" - } - }, - "doc": "A remote config (feature flag) entry." - }, - "GroupHistoryEntry": { - "fields": { - "group": { - "type": "JsonGroupV2Info", - "version": "v1" - }, - "change": { - "type": "GroupChange", - "version": "v1" - } - } - }, - "PagingData": { - "fields": { - "has_more_pages": { - "type": "boolean" - }, - "next_page_revision": { - "type": "int" - } - } - }, - "JsonViewOnceOpenMessage": { - "fields": { - "sender": { - "type": "JsonAddress", - "version": "v1" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - } - } - }, - "JsonMessageRequestResponseMessage": { - "fields": { - "person": { - "type": "JsonAddress", - "version": "v1" - }, - "groupId": { - "type": "String" - }, - "type": { - "type": "String", - "doc": "One of UNKNOWN, ACCEPT, DELETE, BLOCK, BLOCK_AND_DELETE, UNBLOCK_AND_ACCEPT" - } - }, - "doc": "Responses to message requests from unknown users or groups" - }, - "SendSuccess": { - "fields": { - "unidentified": { - "type": "boolean" - }, - "needsSync": { - "type": "boolean" - }, - "duration": { - "type": "long" - }, - "devices": { - "list": true, - "type": "Integer" - } - } - }, - "ProofRequiredError": { - "fields": { - "token": { - "type": "String" - }, - "options": { - "list": true, - "type": "String", - "doc": "possible list values are RECAPTCHA and PUSH_CHALLENGE" - }, - "message": { - "type": "String" - }, - "retry_after": { - "type": "long", - "doc": "value in seconds" - } - }, - "error": true - }, - "SharedContact": { - "fields": { - "name": { - "type": "SharedContactName", - "version": "v1", - "doc": "the name of the shared contact" - }, - "email": { - "list": true, - "type": "SharedContactEmail", - "version": "v1", - "doc": "the email addresses of the shared contact" - }, - "phone": { - "list": true, - "type": "SharedContactPhone", - "version": "v1", - "doc": "the phone numbers of the shared contact" - }, - "address": { - "list": true, - "type": "SharedContactAddress", - "version": "v1", - "doc": "the physical addresses of the shared contact" - }, - "avatar": { - "type": "SharedContactAvatar", - "version": "v1", - "doc": "the profile picture/avatar of the shared contact" - }, - "organization": { - "type": "String", - "doc": "the organization (e.g. workplace) of the shared contact" - } - } - }, - "RemoteDelete": { - "fields": { - "target_sent_timestamp": { - "type": "long" - } - } - }, - "StoryContext": { - "fields": { - "author": { - "type": "String" - }, - "sent_timestamp": { - "type": "long" - } - } - }, - "JsonSentTranscriptMessage": { - "fields": { - "destination": { - "type": "JsonAddress", - "version": "v1" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - }, - "expirationStartTimestamp": { - "type": "long" - }, - "message": { - "type": "JsonDataMessage", - "version": "v1" - }, - "unidentifiedStatus": { - "type": "Map" - }, - "isRecipientUpdate": { - "type": "boolean" - } - } - }, - "JsonBlockedListMessage": { - "fields": { - "addresses": { - "list": true, - "type": "JsonAddress", - "version": "v1" - }, - "groupIds": { - "list": true, - "type": "String" - } - } - }, - "JsonReadMessage": { - "fields": { - "sender": { - "type": "JsonAddress", - "version": "v1" - }, - "timestamp": { - "type": "long", - "example": "1615576442475" - } - } - }, - "JsonVerifiedMessage": { - "fields": { - "destination": { - "type": "JsonAddress", - "version": "v1" - }, - "identityKey": { - "type": "String" - }, - "verified": { - "type": "String" - }, - "timestamp": { - "type": "long" - } - } - }, - "OfferMessage": { - "fields": { - "id": { - "type": "long" - }, - "sdp": { - "type": "String" - }, - "type": { - "type": "String" - }, - "opaque": { - "type": "String" - } - } - }, - "AnswerMessage": { - "fields": { - "id": { - "type": "long" - }, - "sdp": { - "type": "String" - }, - "opaque": { - "type": "String" - } - } - }, - "BusyMessage": { - "fields": { - "id": { - "type": "long" - } - } - }, - "HangupMessage": { - "fields": { - "id": { - "type": "long" - }, - "type": { - "type": "String" - }, - "legacy": { - "type": "boolean" - }, - "device_id": { - "type": "int" - } - } - }, - "IceUpdateMessage": { - "fields": { - "id": { - "type": "long" - }, - "opaque": { - "type": "String" - }, - "sdp": { - "type": "String" - } - } - }, - "TextAttachment": { - "fields": { - "text": { - "type": "String" - }, - "style": { - "type": "String" - }, - "preview": { - "type": "JsonPreview", - "version": "v1" - }, - "text_foreground_color": { - "type": "String" - }, - "text_background_color": { - "type": "String" - }, - "background_gradient": { - "type": "Gradient", - "version": "v1" - }, - "background_color": { - "type": "String" - } - } - }, - "GroupPendingMember": { - "fields": { - "uuid": { - "type": "String", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" - }, - "role": { - "type": "String", - "doc": "possible values are: UNKNOWN, DEFAULT, ADMINISTRATOR and UNRECOGNIZED", - "example": "\"DEFAULT\"" - }, - "timestamp": { - "type": "long" - }, - "added_by_uuid": { - "type": "String", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" - } - } - }, - "GroupRequestingMember": { - "fields": { - "uuid": { - "type": "String", - "example": "\"aeed01f0-a234-478e-8cf7-261c283151e7\"" - }, - "timestamp": { - "type": "long" - } - } - }, - "ServerCDN": { - "fields": { - "number": { - "type": "int" - }, - "url": { - "type": "String" - } - } - }, - "SharedContactName": { - "fields": { - "display": { - "type": "String", - "doc": "the full name that should be displayed" - }, - "given": { - "type": "String", - "doc": "given name" - }, - "middle": { - "type": "String", - "doc": "middle name" - }, - "family": { - "type": "String", - "doc": "family name (surname)" - }, - "prefix": { - "type": "String" - }, - "suffix": { - "type": "String" - } - } - }, - "SharedContactEmail": { - "fields": { - "type": { - "type": "String", - "doc": "the type of email (options: HOME, WORK, MOBILE, CUSTOM)" - }, - "value": { - "type": "String", - "doc": "the email address" - }, - "label": { - "type": "String", - "doc": "the type label when type is CUSTOM" - } - } - }, - "SharedContactPhone": { - "fields": { - "type": { - "type": "String", - "doc": "the type of phone (options: HOME, WORK, MOBILE, CUSTOM)" - }, - "value": { - "type": "String", - "doc": "the phone number" - }, - "label": { - "type": "String", - "doc": "the type label when type is CUSTOM" - } - } - }, - "SharedContactAddress": { - "fields": { - "type": { - "type": "String", - "doc": "the type of address (options: HOME, WORK, CUSTOM)" - }, - "label": { - "type": "String" - }, - "street": { - "type": "String" - }, - "pobox": { - "type": "String" - }, - "neighborhood": { - "type": "String" - }, - "city": { - "type": "String" - }, - "region": { - "type": "String" - }, - "postcode": { - "type": "String" - }, - "country": { - "type": "String" - } - } - }, - "SharedContactAvatar": { - "fields": { - "attachment": { - "type": "JsonAttachment", - "version": "v1" - }, - "is_profile": { - "type": "boolean" - } - } - }, - "Gradient": { - "fields": { - "start_color": { - "type": "String" - }, - "end_color": { - "type": "String" - } - } - } - } - }, - "actions": { - "v1": { - "send": { - "request": "SendRequest", - "response": "SendResponse", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "NoSendPermissionError" - }, - { - "name": "InvalidAttachmentError" - }, - { - "name": "InternalError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "RateLimitError" - }, - { - "name": "InvalidRecipientError" - }, - { - "name": "AttachmentTooLargeError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "react": { - "request": "ReactRequest", - "response": "SendResponse", - "doc": "react to a previous message", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "NoSendPermissionError" - }, - { - "name": "InternalError" - }, - { - "name": "InvalidRecipientError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "RateLimitError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "version": { - "request": "VersionRequest", - "response": "JsonVersionMessage" - }, - "accept_invitation": { - "request": "AcceptInvitationRequest", - "response": "JsonGroupV2Info", - "doc": "Accept a v2 group invitation. Note that you must have a profile name set to join groups.", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "OwnProfileKeyDoesNotExistError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "InternalError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError", - "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." - }, - { - "name": "SQLError" - }, - { - "name": "GroupPatchNotAcceptedError", - "doc": "Caused when server rejects the group update." - } - ] - }, - "approve_membership": { - "request": "ApproveMembershipRequest", - "response": "JsonGroupV2Info", - "doc": "approve a request to join a group", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "InternalError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError", - "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." - }, - { - "name": "SQLError" - }, - { - "name": "GroupPatchNotAcceptedError", - "doc": "Caused when server rejects the group update." - } - ] - }, - "get_group": { - "request": "GetGroupRequest", - "response": "JsonGroupV2Info", - "doc": "Query the server for the latest state of a known group. If the account is not a member of the group, an UnknownGroupError is returned.", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "InternalError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "InvalidGroupStateError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "get_linked_devices": { - "request": "GetLinkedDevicesRequest", - "response": "LinkedDevices", - "doc": "list all linked devices on a Signal account", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "join_group": { - "request": "JoinGroupRequest", - "response": "JsonGroupJoinInfo", - "doc": "Join a group using the a signal.group URL. Note that you must have a profile name set to join groups.", - "errors": [ - { - "name": "InvalidRequestError" - }, - { - "name": "InvalidInviteURIError" - }, - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "OwnProfileKeyDoesNotExistError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "GroupNotActiveError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "InvalidGroupStateError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "remove_linked_device": { - "request": "RemoveLinkedDeviceRequest", - "doc": "Remove a linked device from the Signal account. Only allowed when the local device id is 1", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "update_group": { - "request": "UpdateGroupRequest", - "response": "GroupInfo", - "doc": "modify a group. Note that only one modification action may be performed at once", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError", - "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "SQLError" - }, - { - "name": "GroupPatchNotAcceptedError", - "doc": "Caused when server rejects the group update, e.g. trying to add a user that's already in the group" - } - ] - }, - "set_profile": { - "request": "SetProfile", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "InvalidBase64Error" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "resolve_address": { - "request": "ResolveAddressRequest", - "response": "JsonAddress", - "doc": "Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns.", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "mark_read": { - "request": "MarkReadRequest", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "InternalError" - }, - { - "name": "UntrustedIdentityError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "get_profile": { - "request": "GetProfileRequest", - "response": "Profile", - "doc": "Get all information available about a user", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "ProfileUnavailableError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "list_groups": { - "request": "ListGroupsRequest", - "response": "GroupList", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "list_contacts": { - "request": "ListContactsRequest", - "response": "ProfileList", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "create_group": { - "request": "CreateGroupRequest", - "response": "JsonGroupV2Info", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "OwnProfileKeyDoesNotExistError" - }, - { - "name": "NoKnownUUIDError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "InvalidGroupStateError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "leave_group": { - "request": "LeaveGroupRequest", - "response": "GroupInfo", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "InternalError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "generate_linking_uri": { - "request": "GenerateLinkingURIRequest", - "response": "LinkingURI", - "doc": "Generate a linking URI. Typically this is QR encoded and scanned by the primary device. Submit the returned session_id with a finish_link request.", - "errors": [ - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - } - ] - }, - "finish_link": { - "request": "FinishLinkRequest", - "response": "Account", - "doc": "After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device and the new account is setup. Note that the account setup process can sometimes take some time, if rapid userfeedback is required after scanning, use wait_for_scan first, then finish setup with finish_link.", - "errors": [ - { - "name": "NoSuchSessionError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "InternalError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "UserAlreadyExistsError" - }, - { - "name": "ScanTimeoutError" - } - ] - }, - "add_device": { - "request": "AddLinkedDeviceRequest", - "doc": "Link a new device to a local Signal account", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "InvalidRequestError", - "doc": "caused by syntax errors with the provided linking URI" - }, - { - "name": "InternalError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "register": { - "request": "RegisterRequest", - "response": "Account", - "doc": "begin the account registration process by requesting a phone number verification code. when the code is received, submit it with a verify request", - "errors": [ - { - "name": "CaptchaRequiredError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - } - ] - }, - "verify": { - "request": "VerifyRequest", - "response": "Account", - "doc": "verify an account's phone number with a code after registering, completing the account creation process", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "AccountHasNoKeysError" - }, - { - "name": "AccountAlreadyVerifiedError" - }, - { - "name": "AccountLockedError" - }, - { - "name": "NoSuchAccountError" - } - ] - }, - "get_identities": { - "request": "GetIdentitiesRequest", - "response": "IdentityKeyList", - "doc": "Get information about a known keys for a particular address", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "trust": { - "request": "TrustRequest", - "doc": "Trust another user's safety number using either the QR code data or the safety number text", - "errors": [ - { - "name": "InvalidRequestError" - }, - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "FingerprintVersionMismatchError" - }, - { - "name": "InvalidBase64Error" - }, - { - "name": "UnknownIdentityKeyError" - }, - { - "name": "InvalidFingerprintError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "delete_account": { - "request": "DeleteAccountRequest", - "doc": "delete all account data signald has on disk, and optionally delete the account from the server as well. Note that this is not \"unlink\" and will delete the entire account, even from a linked device.", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "typing": { - "request": "TypingRequest", - "doc": "send a typing started or stopped message", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "InvalidRecipientError" - }, - { - "name": "InvalidGroupError" - }, - { - "name": "UntrustedIdentityError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "reset_session": { - "request": "ResetSessionRequest", - "response": "SendResponse", - "doc": "reset a session with a particular user", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "NoSendPermissionError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "RateLimitError" - }, - { - "name": "InvalidRecipientError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "request_sync": { - "request": "RequestSyncRequest", - "doc": "Request other devices on the account send us their group list, syncable config and contact list.", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "UntrustedIdentityError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "list_accounts": { - "request": "ListAccountsRequest", - "response": "AccountList", - "doc": "return all local accounts", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "InternalError" - } - ] - }, - "group_link_info": { - "request": "GroupLinkInfoRequest", - "response": "JsonGroupJoinInfo", - "doc": "Get information about a group from a signal.group link", - "errors": [ - { - "name": "GroupLinkNotActiveError" - }, - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "SQLError" - } - ] - }, - "update_contact": { - "request": "UpdateContactRequest", - "response": "Profile", - "doc": "update information about a local contact", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "InternalError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "set_expiration": { - "request": "SetExpirationRequest", - "response": "SendResponse", - "doc": "Set the message expiration timer for a thread. Expiration must be specified in seconds, set to 0 to disable timer", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError", - "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "SQLError" - }, - { - "name": "GroupPatchNotAcceptedError", - "doc": "If updating a group, caused when server rejects the group update." - } - ] - }, - "set_device_name": { - "request": "SetDeviceNameRequest", - "doc": "set this device's name. This will show up on the mobile device on the same account under settings -> linked devices", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "get_all_identities": { - "request": "GetAllIdentities", - "response": "AllIdentityKeyList", - "doc": "get all known identity keys", - "errors": [ - { - "name": "InvalidProxyError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InternalError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "subscribe": { - "request": "SubscribeRequest", - "doc": "receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages.", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "InternalError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "unsubscribe": { - "request": "UnsubscribeRequest", - "doc": "See subscribe for more info", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "InternalError" - }, - { - "name": "SQLError" - } - ] - }, - "remote_delete": { - "request": "RemoteDeleteRequest", - "response": "SendResponse", - "doc": "delete a message previously sent", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "InvalidRecipientError" - }, - { - "name": "NoSendPermissionError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "RateLimitError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "add_server": { - "request": "AddServerRequest", - "response": "String", - "doc": "add a new server to connect to. Returns the new server's UUID.", - "errors": [ - { - "name": "InvalidProxyError" - }, - { - "name": "InternalError" - } - ] - }, - "get_servers": { - "request": "GetServersRequest", - "response": "ServerList", - "errors": [ - { - "name": "InternalError" - } - ] - }, - "delete_server": { - "request": "RemoveServerRequest", - "errors": [ - { - "name": "InternalError" - } - ] - }, - "send_payment": { - "request": "SendPaymentRequest", - "response": "SendResponse", - "doc": "send a mobilecoin payment", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "InvalidBase64Error" - }, - { - "name": "InvalidRecipientError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "NoSendPermissionError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "RateLimitError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "get_remote_config": { - "request": "RemoteConfigRequest", - "response": "RemoteConfigList", - "doc": "Retrieves the remote config (feature flags) from the server.", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "SQLError" - } - ] - }, - "refuse_membership": { - "request": "RefuseMembershipRequest", - "response": "JsonGroupV2Info", - "doc": "deny a request to join a group", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "InternalError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError", - "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "SQLError" - }, - { - "name": "GroupPatchNotAcceptedError", - "doc": "Caused when server rejects the group update." - } - ] - }, - "submit_challenge": { - "request": "SubmitChallengeRequest", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "InternalError" - }, - { - "name": "SQLError" - } - ] - }, - "is_identifier_registered": { - "request": "IsIdentifierRegisteredRequest", - "response": "BooleanMessage", - "doc": "Determine whether an account identifier is registered on the Signal service.", - "errors": [ - { - "name": "InternalError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "SQLError" - } - ] - }, - "wait_for_scan": { - "request": "WaitForScanRequest", - "doc": "An optional part of the linking process. Intended to be called after displaying the QR code, will return quickly after the user scans the QR code. finish_link must be called after wait_for_scan returns a non-error", - "errors": [ - { - "name": "NoSuchSessionError" - }, - { - "name": "ScanTimeoutError" - }, - { - "name": "InternalError" - } - ] - }, - "get_group_revision_pages": { - "request": "GetGroupRevisionPagesRequest", - "response": "GroupHistoryPage", - "doc": "Query the server for group revision history. The history contains information about the changes between each revision and the user that made the change.", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "InternalError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "InvalidGroupStateError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError", - "doc": "caused when not a member of the group, when requesting logs from a revision lower than your joinedAtVersion, etc." - }, - { - "name": "RateLimitError" - }, - { - "name": "SQLError" - } - ] - }, - "send_sync_message": { - "request": "SendSyncMessageRequest", - "response": "JsonSendMessageResult", - "doc": "Sends a sync message to the account's devices", - "errors": [ - { - "name": "InvalidRequestError" - }, - { - "name": "RateLimitError" - }, - { - "name": "InternalError" - }, - { - "name": "UnregisteredUserError" - }, - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "AuthorizationFailedError" - }, - { - "name": "SQLError" - } - ] - }, - "ban_user": { - "request": "BanUserRequest", - "response": "JsonGroupV2Info", - "doc": "Bans users from a group. This works even if the users aren't in the group. If they are currently in the group, they will also be removed.", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "InternalError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError", - "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." - }, - { - "name": "SQLError" - }, - { - "name": "GroupPatchNotAcceptedError", - "doc": "Caused when server rejects the group update." - } - ] - }, - "unban_user": { - "request": "UnbanUserRequest", - "response": "JsonGroupV2Info", - "doc": "Unbans users from a group.", - "errors": [ - { - "name": "NoSuchAccountError" - }, - { - "name": "ServerNotFoundError" - }, - { - "name": "InvalidProxyError" - }, - { - "name": "UnknownGroupError" - }, - { - "name": "GroupVerificationError" - }, - { - "name": "InternalError" - }, - { - "name": "InvalidRequestError" - }, - { - "name": "AuthorizationFailedError", - "doc": "Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)." - }, - { - "name": "SQLError" - }, - { - "name": "GroupPatchNotAcceptedError", - "doc": "Caused when server rejects the group update." - } - ] - } - } - } -} +{"doc_version":"v1","version":{"name":"signald","version":"unversioned","branch":"","commit":""},"info":"This document describes objects that may be used when communicating with signald.","types":{"v0":{"JsonAccountList":{"fields":{"accounts":{"list":true,"type":"JsonAccount","version":"v0"}},"deprecated":true,"removal_date":1641027661},"JsonMessageEnvelope":{"fields":{"username":{"type":"String","example":"\"+12024561414\""},"uuid":{"type":"String","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\""},"source":{"type":"JsonAddress","version":"v0"},"sourceDevice":{"type":"int"},"type":{"type":"String"},"relay":{"type":"String","doc":"this field is no longer available and will never be populated"},"timestamp":{"type":"long","example":"1615576442475"},"timestampISO":{"type":"String"},"serverTimestamp":{"type":"long"},"serverDeliveredTimestamp":{"type":"long","example":"161557644247580"},"hasLegacyMessage":{"type":"boolean"},"hasContent":{"type":"boolean"},"isUnidentifiedSender":{"type":"boolean"},"dataMessage":{"type":"JsonDataMessage","version":"v0"},"syncMessage":{"type":"JsonSyncMessage","version":"v0"},"callMessage":{"type":"JsonCallMessage","version":"v0"},"receipt":{"type":"JsonReceiptMessage","version":"v0"},"typing":{"type":"JsonTypingMessage","version":"v0"}},"deprecated":true,"removal_date":1641027661},"JsonAccount":{"fields":{"deviceId":{"type":"int"},"username":{"type":"String"},"filename":{"type":"String"},"uuid":{"type":"String"},"registered":{"type":"boolean"},"has_keys":{"type":"boolean"},"subscribed":{"type":"boolean"}},"deprecated":true,"removal_date":1641027661},"JsonAddress":{"fields":{"number":{"type":"String"},"uuid":{"type":"UUID"},"relay":{"type":"String"}},"deprecated":true,"removal_date":1641027661},"JsonDataMessage":{"fields":{"timestamp":{"type":"long","doc":"the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes.","example":"1615576442475"},"attachments":{"list":true,"type":"JsonAttachment","version":"v0","doc":"files attached to the incoming message"},"body":{"type":"String","doc":"the text body of the incoming message.","example":"\"hello\""},"group":{"type":"JsonGroupInfo","version":"v0","doc":"if the incoming message was sent to a v1 group, information about that group will be here"},"groupV2":{"type":"JsonGroupV2Info","version":"v0","doc":"is the incoming message was sent to a v2 group, basic identifying information about that group will be here. For full information, use list_groups"},"endSession":{"type":"boolean"},"expiresInSeconds":{"type":"int","doc":"the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds"},"profileKeyUpdate":{"type":"boolean"},"quote":{"type":"JsonQuote","version":"v0","doc":"if the incoming message is a quote or reply to another message, this will contain information about that message"},"contacts":{"list":true,"type":"SharedContact","version":"v0","doc":"if the incoming message has a shared contact, the contact's information will be here"},"previews":{"list":true,"type":"JsonPreview","version":"v0","doc":"if the incoming message has a link preview, information about that preview will be here"},"sticker":{"type":"JsonSticker","version":"v0","doc":"if the incoming message is a sticker, information about the sicker will be here"},"viewOnce":{"type":"boolean","doc":"indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image."},"reaction":{"type":"JsonReaction","version":"v0","doc":"if the message adds or removes a reaction to another message, this will indicate what change is being made"},"remoteDelete":{"type":"RemoteDelete","version":"v0","doc":"if the inbound message is deleting a previously sent message, indicates which message should be deleted"},"mentions":{"list":true,"type":"JsonMention","version":"v0","doc":"list of mentions in the message"}},"deprecated":true,"removal_date":1641027661},"JsonSyncMessage":{"fields":{"sent":{"type":"JsonSentTranscriptMessage","version":"v0"},"contacts":{"type":"JsonAttachment","version":"v0"},"contactsComplete":{"type":"boolean"},"groups":{"type":"JsonAttachment","version":"v0"},"blockedList":{"type":"JsonBlockedListMessage","version":"v0"},"request":{"type":"String"},"readMessages":{"list":true,"type":"JsonReadMessage","version":"v0"},"viewOnceOpen":{"type":"JsonViewOnceOpenMessage","version":"v0"},"verified":{"type":"JsonVerifiedMessage","version":"v0"},"configuration":{"type":"ConfigurationMessage","version":"v0"},"stickerPackOperations":{"list":true,"type":"JsonStickerPackOperationMessage","version":"v0"},"fetchType":{"type":"String"},"messageRequestResponse":{"type":"JsonMessageRequestResponseMessage","version":"v0"}},"deprecated":true,"removal_date":1641027661},"JsonCallMessage":{"fields":{"offerMessage":{"type":"OfferMessage","version":"v0"},"answerMessage":{"type":"AnswerMessage","version":"v0"},"busyMessage":{"type":"BusyMessage","version":"v0"},"hangupMessage":{"type":"HangupMessage","version":"v0"},"iceUpdateMessages":{"list":true,"type":"IceUpdateMessage","version":"v0"},"destinationDeviceId":{"type":"int"},"isMultiRing":{"type":"boolean"}},"deprecated":true,"removal_date":1641027661},"JsonReceiptMessage":{"fields":{"type":{"type":"String"},"timestamps":{"list":true,"type":"Long"},"when":{"type":"long"}},"deprecated":true,"removal_date":1641027661},"JsonTypingMessage":{"fields":{"action":{"type":"String"},"timestamp":{"type":"long"},"groupId":{"type":"String"}},"deprecated":true,"removal_date":1641027661},"JsonAttachment":{"fields":{"contentType":{"type":"String"},"id":{"type":"String"},"size":{"type":"int"},"storedFilename":{"type":"String"},"filename":{"type":"String"},"customFilename":{"type":"String"},"caption":{"type":"String"},"width":{"type":"int"},"height":{"type":"int"},"voiceNote":{"type":"boolean"},"key":{"type":"String"},"digest":{"type":"String"},"blurhash":{"type":"String"}},"deprecated":true,"removal_date":1641027661},"JsonGroupInfo":{"fields":{"groupId":{"type":"String"},"members":{"list":true,"type":"JsonAddress","version":"v0"},"name":{"type":"String"},"type":{"type":"String"},"avatarId":{"type":"long"}},"deprecated":true,"removal_date":1641027661},"JsonGroupV2Info":{"fields":{"id":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\""},"revision":{"type":"int","example":"5"},"title":{"type":"String","example":"\"Parkdale Run Club\""},"description":{"type":"String"},"avatar":{"type":"String","doc":"path to the group's avatar on local disk, if available","example":"\"/var/lib/signald/avatars/group-EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\""},"timer":{"type":"int","example":"604800"},"members":{"list":true,"type":"JsonAddress","version":"v0"},"pendingMembers":{"list":true,"type":"JsonAddress","version":"v0"},"requestingMembers":{"list":true,"type":"JsonAddress","version":"v0"},"inviteLink":{"type":"String","doc":"the signal.group link, if applicable"},"accessControl":{"type":"GroupAccessControl","version":"v0","doc":"current access control settings for this group"},"memberDetail":{"list":true,"type":"GroupMember","version":"v0","doc":"detailed member list"},"pendingMemberDetail":{"list":true,"type":"GroupMember","version":"v0","doc":"detailed pending member list"}},"deprecated":true,"removal_date":1641027661},"JsonQuote":{"fields":{"id":{"type":"long","doc":"the client timestamp of the message being quoted","example":"1615576442475"},"author":{"type":"JsonAddress","version":"v0","doc":"the author of the message being quoted"},"text":{"type":"String","doc":"the body of the message being quoted","example":"\"hey  what's up?\""},"attachments":{"list":true,"type":"JsonQuotedAttachment","version":"v0","doc":"list of files attached to the quoted message"},"mentions":{"list":true,"type":"JsonMention","version":"v0","doc":"list of mentions in the quoted message"}},"doc":"A quote is a reply to a previous message. ID is the sent time of the message being replied to","deprecated":true,"removal_date":1641027661},"SharedContact":{"fields":{"name":{"type":"Name","version":"v0"},"avatar":{"type":"Optional","version":"v0"},"phone":{"type":"Optional","version":"v0"},"email":{"type":"Optional","version":"v0"},"address":{"type":"Optional","version":"v0"},"organization":{"type":"Optional","version":"v0"}}},"JsonPreview":{"fields":{"url":{"type":"String"},"title":{"type":"String"},"attachment":{"type":"JsonAttachment","version":"v0"}},"deprecated":true,"removal_date":1641027661},"JsonSticker":{"fields":{"packID":{"type":"String"},"packKey":{"type":"String"},"stickerID":{"type":"int"},"attachment":{"type":"JsonAttachment","version":"v0"},"image":{"type":"String"}},"deprecated":true,"removal_date":1641027661},"JsonReaction":{"fields":{"emoji":{"type":"String","doc":"the emoji to react with","example":"\"👍\""},"remove":{"type":"boolean","doc":"set to true to remove the reaction. requires emoji be set to previously reacted emoji"},"targetAuthor":{"type":"JsonAddress","version":"v0","doc":"the author of the message being reacted to"},"targetSentTimestamp":{"type":"long","doc":"the client timestamp of the message being reacted to","example":"1615576442475"}},"deprecated":true,"removal_date":1641027661},"RemoteDelete":{"fields":{"targetSentTimestamp":{"type":"long"}}},"JsonMention":{"fields":{"uuid":{"type":"String","doc":"The UUID of the account being mentioned","example":"\"aeed01f0-a234-478e-8cf7-261c283151e7\""},"start":{"type":"int","doc":"The number of characters in that the mention starts at. Note that due to a quirk of how signald encodes JSON, if this value is 0 (for example if the first character in the message is the mention) the field won't show up.","example":"4"},"length":{"type":"int","doc":"The length of the mention represented in the message. Seems to always be 1 but included here in case that changes.","example":"1"}},"deprecated":true,"removal_date":1641027661},"JsonSentTranscriptMessage":{"fields":{"destination":{"type":"JsonAddress","version":"v0"},"timestamp":{"type":"long","example":"1615576442475"},"expirationStartTimestamp":{"type":"long"},"message":{"type":"JsonDataMessage","version":"v0"},"unidentifiedStatus":{"type":"Map"},"isRecipientUpdate":{"type":"boolean"}},"deprecated":true,"removal_date":1641027661},"JsonBlockedListMessage":{"fields":{"addresses":{"list":true,"type":"JsonAddress","version":"v0"},"groupIds":{"list":true,"type":"String"}},"deprecated":true,"removal_date":1641027661},"JsonReadMessage":{"fields":{"sender":{"type":"JsonAddress","version":"v0"},"timestamp":{"type":"long","example":"1615576442475"}},"deprecated":true,"removal_date":1641027661},"JsonViewOnceOpenMessage":{"fields":{"sender":{"type":"JsonAddress","version":"v0"},"timestamp":{"type":"long","example":"1615576442475"}},"deprecated":true,"removal_date":1641027661},"JsonVerifiedMessage":{"fields":{"destination":{"type":"JsonAddress","version":"v0"},"identityKey":{"type":"String"},"verified":{"type":"String"},"timestamp":{"type":"long"}},"deprecated":true,"removal_date":1641027661},"ConfigurationMessage":{"fields":{"readReceipts":{"type":"Optional","version":"v0"},"unidentifiedDeliveryIndicators":{"type":"Optional","version":"v0"},"typingIndicators":{"type":"Optional","version":"v0"},"linkPreviews":{"type":"Optional","version":"v0"}}},"JsonStickerPackOperationMessage":{"fields":{"packID":{"type":"String"},"packKey":{"type":"String"},"type":{"type":"String"}},"deprecated":true,"removal_date":1641027661},"JsonMessageRequestResponseMessage":{"fields":{"person":{"type":"JsonAddress","version":"v0"},"groupId":{"type":"String"},"type":{"type":"String"}},"deprecated":true,"removal_date":1641027661},"OfferMessage":{"fields":{"id":{"type":"long"},"sdp":{"type":"String"},"type":{"type":"Type","version":"v0"},"opaque":{"type":"String"}}},"AnswerMessage":{"fields":{"id":{"type":"long"},"sdp":{"type":"String"},"opaque":{"type":"String"}}},"BusyMessage":{"fields":{"id":{"type":"long"}}},"HangupMessage":{"fields":{"id":{"type":"long"},"type":{"type":"Type","version":"v0"},"deviceId":{"type":"int"},"legacy":{"type":"boolean"}}},"IceUpdateMessage":{"fields":{"id":{"type":"long"},"opaque":{"type":"String"},"sdp":{"type":"String"}}},"JsonQuotedAttachment":{"fields":{"contentType":{"type":"String"},"fileName":{"type":"String"},"thumbnail":{"type":"JsonAttachment","version":"v0"}},"deprecated":true,"removal_date":1641027661},"GroupAccessControl":{"fields":{"link":{"type":"String","doc":"UNSATISFIABLE when the group link is disabled, ADMINISTRATOR when the group link is enabled but an administrator must approve new members, ANY when the group link is enabled and no approval is required","example":"\"ANY\""},"attributes":{"type":"String","doc":"who can edit group info"},"members":{"type":"String","doc":"who can add members"}},"doc":"group access control settings. Options for each controlled action are: UNKNOWN, ANY, MEMBER, ADMINISTRATOR, UNSATISFIABLE and UNRECOGNIZED","deprecated":true,"removal_date":1641027661},"GroupMember":{"fields":{"uuid":{"type":"String","example":"\"aeed01f0-a234-478e-8cf7-261c283151e7\""},"role":{"type":"String","doc":"possible values are: UNKNOWN, DEFAULT, ADMINISTRATOR and UNRECOGNIZED","example":"\"DEFAULT\""},"joined_revision":{"type":"int"}},"deprecated":true,"removal_date":1641027661},"Name":{"fields":{"display":{"type":"Optional","version":"v0"},"given":{"type":"Optional","version":"v0"},"family":{"type":"Optional","version":"v0"},"prefix":{"type":"Optional","version":"v0"},"suffix":{"type":"Optional","version":"v0"},"middle":{"type":"Optional","version":"v0"}}},"Optional":{"fields":{"empty":{"type":"boolean"},"present":{"type":"boolean"}}},"Type":{"fields":{}}},"v1":{"ClientMessageWrapper":{"fields":{"type":{"type":"String","doc":"the type of object to expect in the `data` field"},"version":{"type":"String","doc":"the version of the object in the `data` field"},"data":{"type":"Object","doc":"the incoming object. The structure will vary from message to message, see `type` and `version` fields"},"error":{"type":"Boolean","doc":"true if the incoming message represents an error"},"account":{"type":"String","doc":"the account this message is from"}},"doc":"Wraps all incoming messages sent to the client after a v1 subscribe request is issued"},"DuplicateMessageError":{"fields":{"timestamp":{"type":"long"},"message":{"type":"String"}},"error":true},"UntrustedIdentityError":{"fields":{"identifier":{"type":"String"},"message":{"type":"String"},"identity_key":{"type":"IdentityKey","version":"v1"}},"error":true},"ProtocolNoSessionError":{"fields":{"sender":{"type":"String"},"timestamp":{"type":"long"},"message":{"type":"String"},"sender_device":{"type":"int"},"content_hint":{"type":"int"},"group_id":{"type":"String"}},"error":true},"ProtocolInvalidKeyIdError":{"fields":{"sender":{"type":"String"},"timestamp":{"type":"long"},"message":{"type":"String"},"sender_device":{"type":"int"},"content_hint":{"type":"int"},"group_id":{"type":"String"}},"error":true},"ProtocolInvalidMessageError":{"fields":{"sender":{"type":"String"},"timestamp":{"type":"long"},"message":{"type":"String"},"sender_device":{"type":"int"},"content_hint":{"type":"int"},"group_id":{"type":"String"}},"error":true},"IncomingMessage":{"fields":{"account":{"type":"String","example":"\"+12024561414\""},"source":{"type":"JsonAddress","version":"v1"},"type":{"type":"String"},"timestamp":{"type":"long","example":"1615576442475"},"source_device":{"type":"int"},"server_receiver_timestamp":{"type":"long","example":"1615576442475"},"server_deliver_timestamp":{"type":"long","example":"1615576442475"},"has_legacy_message":{"type":"boolean"},"has_content":{"type":"boolean"},"unidentified_sender":{"type":"boolean"},"data_message":{"type":"JsonDataMessage","version":"v1"},"sync_message":{"type":"JsonSyncMessage","version":"v1"},"call_message":{"type":"CallMessage","version":"v1"},"receipt_message":{"type":"ReceiptMessage","version":"v1"},"typing_message":{"type":"TypingMessage","version":"v1"},"story_message":{"type":"StoryMessage","version":"v1"},"server_guid":{"type":"String"}}},"ListenerState":{"fields":{"connected":{"type":"boolean"}},"doc":"prior attempt to indicate signald connectivity state. WebSocketConnectionState messages will be delivered at the same time as well as in other parts of the websocket lifecycle."},"WebSocketConnectionState":{"fields":{"state":{"type":"String","doc":"One of: DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, DISCONNECTING, AUTHENTICATION_FAILED, FAILED"},"socket":{"type":"String","doc":"One of: UNIDENTIFIED, IDENTIFIED"}},"doc":"indicates when the websocket connection state to the signal server has changed"},"StorageChange":{"fields":{"version":{"type":"long","doc":"Seems to behave like the group version numbers and increments every time the state changes"}},"doc":"Broadcast to subscribed clients when there is a state change from the storage service"},"SendRequest":{"fields":{"username":{"type":"String","example":"\"+12024561414\""},"account":{"type":"String","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\""},"recipientAddress":{"type":"JsonAddress","version":"v1"},"recipientGroupId":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\""},"messageBody":{"type":"String","example":"\"hello\""},"attachments":{"list":true,"type":"JsonAttachment","version":"v1"},"quote":{"type":"JsonQuote","version":"v1"},"timestamp":{"type":"Long"},"mentions":{"list":true,"type":"JsonMention","version":"v1"},"previews":{"list":true,"type":"JsonPreview","version":"v1"},"members":{"list":true,"type":"JsonAddress","version":"v1","doc":"Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified"}}},"SendResponse":{"fields":{"results":{"list":true,"type":"JsonSendMessageResult","version":"v1"},"timestamp":{"type":"long","example":"1615576442475"}}},"NoSuchAccountError":{"fields":{"account":{"type":"String"},"message":{"type":"String"}},"error":true},"ServerNotFoundError":{"fields":{"uuid":{"type":"String"},"message":{"type":"String"}},"error":true},"InvalidProxyError":{"fields":{"message":{"type":"String"}},"error":true},"NoSendPermissionError":{"fields":{"message":{"type":"String"}},"error":true},"InvalidAttachmentError":{"fields":{"filename":{"type":"String"},"message":{"type":"String"}},"error":true},"InternalError":{"fields":{"exceptions":{"list":true,"type":"String"},"message":{"type":"String"}},"doc":"an internal error in signald has occurred. typically these are things that \"should never happen\" such as issues saving to the local disk, but it is also the default error type and may catch some things that should have their own error type. If you find tht your code is depending on the exception list for any particular behavior, please file an issue so we can pull those errors out to a separate error type: https://gitlab.com/signald/signald/-/issues/new","error":true},"InvalidRequestError":{"fields":{"message":{"type":"String"}},"error":true},"UnknownGroupError":{"fields":{"message":{"type":"String"}},"error":true},"RateLimitError":{"fields":{"message":{"type":"String"}},"error":true},"InvalidRecipientError":{"fields":{"message":{"type":"String"}},"error":true},"AttachmentTooLargeError":{"fields":{"filename":{"type":"String"},"message":{"type":"String"}},"error":true},"AuthorizationFailedError":{"fields":{"message":{"type":"String"}},"doc":"Indicates the server rejected our credentials or a failed group update. Typically means the linked device was removed by the primary device, or that the account was re-registered. For group updates, this can indicate that we lack permissions.","error":true},"SQLError":{"fields":{"message":{"type":"String"}},"error":true},"ReactRequest":{"fields":{"username":{"type":"String","example":"\"+12024561414\"","required":true},"recipientAddress":{"type":"JsonAddress","version":"v1"},"recipientGroupId":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\""},"reaction":{"type":"JsonReaction","version":"v1","required":true},"timestamp":{"type":"long"},"members":{"list":true,"type":"JsonAddress","version":"v1","doc":"Optionally set to a sub-set of group members. Ignored if recipientGroupId isn't specified"}},"doc":"react to a previous message"},"UnregisteredUserError":{"fields":{"message":{"type":"String"},"e164_number":{"type":"String"}},"error":true},"VersionRequest":{"fields":{}},"JsonVersionMessage":{"fields":{"name":{"type":"String","example":"\"signald\""},"version":{"type":"String","example":"\"unversioned\""},"branch":{"type":"String","example":"\"\""},"commit":{"type":"String","example":"\"\""}}},"AcceptInvitationRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"groupID":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"","required":true}},"doc":"Accept a v2 group invitation. Note that you must have a profile name set to join groups."},"JsonGroupV2Info":{"fields":{"id":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\""},"revision":{"type":"int","example":"5"},"title":{"type":"String","example":"\"Parkdale Run Club\""},"description":{"type":"String"},"avatar":{"type":"String","doc":"path to the group's avatar on local disk, if available","example":"\"/var/lib/signald/avatars/group-EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\""},"timer":{"type":"int","example":"604800"},"members":{"list":true,"type":"JsonAddress","version":"v1"},"pendingMembers":{"list":true,"type":"JsonAddress","version":"v1"},"requestingMembers":{"list":true,"type":"JsonAddress","version":"v1"},"inviteLink":{"type":"String","doc":"the signal.group link, if applicable"},"accessControl":{"type":"GroupAccessControl","version":"v1","doc":"current access control settings for this group"},"memberDetail":{"list":true,"type":"GroupMember","version":"v1","doc":"detailed member list"},"pendingMemberDetail":{"list":true,"type":"GroupMember","version":"v1","doc":"detailed pending member list"},"announcements":{"type":"String","doc":"indicates if the group is an announcements group. Only admins are allowed to send messages to announcements groups. Options are UNKNOWN, ENABLED or DISABLED"},"removed":{"type":"boolean","doc":"will be set to true for incoming messages to indicate the user has been removed from the group"},"banned_members":{"list":true,"type":"BannedGroupMember","version":"v1"},"group_change":{"type":"GroupChange","version":"v1","doc":"Represents a peer-to-peer group change done by a user. Will not be set if the group change signature fails verification. This is usually only set inside of incoming messages."}},"doc":"Information about a Signal group"},"OwnProfileKeyDoesNotExistError":{"fields":{"message":{"type":"String"}},"error":true},"GroupPatchNotAcceptedError":{"fields":{"message":{"type":"String"}},"doc":"Indicates the server rejected our group update. This can be due to errors such as trying to add a user that's already in the group.","error":true},"ApproveMembershipRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"groupID":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"","required":true},"members":{"list":true,"type":"JsonAddress","version":"v1","doc":"list of requesting members to approve","required":true}},"doc":"approve a request to join a group"},"GroupVerificationError":{"fields":{"message":{"type":"String"}},"error":true},"GetGroupRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"groupID":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"","required":true},"revision":{"type":"int","doc":"the latest known revision, default value (-1) forces fetch from server"}},"doc":"Query the server for the latest state of a known group. If the account is not a member of the group, an UnknownGroupError is returned."},"InvalidGroupStateError":{"fields":{"message":{"type":"String"}},"error":true},"GetLinkedDevicesRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true}},"doc":"list all linked devices on a Signal account"},"LinkedDevices":{"fields":{"devices":{"list":true,"type":"DeviceInfo","version":"v1"}}},"JoinGroupRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"uri":{"type":"String","doc":"The signal.group URL","example":"\"https://signal.group/#CjQKINH_GZhXhfifTcnBkaKTNRxW-hHKnGSq-cJNyPVqHRp8EhDUB7zjKNEl0NaULhsqJCX3\"","required":true}},"doc":"Join a group using the a signal.group URL. Note that you must have a profile name set to join groups."},"JsonGroupJoinInfo":{"fields":{"groupID":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\""},"title":{"type":"String","example":"\"Parkdale Run Club\""},"description":{"type":"String","example":"\"A club for running in Parkdale\""},"memberCount":{"type":"int","example":"3"},"addFromInviteLink":{"type":"int","doc":"The access level required in order to join the group from the invite link, as an AccessControl.AccessRequired enum from the upstream Signal groups.proto file. This is UNSATISFIABLE (4) when the group link is disabled; ADMINISTRATOR (3) when the group link is enabled, but an administrator must approve new members; and ANY (1) when the group link is enabled and no approval is required. See theGroupAccessControl structure and the upstream enum ordinals."},"revision":{"type":"int","doc":"The Group V2 revision. This is incremented by clients whenever they update group information, and it is often used by clients to determine if the local group state is out-of-date with the server's revision.","example":"5"},"pendingAdminApproval":{"type":"boolean","doc":"Whether the account is waiting for admin approval in order to be added to the group."}}},"InvalidInviteURIError":{"fields":{"message":{"type":"String"}},"error":true},"GroupNotActiveError":{"fields":{"message":{"type":"String"}},"error":true},"RemoveLinkedDeviceRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"+12024561414\"","required":true},"deviceId":{"type":"long","doc":"the ID of the device to unlink","example":"3","required":true}},"doc":"Remove a linked device from the Signal account. Only allowed when the local device id is 1"},"UpdateGroupRequest":{"fields":{"account":{"type":"String","doc":"The identifier of the account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"groupID":{"type":"String","doc":"the ID of the group to update","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"","required":true},"title":{"type":"String","example":"\"Parkdale Run Club\""},"description":{"type":"String","doc":"A new group description. Set to empty string to remove an existing description.","example":"\"A club for running in Parkdale\""},"avatar":{"type":"String","example":"\"/tmp/image.jpg\""},"updateTimer":{"type":"int","doc":"update the group timer."},"addMembers":{"list":true,"type":"JsonAddress","version":"v1"},"removeMembers":{"list":true,"type":"JsonAddress","version":"v1"},"updateRole":{"type":"GroupMember","version":"v1"},"updateAccessControl":{"type":"GroupAccessControl","version":"v1","doc":"note that only one of the access controls may be updated per request"},"resetLink":{"type":"boolean","doc":"regenerate the group link password, invalidating the old one"},"announcements":{"type":"String","doc":"ENABLED to only allow admins to post messages, DISABLED to allow anyone to post"}},"doc":"modify a group. Note that only one modification action may be performed at once"},"GroupInfo":{"fields":{"v1":{"type":"JsonGroupInfo","version":"v1"},"v2":{"type":"JsonGroupV2Info","version":"v1"}},"doc":"A generic type that is used when the group version is not known"},"UnsupportedGroupError":{"fields":{"message":{"type":"String"}},"doc":"returned in response to use v1 groups, which are no longer supported","error":true},"SetProfile":{"fields":{"account":{"type":"String","doc":"The phone number of the account to use","example":"\"+12024561414\"","required":true},"name":{"type":"String","doc":"Change the profile name","example":"\"signald user\""},"avatarFile":{"type":"String","doc":"Path to new profile avatar file. If unset or null, unset the profile avatar","example":"\"/tmp/image.jpg\""},"about":{"type":"String","doc":"Change the 'about' profile field"},"emoji":{"type":"String","doc":"Change the profile emoji"},"mobilecoin_address":{"type":"String","doc":"Change the profile payment address. Payment address must be a *base64-encoded* MobileCoin address. Note that this is not the traditional MobileCoin address encoding, which is custom. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side."},"visible_badge_ids":{"list":true,"type":"String","doc":"configure visible badge IDs"}}},"InvalidBase64Error":{"fields":{"message":{"type":"String"}},"error":true},"ResolveAddressRequest":{"fields":{"account":{"type":"String","doc":"The signal account to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"partial":{"type":"JsonAddress","version":"v1","doc":"The partial address, missing fields","required":true}},"doc":"Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns."},"JsonAddress":{"fields":{"number":{"type":"String","doc":"An e164 phone number, starting with +. Currently the only available user-facing Signal identifier.","example":"\"+13215551234\""},"uuid":{"type":"UUID","doc":"A UUID, the unique identifier for a particular Signal account."},"relay":{"type":"String"}}},"MarkReadRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"to":{"type":"JsonAddress","version":"v1","doc":"The address that sent the message being marked as read","required":true},"timestamps":{"list":true,"type":"Long","doc":"List of messages to mark as read","example":"1615576442475","required":true},"when":{"type":"Long"}}},"ProofRequiredError":{"fields":{"token":{"type":"String"},"options":{"list":true,"type":"String","doc":"possible list values are RECAPTCHA and PUSH_CHALLENGE"},"message":{"type":"String"},"retry_after":{"type":"long","doc":"value in seconds"}},"error":true},"GetProfileRequest":{"fields":{"account":{"type":"String","doc":"the signald account to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"async":{"type":"boolean","doc":"if true, return results from local store immediately, refreshing from server in the background if needed. if false (default), block until profile can be retrieved from server"},"address":{"type":"JsonAddress","version":"v1","doc":"the address to look up","required":true}},"doc":"Get all information available about a user"},"Profile":{"fields":{"name":{"type":"String","doc":"The user's name from local contact names if available, or if not in contact list their Signal profile name"},"avatar":{"type":"String","doc":"path to avatar on local disk"},"address":{"type":"JsonAddress","version":"v1"},"capabilities":{"type":"Capabilities","version":"v1"},"color":{"type":"String","doc":"color of the chat with this user"},"about":{"type":"String"},"emoji":{"type":"String"},"contact_name":{"type":"String","doc":"The user's name from local contact names"},"profile_name":{"type":"String","doc":"The user's Signal profile name"},"inbox_position":{"type":"int"},"expiration_time":{"type":"int"},"mobilecoin_address":{"type":"String","doc":"*base64-encoded* mobilecoin address. Note that this is not the traditional MobileCoin address encoding. Clients are responsible for converting between MobileCoin's custom base58 on the user-facing side and base64 encoding on the signald side. If unset, null or an empty string, will empty the profile payment address"},"visible_badge_ids":{"list":true,"type":"String","doc":"currently unclear how these work, as they are not available in the production Signal apps"}},"doc":"Information about a Signal user"},"ProfileUnavailableError":{"fields":{"message":{"type":"String"}},"error":true},"ListGroupsRequest":{"fields":{"account":{"type":"String","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true}}},"GroupList":{"fields":{"groups":{"list":true,"type":"JsonGroupV2Info","version":"v1"},"legacyGroups":{"list":true,"type":"JsonGroupInfo","version":"v1","doc":"list of legacy (v1) groups, no longer supported (will always be empty)"}}},"ListContactsRequest":{"fields":{"account":{"type":"String","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"async":{"type":"boolean","doc":"return results from local store immediately, refreshing from server afterward if needed. If false (default), block until all pending profiles have been retrieved."}}},"ProfileList":{"fields":{"profiles":{"list":true,"type":"Profile","version":"v1"}}},"CreateGroupRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"title":{"type":"String","example":"\"Parkdale Run Club\"","required":true},"avatar":{"type":"String","example":"\"/tmp/image.jpg\""},"members":{"list":true,"type":"JsonAddress","version":"v1","required":true},"timer":{"type":"int","doc":"the message expiration timer"},"member_role":{"type":"String","doc":"The role of all members other than the group creator. Options are ADMINISTRATOR or DEFAULT (case insensitive)","example":"\"ADMINISTRATOR\""}}},"NoKnownUUIDError":{"fields":{"message":{"type":"String"}},"error":true},"LeaveGroupRequest":{"fields":{"account":{"type":"String","doc":"The account to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"groupID":{"type":"String","doc":"The group to leave","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"","required":true}}},"GenerateLinkingURIRequest":{"fields":{"server":{"type":"String","doc":"The identifier of the server to use. Leave blank for default (usually Signal production servers but configurable at build time)"}},"doc":"Generate a linking URI. Typically this is QR encoded and scanned by the primary device. Submit the returned session_id with a finish_link request."},"LinkingURI":{"fields":{"uri":{"type":"String"},"session_id":{"type":"String"}}},"FinishLinkRequest":{"fields":{"overwrite":{"type":"boolean","doc":"overwrite existing account data if the phone number conflicts. false by default"},"device_name":{"type":"String"},"session_id":{"type":"String"}},"doc":"After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device and the new account is setup. Note that the account setup process can sometimes take some time, if rapid userfeedback is required after scanning, use wait_for_scan first, then finish setup with finish_link."},"Account":{"fields":{"address":{"type":"JsonAddress","version":"v1","doc":"The address of this account"},"pending":{"type":"Boolean","doc":"indicates the account has not completed registration"},"pni":{"type":"String"},"device_id":{"type":"int","doc":"The Signal device ID. Official Signal mobile clients (iPhone and Android) have device ID = 1, while linked devices such as Signal Desktop or Signal iPad have higher device IDs."},"account_id":{"type":"String","doc":"The primary identifier on the account, included with all requests to signald for this account. Previously called 'username'"}},"doc":"A local account in signald"},"NoSuchSessionError":{"fields":{"message":{"type":"String"}},"error":true},"UserAlreadyExistsError":{"fields":{"uuid":{"type":"UUID"},"message":{"type":"String"}},"error":true},"ScanTimeoutError":{"fields":{"message":{"type":"String"}},"error":true},"AddLinkedDeviceRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"uri":{"type":"String","doc":"the sgnl://linkdevice uri provided (typically in qr code form) by the new device","example":"\"sgnl://linkdevice?uuid=jAaZ5lxLfh7zVw5WELd6-Q&pub_key=BfFbjSwmAgpVJBXUdfmSgf61eX3a%2Bq9AoxAVpl1HUap9\"","required":true}},"doc":"Link a new device to a local Signal account"},"RegisterRequest":{"fields":{"account":{"type":"String","doc":"the e164 phone number to register with","example":"\"+12024561414\"","required":true},"voice":{"type":"boolean","doc":"set to true to request a voice call instead of an SMS for verification"},"captcha":{"type":"String","doc":"See https://signald.org/articles/captcha/"},"server":{"type":"String","doc":"The identifier of the server to use. Leave blank for default (usually Signal production servers but configurable at build time)"}},"doc":"begin the account registration process by requesting a phone number verification code. when the code is received, submit it with a verify request"},"CaptchaRequiredError":{"fields":{"more":{"type":"String"},"message":{"type":"String"}},"error":true},"VerifyRequest":{"fields":{"account":{"type":"String","doc":"the e164 phone number being verified","example":"\"+12024561414\"","required":true},"code":{"type":"String","doc":"the verification code, dash (-) optional","example":"\"555555\"","required":true}},"doc":"verify an account's phone number with a code after registering, completing the account creation process"},"AccountHasNoKeysError":{"fields":{"message":{"type":"String"}},"error":true},"AccountAlreadyVerifiedError":{"fields":{"message":{"type":"String"}},"error":true},"AccountLockedError":{"fields":{"more":{"type":"String"},"message":{"type":"String"}},"error":true},"GetIdentitiesRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"address":{"type":"JsonAddress","version":"v1","doc":"address to get keys for","required":true}},"doc":"Get information about a known keys for a particular address"},"IdentityKeyList":{"fields":{"address":{"type":"JsonAddress","version":"v1"},"identities":{"list":true,"type":"IdentityKey","version":"v1"}},"doc":"a list of identity keys associated with a particular address"},"TrustRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"address":{"type":"JsonAddress","version":"v1","doc":"The user to query identity keys for","required":true},"safety_number":{"type":"String","doc":"required if qr_code_data is absent","example":"\"373453558586758076680580548714989751943247272727416091564451\""},"qr_code_data":{"type":"String","doc":"base64-encoded QR code data. required if safety_number is absent"},"trust_level":{"type":"String","doc":"One of TRUSTED_UNVERIFIED, TRUSTED_VERIFIED or UNTRUSTED. Default is TRUSTED_VERIFIED","example":"\"TRUSTED_VERIFIED\""}},"doc":"Trust another user's safety number using either the QR code data or the safety number text"},"FingerprintVersionMismatchError":{"fields":{"message":{"type":"String"}},"error":true},"UnknownIdentityKeyError":{"fields":{"message":{"type":"String"}},"error":true},"InvalidFingerprintError":{"fields":{"message":{"type":"String"}},"error":true},"DeleteAccountRequest":{"fields":{"account":{"type":"String","doc":"The account to delete","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"server":{"type":"boolean","doc":"delete account information from the server as well (default false)"}},"doc":"delete all account data signald has on disk, and optionally delete the account from the server as well. Note that this is not \"unlink\" and will delete the entire account, even from a linked device."},"TypingRequest":{"fields":{"account":{"type":"String","doc":"The account to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"address":{"type":"JsonAddress","version":"v1"},"group":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\""},"typing":{"type":"boolean","example":"true","required":true},"when":{"type":"long"}},"doc":"send a typing started or stopped message"},"InvalidGroupError":{"fields":{"message":{"type":"String"}},"error":true},"ResetSessionRequest":{"fields":{"account":{"type":"String","doc":"The account to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"address":{"type":"JsonAddress","version":"v1","doc":"the user to reset session with","required":true},"timestamp":{"type":"Long"}},"doc":"reset a session with a particular user"},"RequestSyncRequest":{"fields":{"groups":{"type":"boolean","doc":"request group sync (default true)"},"configuration":{"type":"boolean","doc":"request configuration sync (default true)"},"contacts":{"type":"boolean","doc":"request contact sync (default true)"},"blocked":{"type":"boolean","doc":"request block list sync (default true)"},"keys":{"type":"boolean","doc":"request storage service keys"},"account":{"type":"String","doc":"The account to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true}},"doc":"Request other devices on the account send us their group list, syncable config and contact list."},"ListAccountsRequest":{"fields":{},"doc":"return all local accounts"},"AccountList":{"fields":{"accounts":{"list":true,"type":"Account","version":"v1"}}},"GroupLinkInfoRequest":{"fields":{"account":{"type":"String","doc":"The account to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"uri":{"type":"String","doc":"the signald.group link","example":"\"https://signal.group/#CjQKINH_GZhXhfifTcnBkaKTNRxW-hHKnGSq-cJNyPVqHRp8EhDUB7zjKNEl0NaULhsqJCX3\"","required":true}},"doc":"Get information about a group from a signal.group link"},"GroupLinkNotActiveError":{"fields":{"message":{"type":"String"}},"error":true},"UpdateContactRequest":{"fields":{"account":{"type":"String","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"address":{"type":"JsonAddress","version":"v1","required":true},"name":{"type":"String"},"color":{"type":"String"},"inbox_position":{"type":"int"}},"doc":"update information about a local contact"},"SetExpirationRequest":{"fields":{"account":{"type":"String","doc":"The account to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"address":{"type":"JsonAddress","version":"v1"},"group":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\""},"expiration":{"type":"int","example":"604800","required":true}},"doc":"Set the message expiration timer for a thread. Expiration must be specified in seconds, set to 0 to disable timer"},"SetDeviceNameRequest":{"fields":{"account":{"type":"String","doc":"The account to set the device name of","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"device_name":{"type":"String","doc":"The device name"}},"doc":"set this device's name. This will show up on the mobile device on the same account under settings -> linked devices"},"GetAllIdentities":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"+12024561414\"","required":true}},"doc":"get all known identity keys"},"AllIdentityKeyList":{"fields":{"identity_keys":{"list":true,"type":"IdentityKeyList","version":"v1"}}},"SubscribeRequest":{"fields":{"account":{"type":"String","doc":"The account to subscribe to incoming message for","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true}},"doc":"receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages."},"UnsubscribeRequest":{"fields":{"account":{"type":"String","doc":"The account to unsubscribe from","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true}},"doc":"See subscribe for more info"},"RemoteDeleteRequest":{"fields":{"account":{"type":"String","doc":"the account to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"address":{"type":"JsonAddress","version":"v1","doc":"the address to send the delete message to. should match address the message to be deleted was sent to. required if group is not set."},"group":{"type":"String","doc":"the group to send the delete message to. should match group the message to be deleted was sent to. required if address is not set.","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\""},"timestamp":{"type":"long","required":true},"members":{"list":true,"type":"JsonAddress","version":"v1","doc":"Optionally set to a sub-set of group members. Ignored if group isn't specified"}},"doc":"delete a message previously sent"},"AddServerRequest":{"fields":{"server":{"type":"Server","version":"v1","required":true}},"doc":"add a new server to connect to. Returns the new server's UUID."},"GetServersRequest":{"fields":{}},"ServerList":{"fields":{"servers":{"list":true,"type":"Server","version":"v1"}}},"RemoveServerRequest":{"fields":{"uuid":{"type":"String"}}},"SendPaymentRequest":{"fields":{"account":{"type":"String","doc":"the account to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"address":{"type":"JsonAddress","version":"v1","doc":"the address to send the payment message to","required":true},"payment":{"type":"Payment","version":"v1","required":true},"when":{"type":"Long"}},"doc":"send a mobilecoin payment"},"RemoteConfigRequest":{"fields":{"account":{"type":"String","doc":"The account to use to retrieve the remote config","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true}},"doc":"Retrieves the remote config (feature flags) from the server."},"RemoteConfigList":{"fields":{"config":{"list":true,"type":"RemoteConfig","version":"v1"}}},"RefuseMembershipRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"members":{"list":true,"type":"JsonAddress","version":"v1","doc":"list of requesting members to refuse","required":true},"group_id":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"","required":true},"also_ban":{"type":"boolean"}},"doc":"deny a request to join a group"},"SubmitChallengeRequest":{"fields":{"account":{"type":"String","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"challenge":{"type":"String","required":true},"captcha_token":{"type":"String"}}},"IsIdentifierRegisteredRequest":{"fields":{"account":{"type":"String","doc":"The account to use to use","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"identifier":{"type":"String","doc":"The UUID of an identifier to check if it is registered on Signal. This UUID is either a Phone Number Identity (PNI) or an Account Identity (ACI).","example":"\"aeed01f0-a234-478e-8cf7-261c283151e7\"","required":true}},"doc":"Determine whether an account identifier is registered on the Signal service."},"BooleanMessage":{"fields":{"value":{"type":"boolean"}},"doc":"A message containing a single boolean, usually as a response"},"WaitForScanRequest":{"fields":{"session_id":{"type":"String"}},"doc":"An optional part of the linking process. Intended to be called after displaying the QR code, will return quickly after the user scans the QR code. finish_link must be called after wait_for_scan returns a non-error"},"GetGroupRevisionPagesRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"group_id":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"","required":true},"from_revision":{"type":"int","doc":"The revision to start the pages from. Note that if this is lower than the revision you joined the group, an AuthorizationFailedError is returned.","required":true},"include_first_revision":{"type":"boolean","doc":"Whether to include the first state in the returned pages (default false)"}},"doc":"Query the server for group revision history. The history contains information about the changes between each revision and the user that made the change."},"GroupHistoryPage":{"fields":{"results":{"list":true,"type":"GroupHistoryEntry","version":"v1"},"paging_data":{"type":"PagingData","version":"v1"}},"doc":"The result of fetching a group's history along with paging data."},"SendSyncMessageRequest":{"fields":{"account":{"type":"String","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"view_once_open_message":{"type":"JsonViewOnceOpenMessage","version":"v1","doc":"This can be set to indicate to other devices about having viewed a view-once message."},"message_request_response":{"type":"JsonMessageRequestResponseMessage","version":"v1","doc":"This can be set to indicate to other devices about a response to an incoming message request from an unknown user or group. Warning: Using the BLOCK and BLOCK_AND_DELETE options relies on other devices to do the blocking, and it does not make you leave the group!"}},"doc":"Sends a sync message to the account's devices"},"JsonSendMessageResult":{"fields":{"address":{"type":"JsonAddress","version":"v1"},"success":{"type":"SendSuccess","version":"v1"},"networkFailure":{"type":"boolean","example":"false"},"unregisteredFailure":{"type":"boolean","example":"false"},"identityFailure":{"type":"String"},"proof_required_failure":{"type":"ProofRequiredError","version":"v1"}}},"BanUserRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"group_id":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"","required":true},"users":{"list":true,"type":"JsonAddress","version":"v1","doc":"List of users to ban","required":true}},"doc":"Bans users from a group. This works even if the users aren't in the group. If they are currently in the group, they will also be removed."},"UnbanUserRequest":{"fields":{"account":{"type":"String","doc":"The account to interact with","example":"\"0cc10e61-d64c-4dbc-b51c-334f7dd45a4a\"","required":true},"group_id":{"type":"String","example":"\"EdSqI90cS0UomDpgUXOlCoObWvQOXlH5G3Z2d3f4ayE=\"","required":true},"users":{"list":true,"type":"JsonAddress","version":"v1","doc":"List of users to unban","required":true}},"doc":"Unbans users from a group."},"IdentityKey":{"fields":{"added":{"type":"long","doc":"the first time this identity key was seen"},"safety_number":{"type":"String","example":"\"373453558586758076680580548714989751943247272727416091564451\""},"qr_code_data":{"type":"String","doc":"base64-encoded QR code data"},"trust_level":{"type":"String","doc":"One of TRUSTED_UNVERIFIED, TRUSTED_VERIFIED or UNTRUSTED"}}},"JsonDataMessage":{"fields":{"timestamp":{"type":"long","doc":"the timestamp that the message was sent at, according to the sender's device. This is used to uniquely identify this message for things like reactions and quotes.","example":"1615576442475"},"attachments":{"list":true,"type":"JsonAttachment","version":"v1","doc":"files attached to the incoming message"},"body":{"type":"String","doc":"the text body of the incoming message.","example":"\"hello\""},"group":{"type":"JsonGroupInfo","version":"v1","doc":"if the incoming message was sent to a v1 group, information about that group will be here"},"groupV2":{"type":"JsonGroupV2Info","version":"v1","doc":"if the incoming message was sent to a v2 group, basic identifying information about that group will be here. If group information changes, JsonGroupV2Info.revision is incremented. If the group revision is higher than previously seen, a client can retrieve the group information by calling get_group."},"endSession":{"type":"boolean"},"expiresInSeconds":{"type":"int","doc":"the expiry timer on the incoming message. Clients should delete records of the message within this number of seconds"},"profileKeyUpdate":{"type":"boolean"},"quote":{"type":"JsonQuote","version":"v1","doc":"if the incoming message is a quote or reply to another message, this will contain information about that message"},"contacts":{"list":true,"type":"SharedContact","version":"v1","doc":"if the incoming message has a shared contact, the contact's information will be here"},"previews":{"list":true,"type":"JsonPreview","version":"v1","doc":"if the incoming message has a link preview, information about that preview will be here"},"sticker":{"type":"JsonSticker","version":"v0","doc":"if the incoming message is a sticker, information about the sicker will be here"},"viewOnce":{"type":"boolean","doc":"indicates the message is a view once message. View once messages typically include no body and a single image attachment. Official Signal clients will prevent the user from saving the image, and once the user has viewed the image once they will destroy the image."},"reaction":{"type":"JsonReaction","version":"v1","doc":"if the message adds or removes a reaction to another message, this will indicate what change is being made"},"remoteDelete":{"type":"RemoteDelete","version":"v1","doc":"if the inbound message is deleting a previously sent message, indicates which message should be deleted"},"mentions":{"list":true,"type":"JsonMention","version":"v1","doc":"list of mentions in the message"},"payment":{"type":"Payment","version":"v1","doc":"details about the MobileCoin payment attached to the message, if present"},"is_expiration_update":{"type":"boolean","doc":"whether or not this message changes the expiresInSeconds value for the whole chat. Some messages (remote deletes, reactions, etc) will have expiresInSeconds=0 even though the chat has disappearing messages enabled."},"group_call_update":{"type":"String","doc":"the eraId string from a group call message update"},"story_context":{"type":"StoryContext","version":"v1"}}},"JsonSyncMessage":{"fields":{"sent":{"type":"JsonSentTranscriptMessage","version":"v1"},"contacts":{"type":"JsonAttachment","version":"v1"},"contactsComplete":{"type":"boolean"},"groups":{"type":"JsonAttachment","version":"v1"},"blockedList":{"type":"JsonBlockedListMessage","version":"v1"},"request":{"type":"String"},"readMessages":{"list":true,"type":"JsonReadMessage","version":"v1"},"viewOnceOpen":{"type":"JsonViewOnceOpenMessage","version":"v1"},"verified":{"type":"JsonVerifiedMessage","version":"v1"},"configuration":{"type":"ConfigurationMessage","version":"v0"},"stickerPackOperations":{"list":true,"type":"JsonStickerPackOperationMessage","version":"v0"},"fetchType":{"type":"String"},"messageRequestResponse":{"type":"JsonMessageRequestResponseMessage","version":"v1"}}},"CallMessage":{"fields":{"offer_message":{"type":"OfferMessage","version":"v1"},"answer_message":{"type":"AnswerMessage","version":"v1"},"busy_message":{"type":"BusyMessage","version":"v1"},"hangup_message":{"type":"HangupMessage","version":"v1"},"ice_update_message":{"list":true,"type":"IceUpdateMessage","version":"v1"},"destination_device_id":{"type":"int"},"multi_ring":{"type":"boolean"}}},"ReceiptMessage":{"fields":{"type":{"type":"String","doc":"options: UNKNOWN, DELIVERY, READ, VIEWED"},"timestamps":{"list":true,"type":"Long"},"when":{"type":"long"}}},"TypingMessage":{"fields":{"action":{"type":"String"},"timestamp":{"type":"long"},"group_id":{"type":"String"}}},"StoryMessage":{"fields":{"group":{"type":"JsonGroupV2Info","version":"v1"},"file":{"type":"JsonAttachment","version":"v1"},"text":{"type":"TextAttachment","version":"v1"},"allow_replies":{"type":"Boolean"}}},"JsonAttachment":{"fields":{"contentType":{"type":"String"},"id":{"type":"String"},"size":{"type":"int"},"storedFilename":{"type":"String","doc":"when receiving, the path that file has been downloaded to"},"filename":{"type":"String","doc":"when sending, the path to the local file to upload"},"customFilename":{"type":"String","doc":"the original name of the file"},"caption":{"type":"String"},"width":{"type":"int"},"height":{"type":"int"},"voiceNote":{"type":"boolean"},"key":{"type":"String"},"digest":{"type":"String"},"blurhash":{"type":"String"}},"doc":"represents a file attached to a message. When sending, only `filename` is required."},"JsonQuote":{"fields":{"id":{"type":"long","doc":"the client timestamp of the message being quoted","example":"1615576442475"},"author":{"type":"JsonAddress","version":"v1","doc":"the author of the message being quoted"},"text":{"type":"String","doc":"the body of the message being quoted","example":"\"hey  what's up?\""},"attachments":{"list":true,"type":"JsonQuotedAttachment","version":"v0","doc":"list of files attached to the quoted message"},"mentions":{"list":true,"type":"JsonMention","version":"v1","doc":"list of mentions in the quoted message"}},"doc":"A quote is a reply to a previous message. ID is the sent time of the message being replied to"},"JsonMention":{"fields":{"uuid":{"type":"String","doc":"The UUID of the account being mentioned","example":"\"aeed01f0-a234-478e-8cf7-261c283151e7\""},"start":{"type":"int","doc":"The number of characters in that the mention starts at. Note that due to a quirk of how signald encodes JSON, if this value is 0 (for example if the first character in the message is the mention) the field won't show up.","example":"4"},"length":{"type":"int","doc":"The length of the mention represented in the message. Seems to always be 1 but included here in case that changes.","example":"1"}}},"JsonPreview":{"fields":{"url":{"type":"String"},"title":{"type":"String"},"description":{"type":"String"},"date":{"type":"long"},"attachment":{"type":"JsonAttachment","version":"v1","doc":"an optional image file attached to the preview"}},"doc":"metadata about one of the links in a message"},"JsonReaction":{"fields":{"emoji":{"type":"String","doc":"the emoji to react with","example":"\"👍\""},"remove":{"type":"boolean","doc":"set to true to remove the reaction. requires emoji be set to previously reacted emoji"},"targetAuthor":{"type":"JsonAddress","version":"v1","doc":"the author of the message being reacted to"},"targetSentTimestamp":{"type":"long","doc":"the client timestamp of the message being reacted to","example":"1615576442475"}}},"GroupAccessControl":{"fields":{"link":{"type":"String","doc":"UNSATISFIABLE when the group link is disabled, ADMINISTRATOR when the group link is enabled but an administrator must approve new members, ANY when the group link is enabled and no approval is required","example":"\"ANY\""},"attributes":{"type":"String","doc":"who can edit group info"},"members":{"type":"String","doc":"who can add members"}},"doc":"group access control settings. Options for each controlled action are: UNKNOWN, ANY, MEMBER, ADMINISTRATOR, UNSATISFIABLE and UNRECOGNIZED"},"GroupMember":{"fields":{"uuid":{"type":"String","example":"\"aeed01f0-a234-478e-8cf7-261c283151e7\""},"role":{"type":"String","doc":"possible values are: UNKNOWN, DEFAULT, ADMINISTRATOR and UNRECOGNIZED","example":"\"DEFAULT\""},"joined_revision":{"type":"int"}}},"BannedGroupMember":{"fields":{"uuid":{"type":"String","example":"\"aeed01f0-a234-478e-8cf7-261c283151e7\""},"timestamp":{"type":"long","doc":"Timestamp as milliseconds since Unix epoch of when the user was banned. This field is set by the server."}}},"GroupChange":{"fields":{"editor":{"type":"JsonAddress","version":"v1","doc":"The user that made the change."},"revision":{"type":"int","doc":"The group revision that this change brings the group to."},"new_members":{"list":true,"type":"GroupMember","version":"v1","doc":"Represents users have been added to the group. This can be from group members adding users, or a users joining via a group link that required no approval."},"delete_members":{"list":true,"type":"JsonAddress","version":"v1","doc":"Represents users that have been removed from the group. This can be from admins removing users, or users choosing to leave the group"},"modify_member_roles":{"list":true,"type":"GroupMember","version":"v1","doc":"Represents users with their new, modified role."},"modified_profile_keys":{"list":true,"type":"GroupMember","version":"v1","doc":"Represents users that have rotated their profile key. Note that signald currently does not expose profile keys to clients. The joined revision property will always be 0 in this list."},"new_pending_members":{"list":true,"type":"GroupPendingMember","version":"v1","doc":"Represents a user that has been invited to the group by another user."},"delete_pending_members":{"list":true,"type":"JsonAddress","version":"v1"},"promote_pending_members":{"list":true,"type":"GroupMember","version":"v1"},"new_banned_members":{"list":true,"type":"BannedGroupMember","version":"v1"},"new_unbanned_members":{"list":true,"type":"BannedGroupMember","version":"v1"},"new_title":{"type":"String"},"new_avatar":{"type":"Boolean","doc":"Whether this group change changed the avatar."},"new_timer":{"type":"int","doc":"New disappearing messages timer value."},"new_access_control":{"type":"GroupAccessControl","version":"v1","doc":"If not null, then this group change modified one of the access controls. Some of the properties in here will be null."},"new_requesting_members":{"list":true,"type":"GroupRequestingMember","version":"v1","doc":"Represents users that have requested to join the group via the group link. Note that members requesting to join might not necessarily have the list of users in the group, so they won't be able to send a peer-to-peer group update message to inform users of their request to join. Other users in the group may inform us that the revision has increased, but the members requesting access will have to be obtained from the server instead (which signald will handle). For now, a get_group request has to be made to get the users that have requested to join the group."},"delete_requesting_members":{"list":true,"type":"JsonAddress","version":"v1"},"promote_requesting_members":{"list":true,"type":"GroupMember","version":"v1"},"new_invite_link_password":{"type":"Boolean","doc":"Whether this group change involved resetting the group invite link."},"new_description":{"type":"String"},"new_is_announcement_group":{"type":"String","doc":"Whether this change affected the announcement group setting. Possible values are UNKNOWN, ENABLED or DISABLED"}},"doc":"Represents a group change made by a user. This can also represent request link invites. Only the fields relevant to the group change performed will be set. Note that in signald, group changes are currently only received from incoming messages from a message subscription."},"DeviceInfo":{"fields":{"id":{"type":"long"},"name":{"type":"String"},"created":{"type":"long"},"lastSeen":{"type":"long"}}},"JsonGroupInfo":{"fields":{"groupId":{"type":"String"},"members":{"list":true,"type":"JsonAddress","version":"v1"},"name":{"type":"String"},"type":{"type":"String"},"avatarId":{"type":"long"}},"doc":"information about a legacy group"},"Capabilities":{"fields":{"gv2":{"type":"boolean","doc":"this capability is deprecated and will always be true"},"storage":{"type":"boolean"},"stories":{"type":"boolean"},"gv1-migration":{"type":"boolean"},"sender_key":{"type":"boolean"},"announcement_group":{"type":"boolean"},"change_number":{"type":"boolean"}}},"Server":{"fields":{"uuid":{"type":"UUID","doc":"A unique identifier for the server, referenced when adding accounts. Must be a valid UUID. Will be generated if not specified when creating."},"proxy":{"type":"String"},"ca":{"type":"String","doc":"base64 encoded trust store, password must be 'whisper'"},"service_url":{"type":"String"},"cdn_urls":{"list":true,"type":"ServerCDN","version":"v1"},"contact_discovery_url":{"type":"String"},"key_backup_url":{"type":"String"},"storage_url":{"type":"String"},"zk_param":{"type":"String","doc":"base64 encoded ZKGROUP_SERVER_PUBLIC_PARAMS value"},"unidentified_sender_root":{"type":"String","doc":"base64 encoded"},"key_backup_service_name":{"type":"String"},"key_backup_service_id":{"type":"String","doc":"base64 encoded"},"key_backup_mrenclave":{"type":"String"},"cds_mrenclave":{"type":"String"},"ias_ca":{"type":"String","doc":"base64 encoded trust store, password must be 'whisper'"}},"doc":"a Signal server"},"Payment":{"fields":{"receipt":{"type":"String","doc":"base64 encoded payment receipt data. This is a protobuf value which can be decoded as the Receipt object described in https://github.com/mobilecoinfoundation/mobilecoin/blob/master/api/proto/external.proto"},"note":{"type":"String","doc":"note attached to the payment"}},"doc":"details about a MobileCoin payment"},"RemoteConfig":{"fields":{"name":{"type":"String","doc":"The name of this remote config entry. These names may be prefixed with the platform type (\"android.\", \"ios.\", \"desktop.\", etc.) Typically, clients only handle the relevant configs for its platform, hardcoding the names it cares about handling and ignoring the rest.","example":"desktop.mediaQuality.levels"},"value":{"type":"String","doc":"The value for this remote config entry. Even though this is a string, it could be a boolean as a string, an integer/long value, a comma-delimited list, etc. Clients usually consume this by hardcoding the feature flagsit should track in the app and assuming that the server will send the type that the client expects. If an unexpected type occurs, it falls back to a default value.","example":"1:2,61:2,81:2,82:2,65:2,31:2,47:2,41:2,32:2,385:2,971:2,974:2,49:2,33:2,*:1"}},"doc":"A remote config (feature flag) entry."},"GroupHistoryEntry":{"fields":{"group":{"type":"JsonGroupV2Info","version":"v1"},"change":{"type":"GroupChange","version":"v1"}}},"PagingData":{"fields":{"has_more_pages":{"type":"boolean"},"next_page_revision":{"type":"int"}}},"JsonViewOnceOpenMessage":{"fields":{"sender":{"type":"JsonAddress","version":"v1"},"timestamp":{"type":"long","example":"1615576442475"}}},"JsonMessageRequestResponseMessage":{"fields":{"person":{"type":"JsonAddress","version":"v1"},"groupId":{"type":"String"},"type":{"type":"String","doc":"One of UNKNOWN, ACCEPT, DELETE, BLOCK, BLOCK_AND_DELETE, UNBLOCK_AND_ACCEPT"}},"doc":"Responses to message requests from unknown users or groups"},"SendSuccess":{"fields":{"unidentified":{"type":"boolean"},"needsSync":{"type":"boolean"},"duration":{"type":"long"},"devices":{"list":true,"type":"int"}}},"SharedContact":{"fields":{"name":{"type":"SharedContactName","version":"v1","doc":"the name of the shared contact"},"email":{"list":true,"type":"SharedContactEmail","version":"v1","doc":"the email addresses of the shared contact"},"phone":{"list":true,"type":"SharedContactPhone","version":"v1","doc":"the phone numbers of the shared contact"},"address":{"list":true,"type":"SharedContactAddress","version":"v1","doc":"the physical addresses of the shared contact"},"avatar":{"type":"SharedContactAvatar","version":"v1","doc":"the profile picture/avatar of the shared contact"},"organization":{"type":"String","doc":"the organization (e.g. workplace) of the shared contact"}}},"RemoteDelete":{"fields":{"target_sent_timestamp":{"type":"long"}}},"StoryContext":{"fields":{"author":{"type":"String"},"sent_timestamp":{"type":"long"}}},"JsonSentTranscriptMessage":{"fields":{"destination":{"type":"JsonAddress","version":"v1"},"timestamp":{"type":"long","example":"1615576442475"},"expirationStartTimestamp":{"type":"long"},"message":{"type":"JsonDataMessage","version":"v1"},"story":{"type":"StoryMessage","version":"v1"},"unidentifiedStatus":{"type":"Map"},"isRecipientUpdate":{"type":"boolean"}}},"JsonBlockedListMessage":{"fields":{"addresses":{"list":true,"type":"JsonAddress","version":"v1"},"groupIds":{"list":true,"type":"String"}}},"JsonReadMessage":{"fields":{"sender":{"type":"JsonAddress","version":"v1"},"timestamp":{"type":"long","example":"1615576442475"}}},"JsonVerifiedMessage":{"fields":{"destination":{"type":"JsonAddress","version":"v1"},"identityKey":{"type":"String"},"verified":{"type":"String"},"timestamp":{"type":"long"}}},"OfferMessage":{"fields":{"id":{"type":"long"},"sdp":{"type":"String"},"type":{"type":"String"},"opaque":{"type":"String"}}},"AnswerMessage":{"fields":{"id":{"type":"long"},"sdp":{"type":"String"},"opaque":{"type":"String"}}},"BusyMessage":{"fields":{"id":{"type":"long"}}},"HangupMessage":{"fields":{"id":{"type":"long"},"type":{"type":"String"},"legacy":{"type":"boolean"},"device_id":{"type":"int"}}},"IceUpdateMessage":{"fields":{"id":{"type":"long"},"opaque":{"type":"String"},"sdp":{"type":"String"}}},"TextAttachment":{"fields":{"text":{"type":"String"},"style":{"type":"String"},"preview":{"type":"JsonPreview","version":"v1"},"text_foreground_color":{"type":"String"},"text_background_color":{"type":"String"},"background_gradient":{"type":"Gradient","version":"v1"},"background_color":{"type":"String"}}},"GroupPendingMember":{"fields":{"uuid":{"type":"String","example":"\"aeed01f0-a234-478e-8cf7-261c283151e7\""},"role":{"type":"String","doc":"possible values are: UNKNOWN, DEFAULT, ADMINISTRATOR and UNRECOGNIZED","example":"\"DEFAULT\""},"timestamp":{"type":"long"},"added_by_uuid":{"type":"String","example":"\"aeed01f0-a234-478e-8cf7-261c283151e7\""}}},"GroupRequestingMember":{"fields":{"uuid":{"type":"String","example":"\"aeed01f0-a234-478e-8cf7-261c283151e7\""},"timestamp":{"type":"long"}}},"ServerCDN":{"fields":{"number":{"type":"int"},"url":{"type":"String"}}},"SharedContactName":{"fields":{"display":{"type":"String","doc":"the full name that should be displayed"},"given":{"type":"String","doc":"given name"},"middle":{"type":"String","doc":"middle name"},"family":{"type":"String","doc":"family name (surname)"},"prefix":{"type":"String"},"suffix":{"type":"String"}}},"SharedContactEmail":{"fields":{"type":{"type":"String","doc":"the type of email (options: HOME, WORK, MOBILE, CUSTOM)"},"value":{"type":"String","doc":"the email address"},"label":{"type":"String","doc":"the type label when type is CUSTOM"}}},"SharedContactPhone":{"fields":{"type":{"type":"String","doc":"the type of phone (options: HOME, WORK, MOBILE, CUSTOM)"},"value":{"type":"String","doc":"the phone number"},"label":{"type":"String","doc":"the type label when type is CUSTOM"}}},"SharedContactAddress":{"fields":{"type":{"type":"String","doc":"the type of address (options: HOME, WORK, CUSTOM)"},"label":{"type":"String"},"street":{"type":"String"},"pobox":{"type":"String"},"neighborhood":{"type":"String"},"city":{"type":"String"},"region":{"type":"String"},"postcode":{"type":"String"},"country":{"type":"String"}}},"SharedContactAvatar":{"fields":{"attachment":{"type":"JsonAttachment","version":"v1"},"is_profile":{"type":"boolean"}}},"Gradient":{"fields":{"start_color":{"type":"String"},"end_color":{"type":"String"}}}}},"actions":{"v1":{"send":{"request":"SendRequest","response":"SendResponse","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"NoSendPermissionError"},{"name":"InvalidAttachmentError"},{"name":"InternalError"},{"name":"InvalidRequestError"},{"name":"UnknownGroupError"},{"name":"RateLimitError"},{"name":"InvalidRecipientError"},{"name":"AttachmentTooLargeError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"react":{"request":"ReactRequest","response":"SendResponse","doc":"react to a previous message","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"InternalError"},{"name":"InvalidRecipientError"},{"name":"UnknownGroupError"},{"name":"InvalidRequestError"},{"name":"RateLimitError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"version":{"request":"VersionRequest","response":"JsonVersionMessage"},"accept_invitation":{"request":"AcceptInvitationRequest","response":"JsonGroupV2Info","doc":"Accept a v2 group invitation. Note that you must have a profile name set to join groups.","errors":[{"name":"NoSuchAccountError"},{"name":"OwnProfileKeyDoesNotExistError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"UnknownGroupError"},{"name":"InternalError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError","doc":"Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)."},{"name":"SQLError"},{"name":"GroupPatchNotAcceptedError","doc":"Caused when server rejects the group update."}]},"approve_membership":{"request":"ApproveMembershipRequest","response":"JsonGroupV2Info","doc":"approve a request to join a group","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"UnknownGroupError"},{"name":"InternalError"},{"name":"GroupVerificationError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError","doc":"Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)."},{"name":"SQLError"},{"name":"GroupPatchNotAcceptedError","doc":"Caused when server rejects the group update."}]},"get_group":{"request":"GetGroupRequest","response":"JsonGroupV2Info","doc":"Query the server for the latest state of a known group. If the account is not a member of the group, an UnknownGroupError is returned.","errors":[{"name":"NoSuchAccountError"},{"name":"UnknownGroupError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"InternalError"},{"name":"GroupVerificationError"},{"name":"InvalidGroupStateError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"get_linked_devices":{"request":"GetLinkedDevicesRequest","response":"LinkedDevices","doc":"list all linked devices on a Signal account","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"join_group":{"request":"JoinGroupRequest","response":"JsonGroupJoinInfo","doc":"Join a group using the a signal.group URL. Note that you must have a profile name set to join groups.","errors":[{"name":"InvalidRequestError"},{"name":"InvalidInviteURIError"},{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"OwnProfileKeyDoesNotExistError"},{"name":"GroupVerificationError"},{"name":"GroupNotActiveError"},{"name":"UnknownGroupError"},{"name":"InvalidGroupStateError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"remove_linked_device":{"request":"RemoveLinkedDeviceRequest","doc":"Remove a linked device from the Signal account. Only allowed when the local device id is 1","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"update_group":{"request":"UpdateGroupRequest","response":"GroupInfo","doc":"modify a group. Note that only one modification action may be performed at once","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"UnknownGroupError"},{"name":"GroupVerificationError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError","doc":"Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)."},{"name":"UnregisteredUserError"},{"name":"SQLError"},{"name":"GroupPatchNotAcceptedError","doc":"Caused when server rejects the group update, e.g. trying to add a user that's already in the group"},{"name":"UnsupportedGroupError"}]},"set_profile":{"request":"SetProfile","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"InvalidBase64Error"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"resolve_address":{"request":"ResolveAddressRequest","response":"JsonAddress","doc":"Resolve a partial JsonAddress with only a number or UUID to one with both. Anywhere that signald accepts a JsonAddress will except a partial, this is a convenience function for client authors, mostly because signald doesn't resolve all the partials it returns.","errors":[{"name":"InternalError"},{"name":"NoSuchAccountError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"mark_read":{"request":"MarkReadRequest","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"InternalError"},{"name":"UntrustedIdentityError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"},{"name":"InvalidRequestError"},{"name":"ProofRequiredError"}]},"get_profile":{"request":"GetProfileRequest","response":"Profile","doc":"Get all information available about a user","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"ProfileUnavailableError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"},{"name":"InvalidRequestError"}]},"list_groups":{"request":"ListGroupsRequest","response":"GroupList","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"list_contacts":{"request":"ListContactsRequest","response":"ProfileList","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"},{"name":"InvalidRequestError"}]},"create_group":{"request":"CreateGroupRequest","response":"JsonGroupV2Info","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"OwnProfileKeyDoesNotExistError"},{"name":"NoKnownUUIDError"},{"name":"InvalidRequestError"},{"name":"GroupVerificationError"},{"name":"InvalidGroupStateError"},{"name":"UnknownGroupError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"leave_group":{"request":"LeaveGroupRequest","response":"GroupInfo","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"InternalError"},{"name":"UnknownGroupError"},{"name":"GroupVerificationError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"generate_linking_uri":{"request":"GenerateLinkingURIRequest","response":"LinkingURI","doc":"Generate a linking URI. Typically this is QR encoded and scanned by the primary device. Submit the returned session_id with a finish_link request.","errors":[{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"}]},"finish_link":{"request":"FinishLinkRequest","response":"Account","doc":"After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device and the new account is setup. Note that the account setup process can sometimes take some time, if rapid userfeedback is required after scanning, use wait_for_scan first, then finish setup with finish_link.","errors":[{"name":"NoSuchSessionError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"InternalError"},{"name":"NoSuchAccountError"},{"name":"UserAlreadyExistsError"},{"name":"ScanTimeoutError"}]},"add_device":{"request":"AddLinkedDeviceRequest","doc":"Link a new device to a local Signal account","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"InvalidRequestError","doc":"caused by syntax errors with the provided linking URI"},{"name":"InternalError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"register":{"request":"RegisterRequest","response":"Account","doc":"begin the account registration process by requesting a phone number verification code. when the code is received, submit it with a verify request","errors":[{"name":"CaptchaRequiredError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"}]},"verify":{"request":"VerifyRequest","response":"Account","doc":"verify an account's phone number with a code after registering, completing the account creation process","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"AccountHasNoKeysError"},{"name":"AccountAlreadyVerifiedError"},{"name":"AccountLockedError"},{"name":"NoSuchAccountError"}]},"get_identities":{"request":"GetIdentitiesRequest","response":"IdentityKeyList","doc":"Get information about a known keys for a particular address","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"trust":{"request":"TrustRequest","doc":"Trust another user's safety number using either the QR code data or the safety number text","errors":[{"name":"InvalidRequestError"},{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"FingerprintVersionMismatchError"},{"name":"InvalidBase64Error"},{"name":"UnknownIdentityKeyError"},{"name":"InvalidFingerprintError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"delete_account":{"request":"DeleteAccountRequest","doc":"delete all account data signald has on disk, and optionally delete the account from the server as well. Note that this is not \"unlink\" and will delete the entire account, even from a linked device.","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"SQLError"}]},"typing":{"request":"TypingRequest","doc":"send a typing started or stopped message","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"InvalidRecipientError"},{"name":"InvalidGroupError"},{"name":"UntrustedIdentityError"},{"name":"UnknownGroupError"},{"name":"InvalidRequestError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"reset_session":{"request":"ResetSessionRequest","response":"SendResponse","doc":"reset a session with a particular user","errors":[{"name":"InternalError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"NoSuchAccountError"},{"name":"InvalidRequestError"},{"name":"UnknownGroupError"},{"name":"RateLimitError"},{"name":"InvalidRecipientError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"request_sync":{"request":"RequestSyncRequest","doc":"Request other devices on the account send us their group list, syncable config and contact list.","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"UntrustedIdentityError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"list_accounts":{"request":"ListAccountsRequest","response":"AccountList","doc":"return all local accounts","errors":[{"name":"NoSuchAccountError"},{"name":"InternalError"}]},"group_link_info":{"request":"GroupLinkInfoRequest","response":"JsonGroupJoinInfo","doc":"Get information about a group from a signal.group link","errors":[{"name":"GroupLinkNotActiveError"},{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"InvalidRequestError"},{"name":"GroupVerificationError"},{"name":"SQLError"}]},"update_contact":{"request":"UpdateContactRequest","response":"Profile","doc":"update information about a local contact","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"InternalError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"set_expiration":{"request":"SetExpirationRequest","response":"SendResponse","doc":"Set the message expiration timer for a thread. Expiration must be specified in seconds, set to 0 to disable timer","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"UnknownGroupError"},{"name":"GroupVerificationError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError","doc":"Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)."},{"name":"UnregisteredUserError"},{"name":"SQLError"},{"name":"GroupPatchNotAcceptedError","doc":"If updating a group, caused when server rejects the group update."},{"name":"UnsupportedGroupError"}]},"set_device_name":{"request":"SetDeviceNameRequest","doc":"set this device's name. This will show up on the mobile device on the same account under settings -> linked devices","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"},{"name":"InvalidRequestError"}]},"get_all_identities":{"request":"GetAllIdentities","response":"AllIdentityKeyList","doc":"get all known identity keys","errors":[{"name":"InvalidProxyError"},{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InternalError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"subscribe":{"request":"SubscribeRequest","doc":"receive incoming messages. After making a subscribe request, incoming messages will be sent to the client encoded as ClientMessageWrapper. Send an unsubscribe request or disconnect from the socket to stop receiving messages.","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"InternalError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"unsubscribe":{"request":"UnsubscribeRequest","doc":"See subscribe for more info","errors":[{"name":"NoSuchAccountError"},{"name":"InternalError"},{"name":"SQLError"}]},"remote_delete":{"request":"RemoteDeleteRequest","response":"SendResponse","doc":"delete a message previously sent","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"InvalidRecipientError"},{"name":"UnknownGroupError"},{"name":"InvalidRequestError"},{"name":"RateLimitError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"add_server":{"request":"AddServerRequest","response":"String","doc":"add a new server to connect to. Returns the new server's UUID.","errors":[{"name":"InvalidProxyError"},{"name":"InternalError"}]},"get_servers":{"request":"GetServersRequest","response":"ServerList","errors":[{"name":"InternalError"}]},"delete_server":{"request":"RemoveServerRequest","errors":[{"name":"InternalError"}]},"send_payment":{"request":"SendPaymentRequest","response":"SendResponse","doc":"send a mobilecoin payment","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"InvalidBase64Error"},{"name":"InvalidRecipientError"},{"name":"UnknownGroupError"},{"name":"InvalidRequestError"},{"name":"RateLimitError"},{"name":"UnregisteredUserError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"get_remote_config":{"request":"RemoteConfigRequest","response":"RemoteConfigList","doc":"Retrieves the remote config (feature flags) from the server.","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"SQLError"}]},"refuse_membership":{"request":"RefuseMembershipRequest","response":"JsonGroupV2Info","doc":"deny a request to join a group","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"UnknownGroupError"},{"name":"GroupVerificationError"},{"name":"InternalError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError","doc":"Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)."},{"name":"UnregisteredUserError"},{"name":"SQLError"},{"name":"GroupPatchNotAcceptedError","doc":"Caused when server rejects the group update."}]},"submit_challenge":{"request":"SubmitChallengeRequest","errors":[{"name":"NoSuchAccountError"},{"name":"InvalidRequestError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"InternalError"},{"name":"SQLError"}]},"is_identifier_registered":{"request":"IsIdentifierRegisteredRequest","response":"BooleanMessage","doc":"Determine whether an account identifier is registered on the Signal service.","errors":[{"name":"InternalError"},{"name":"InvalidProxyError"},{"name":"ServerNotFoundError"},{"name":"NoSuchAccountError"},{"name":"SQLError"}]},"wait_for_scan":{"request":"WaitForScanRequest","doc":"An optional part of the linking process. Intended to be called after displaying the QR code, will return quickly after the user scans the QR code. finish_link must be called after wait_for_scan returns a non-error","errors":[{"name":"NoSuchSessionError"},{"name":"ScanTimeoutError"},{"name":"InternalError"}]},"get_group_revision_pages":{"request":"GetGroupRevisionPagesRequest","response":"GroupHistoryPage","doc":"Query the server for group revision history. The history contains information about the changes between each revision and the user that made the change.","errors":[{"name":"NoSuchAccountError"},{"name":"UnknownGroupError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"InternalError"},{"name":"GroupVerificationError"},{"name":"InvalidGroupStateError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError","doc":"caused when not a member of the group, when requesting logs from a revision lower than your joinedAtVersion, etc."},{"name":"RateLimitError"},{"name":"SQLError"}]},"send_sync_message":{"request":"SendSyncMessageRequest","response":"JsonSendMessageResult","doc":"Sends a sync message to the account's devices","errors":[{"name":"InvalidRequestError"},{"name":"RateLimitError"},{"name":"InternalError"},{"name":"UnregisteredUserError"},{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"AuthorizationFailedError"},{"name":"SQLError"}]},"ban_user":{"request":"BanUserRequest","response":"JsonGroupV2Info","doc":"Bans users from a group. This works even if the users aren't in the group. If they are currently in the group, they will also be removed.","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"UnknownGroupError"},{"name":"GroupVerificationError"},{"name":"InternalError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError","doc":"Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)."},{"name":"SQLError"},{"name":"GroupPatchNotAcceptedError","doc":"Caused when server rejects the group update."}]},"unban_user":{"request":"UnbanUserRequest","response":"JsonGroupV2Info","doc":"Unbans users from a group.","errors":[{"name":"NoSuchAccountError"},{"name":"ServerNotFoundError"},{"name":"InvalidProxyError"},{"name":"UnknownGroupError"},{"name":"GroupVerificationError"},{"name":"InternalError"},{"name":"InvalidRequestError"},{"name":"AuthorizationFailedError","doc":"Can be caused if signald is setup as a linked device that has been removed by the primary device. If trying to update a group, this can also be caused if group permissions don't allow the update (e.g. current role insufficient or not a member)."},{"name":"SQLError"},{"name":"GroupPatchNotAcceptedError","doc":"Caused when server rejects the group update."}]}}}} \ No newline at end of file diff --git a/signald/client-protocol/v1/errors.go b/signald/client-protocol/v1/errors.go index c7a4841..0ddb670 100644 --- a/signald/client-protocol/v1/errors.go +++ b/signald/client-protocol/v1/errors.go @@ -284,6 +284,13 @@ func mkerr(response client_protocol.BasicResponse) error { return err } return result + case "UnsupportedGroupError": + result := UnsupportedGroupError{} + err := json.Unmarshal(response.Error, &result) + if err != nil { + return err + } + return result case "UntrustedIdentityError": result := UntrustedIdentityError{} err := json.Unmarshal(response.Error, &result) @@ -645,6 +652,15 @@ func (e UnregisteredUserError) Error() string { return e.Message } +// UnsupportedGroupError: returned in response to use v1 groups, which are no longer supported +type UnsupportedGroupError struct { + Message string `json:"message,omitempty" yaml:"message,omitempty"` +} + +func (e UnsupportedGroupError) Error() string { + return e.Message +} + type UntrustedIdentityError struct { Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` IdentityKey **IdentityKey `json:"identity_key,omitempty" yaml:"identity_key,omitempty"` diff --git a/signald/client-protocol/v1/structs.go b/signald/client-protocol/v1/structs.go index cf4845f..8cfcd97 100644 --- a/signald/client-protocol/v1/structs.go +++ b/signald/client-protocol/v1/structs.go @@ -263,7 +263,7 @@ type GroupLinkInfoRequest struct { type GroupList struct { Groups []*JsonGroupV2Info `json:"groups,omitempty" yaml:"groups,omitempty"` - LegacyGroups []*JsonGroupInfo `json:"legacyGroups,omitempty" yaml:"legacyGroups,omitempty"` + LegacyGroups []*JsonGroupInfo `json:"legacyGroups,omitempty" yaml:"legacyGroups,omitempty"` // list of legacy (v1) groups, no longer supported (will always be empty) } type GroupMember struct { @@ -492,6 +492,7 @@ type JsonSentTranscriptMessage struct { ExpirationStartTimestamp int64 `json:"expirationStartTimestamp,omitempty" yaml:"expirationStartTimestamp,omitempty"` IsRecipientUpdate bool `json:"isRecipientUpdate,omitempty" yaml:"isRecipientUpdate,omitempty"` Message *JsonDataMessage `json:"message,omitempty" yaml:"message,omitempty"` + Story *StoryMessage `json:"story,omitempty" yaml:"story,omitempty"` Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` UnidentifiedStatus map[string]string `json:"unidentifiedStatus,omitempty" yaml:"unidentifiedStatus,omitempty"` } @@ -599,7 +600,8 @@ type Profile struct { Address *JsonAddress `json:"address,omitempty" yaml:"address,omitempty"` Avatar string `json:"avatar,omitempty" yaml:"avatar,omitempty"` // path to avatar on local disk Capabilities *Capabilities `json:"capabilities,omitempty" yaml:"capabilities,omitempty"` - Color string `json:"color,omitempty" yaml:"color,omitempty"` // color of the chat with this user + Color string `json:"color,omitempty" yaml:"color,omitempty"` // color of the chat with this user + ContactName string `json:"contact_name,omitempty" yaml:"contact_name,omitempty"` // The user's name from local contact names Emoji string `json:"emoji,omitempty" yaml:"emoji,omitempty"` ExpirationTime int32 `json:"expiration_time,omitempty" yaml:"expiration_time,omitempty"` InboxPosition int32 `json:"inbox_position,omitempty" yaml:"inbox_position,omitempty"` From 5470eb90d9517e79bbda8ba44425d96de540eee5 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Mon, 15 Aug 2022 17:33:06 -0400 Subject: [PATCH 68/73] Avoid more constraint violations during `db-move` --- cmd/signaldctl/cmd/db/migrate.go | 64 ++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index 56e54e9..df67e26 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -209,7 +209,9 @@ func moveAccounts(source *sql.DB, dest *sql.DB) error { } func moveRecipients(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT rowid, account_uuid, uuid, e164, registered FROM recipients") + rows, err := source.Query("SELECT rowid, account_uuid, uuid, e164, registered FROM recipients" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + ) if err != nil { return err } @@ -249,7 +251,9 @@ func moveRecipients(source *sql.DB, dest *sql.DB) error { } func movePrekeys(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, id, record FROM prekeys") + rows, err := source.Query("SELECT account_uuid, id, record FROM prekeys" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + ) if err != nil { return err } @@ -274,7 +278,10 @@ func movePrekeys(source *sql.DB, dest *sql.DB) error { } func moveSessions(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, recipient, device_id, record FROM sessions") + rows, err := source.Query("SELECT account_uuid, recipient, device_id, record FROM sessions" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)" + + " AND recipient IN (SELECT DISTINCT rowid FROM recipients)", + ) if err != nil { return err } @@ -309,7 +316,9 @@ func moveSessions(source *sql.DB, dest *sql.DB) error { } func moveSignedPrekeys(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, id, record FROM signed_prekeys") + rows, err := source.Query("SELECT account_uuid, id, record FROM signed_prekeys" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + ) if err != nil { return err } @@ -334,7 +343,9 @@ func moveSignedPrekeys(source *sql.DB, dest *sql.DB) error { } func moveIdentityKeys(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, recipient, identity_key, trust_level, added FROM identity_keys") + rows, err := source.Query("SELECT account_uuid, recipient, identity_key, trust_level, added FROM identity_keys" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + ) if err != nil { return err } @@ -361,7 +372,9 @@ func moveIdentityKeys(source *sql.DB, dest *sql.DB) error { } func moveAccountData(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, key, value FROM account_data") + rows, err := source.Query("SELECT account_uuid, key, value FROM account_data" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + ) if err != nil { return err } @@ -411,7 +424,9 @@ func movePendingAccountData(source *sql.DB, dest *sql.DB) error { } func moveSenderKeys(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, address, device, distribution_id, record, created_at FROM sender_keys") + rows, err := source.Query("SELECT account_uuid, address, device, distribution_id, record, created_at FROM sender_keys" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + ) if err != nil { return err } @@ -447,7 +462,9 @@ func moveSenderKeys(source *sql.DB, dest *sql.DB) error { } func moveSenderKeyShared(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, distribution_id, address, device FROM sender_key_shared") + rows, err := source.Query("SELECT account_uuid, distribution_id, address, device FROM sender_key_shared" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + ) if err != nil { return err } @@ -483,7 +500,9 @@ func moveSenderKeyShared(source *sql.DB, dest *sql.DB) error { } func moveGroups(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT rowid, account_uuid, group_id, master_key, revision, last_avatar_fetch, distribution_id, group_info FROM groups") + rows, err := source.Query("SELECT rowid, account_uuid, group_id, master_key, revision, last_avatar_fetch, distribution_id, group_info FROM groups" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + ) if err != nil { return err } @@ -519,7 +538,10 @@ func moveGroups(source *sql.DB, dest *sql.DB) error { } func moveGroupCredentials(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, date, credential FROM group_credentials") + rows, err := source.Query("SELECT account_uuid, date, max(credential) FROM group_credentials" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)" + + " GROUP BY account_uuid, date", + ) if err != nil { return err } @@ -545,7 +567,8 @@ func moveGroupCredentials(source *sql.DB, dest *sql.DB) error { func moveContacts(source *sql.DB, dest *sql.DB) error { rows, err := source.Query("SELECT account_uuid, recipient, name, color, profile_key, message_expiration_time, inbox_position FROM contacts" + - " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)" + + " AND recipient IN (SELECT DISTINCT rowid FROM recipients)", ) if err != nil { return err @@ -579,7 +602,10 @@ func moveContacts(source *sql.DB, dest *sql.DB) error { } func moveProfileKeys(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, recipient, profile_key, profile_key_credential, request_pending, unidentified_access_mode FROM profile_keys") + rows, err := source.Query("SELECT account_uuid, recipient, profile_key, profile_key_credential, request_pending, unidentified_access_mode FROM profile_keys" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)" + + " AND recipient IN (SELECT DISTINCT rowid FROM recipients)", + ) if err != nil { return err } @@ -611,7 +637,10 @@ func moveProfileKeys(source *sql.DB, dest *sql.DB) error { } func moveProfiles(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, recipient, last_update, given_name, family_name, about, emoji, payment_address, badges FROM profiles") + rows, err := source.Query("SELECT account_uuid, recipient, last_update, given_name, family_name, about, emoji, payment_address, badges FROM profiles" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)" + + " AND recipient IN (SELECT DISTINCT rowid FROM recipients)", + ) if err != nil { return err } @@ -646,7 +675,10 @@ func moveProfiles(source *sql.DB, dest *sql.DB) error { } func moveProfileCapabilities(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, recipient, storage, gv1_migration, sender_key, announcement_group, change_number, stories FROM profile_capabilities") + rows, err := source.Query("SELECT account_uuid, recipient, storage, gv1_migration, sender_key, announcement_group, change_number, stories FROM profile_capabilities" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)" + + " AND recipient IN (SELECT DISTINCT rowid FROM recipients)", + ) if err != nil { return err } @@ -680,7 +712,9 @@ func moveProfileCapabilities(source *sql.DB, dest *sql.DB) error { } func moveProfileBadges(source *sql.DB, dest *sql.DB) error { - rows, err := source.Query("SELECT account_uuid, id, category, name, description, sprite6 FROM profile_badges") + rows, err := source.Query("SELECT account_uuid, id, category, name, description, sprite6 FROM profile_badges" + + " WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)", + ) if err != nil { return err } From 3e33f0a3f7ee932f991b9faa1a02f29a4f5e4bdf Mon Sep 17 00:00:00 2001 From: finn Date: Mon, 29 Aug 2022 11:57:38 -0700 Subject: [PATCH 69/73] add --overwrite option to signaldctl account link --- cmd/signaldctl/cmd/account/link/link-account.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/signaldctl/cmd/account/link/link-account.go b/cmd/signaldctl/cmd/account/link/link-account.go index 899815d..9cb3fa3 100644 --- a/cmd/signaldctl/cmd/account/link/link-account.go +++ b/cmd/signaldctl/cmd/account/link/link-account.go @@ -34,6 +34,7 @@ import ( var ( testing bool deviceName string + overwrite bool LinkAccountCmd = &cobra.Command{ Use: "link", @@ -80,6 +81,7 @@ var ( finishReq := v1.FinishLinkRequest{ DeviceName: deviceName, SessionId: response.SessionId, + Overwrite: overwrite, } _, err = finishReq.Submit(common.Signald) @@ -99,4 +101,5 @@ func init() { } LinkAccountCmd.Flags().BoolVarP(&testing, "testing", "t", false, "use the Signal testing server") LinkAccountCmd.Flags().StringVarP(&deviceName, "device-name", "n", name, "the name of this device. shown to other devices on the signal account") + LinkAccountCmd.Flags().BoolVar(&overwrite, "overwrite", false, "if an account with the same id already exists in signald's database, delete it before linking") } From 10dab5a5c94ac49d6e0ef984911744d370d07e38 Mon Sep 17 00:00:00 2001 From: finn Date: Tue, 25 Oct 2022 16:17:44 -0700 Subject: [PATCH 70/73] allow updating more of the profile --- .../cmd/account/setprofile/set-profile.go | 26 +++++++++++++------ cmd/signaldctl/cmd/session/root.go | 1 - signald/signald.go | 1 - 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/cmd/signaldctl/cmd/account/setprofile/set-profile.go b/cmd/signaldctl/cmd/account/setprofile/set-profile.go index a1c646a..1d99673 100644 --- a/cmd/signaldctl/cmd/account/setprofile/set-profile.go +++ b/cmd/signaldctl/cmd/account/setprofile/set-profile.go @@ -27,10 +27,14 @@ import ( var ( account string + name string + avatar string + emoji string + about string SetProfileCmd = &cobra.Command{ - Use: "set-profile name", - Short: "updates the profile data with a new name", + Use: "set-profile [name]", + Short: "update an account's profile data", PreRun: func(cmd *cobra.Command, args []string) { if account == "" { account = config.Config.DefaultAccount @@ -39,16 +43,19 @@ var ( common.Must(cmd.Help()) log.Fatal("No account specified. Please specify with --account or set a default") } - if len(args) != 1 { - common.Must(cmd.Help()) - log.Fatal("must specify a name") + + if len(args) > 0 { + name = args[0] } }, - Run: func(_ *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { go common.Signald.Listen(nil) req := v1.SetProfile{ - Account: account, - Name: args[0], + Account: account, + Name: name, + AvatarFile: avatar, + Emoji: emoji, + About: about, } err := req.Submit(common.Signald) if err != nil { @@ -61,4 +68,7 @@ var ( func init() { SetProfileCmd.Flags().StringVarP(&account, "account", "a", "", "the signald account to use") + SetProfileCmd.Flags().StringVarP(&avatar, "avatar", "A", "", "path to avatar file") + SetProfileCmd.Flags().StringVar(&emoji, "emoji", "", "an emoji to be shown next to the about section") + SetProfileCmd.Flags().StringVar(&about, "about", "", "profile about section") } diff --git a/cmd/signaldctl/cmd/session/root.go b/cmd/signaldctl/cmd/session/root.go index 29673b5..614b827 100644 --- a/cmd/signaldctl/cmd/session/root.go +++ b/cmd/signaldctl/cmd/session/root.go @@ -15,7 +15,6 @@ package session - import ( "github.com/spf13/cobra" ) diff --git a/signald/signald.go b/signald/signald.go index 202ec46..f790d0c 100644 --- a/signald/signald.go +++ b/signald/signald.go @@ -99,7 +99,6 @@ func (s *Signald) connect() error { return nil } - func (s *Signald) Close() error { return s.socket.Close() } From fc7a556b5d31fda2e752bcaaf18afe36d9612953 Mon Sep 17 00:00:00 2001 From: finn Date: Tue, 25 Oct 2022 16:57:09 -0700 Subject: [PATCH 71/73] download dependant artifacts via the gitlab API since inter-project dependencies are now a premium feature --- .gitlab-ci.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 14a803a..99cae85 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -62,11 +62,15 @@ test sqlite to postgres: .build-deb: stage: build image: debian:buster - script: + before_script: - echo deb http://deb.debian.org/debian buster-backports main > /etc/apt/sources.list.d/backports.list - apt-get update - - apt-get install -y -t buster-backports git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev golang-github-google-uuid-dev golang-github-mattn-go-sqlite3-dev golang-github-lib-pq-dev golang-github-satori-go.uuid-dev + - apt-get install -y -t buster-backports git-buildpackage dh-golang bash-completion golang-any golang-github-spf13-cobra-dev golang-github-spf13-viper-dev golang-github-google-uuid-dev golang-github-mattn-go-sqlite3-dev golang-github-lib-pq-dev golang-github-satori-go.uuid-dev wget unzip + - wget -O golang-github-mdp-qrterminal.zip --quiet "https://gitlab.com/api/v4/projects/signald%2Flibraries%2Fgolang-github-mdp-qrterminal/jobs/artifacts/master/download?job=build" + - wget -O golang-github-jedib0t-go-pretty.zip --quiet "https://gitlab.com/api/v4/projects/signald%2Flibraries%2Fgolang-github-jedib0t-go-pretty/jobs/artifacts/master/download?job=build" + - for z in *.zip; do unzip $z; done - apt-get install -y ./*.deb && rm -vf *.deb + script: - 'sed -i "s/^Architecture:.*/Architecture: ${ARCH}/g" debian/control' - go run ./cmd/signaldctl doc -o man - go run ./cmd/signaldctl completion bash > debian/package.bash-completion @@ -74,15 +78,6 @@ test sqlite to postgres: - gbp dch --ignore-branch --debian-tag="%(version)s" --git-author --new-version="$(./version.sh | cut -c2-)" - dpkg-buildpackage -us -uc -b - mv ../*.deb . - needs: - - project: signald/libraries/golang-github-mdp-qrterminal - job: build - ref: master - artifacts: true - - project: signald/libraries/golang-github-jedib0t-go-pretty - job: build - ref: master - artifacts: true variables: SIGNALDCTL_PUBLIC_DOC_MODE: "on" artifacts: From 097e95bdb11dd3244b684d31300c1632dbb5d242 Mon Sep 17 00:00:00 2001 From: finn Date: Sun, 18 Dec 2022 22:00:58 -0800 Subject: [PATCH 72/73] Add version 17 migration Fixes #15 --- cmd/signaldctl/cmd/db/migrate.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/signaldctl/cmd/db/migrate.go b/cmd/signaldctl/cmd/db/migrate.go index df67e26..2241838 100644 --- a/cmd/signaldctl/cmd/db/migrate.go +++ b/cmd/signaldctl/cmd/db/migrate.go @@ -33,6 +33,7 @@ var ( {InstalledRank: 4, Version: "14", Description: "multiple identity keys per account", Script: "V14__multiple_identity_keys_per_account.sql", Checksum: -1635788950}, {InstalledRank: 5, Version: "15", Description: "profiles tables", Script: "V15__profiles_tables.sql", Checksum: 809686180}, {InstalledRank: 6, Version: "16", Description: "destination uuid in envelope", Script: "V16__destination_uuid_in_envelope.sql", Checksum: 357656854}, + {InstalledRank: 7, Version: "17", Description: "update server ca", Script: "V17__update_server_ca.sql", Checksum: 1647934070}, } sqlitePath string From d983bfb9a384752f8937e2df4327add293c96daa Mon Sep 17 00:00:00 2001 From: finn Date: Thu, 29 Dec 2022 17:45:22 -0800 Subject: [PATCH 73/73] improve error handling for listen --- signald/signald.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/signald/signald.go b/signald/signald.go index f790d0c..e82ccca 100644 --- a/signald/signald.go +++ b/signald/signald.go @@ -104,7 +104,7 @@ func (s *Signald) Close() error { } // Listen listens for events from signald -func (s *Signald) Listen(c chan client_protocol.BasicResponse) { +func (s *Signald) Listen(c chan client_protocol.BasicResponse) error { for { msg, err := s.readNext() if err == io.EOF { @@ -112,7 +112,11 @@ func (s *Signald) Listen(c chan client_protocol.BasicResponse) { if c != nil { close(c) } - return + return nil + } + + if err != nil { + return err } if msg.Type == "unexpected_error" { @@ -174,9 +178,6 @@ func (s *Signald) readNext() (b client_protocol.BasicResponse, err error) { } else { err = json.NewDecoder(s.socket).Decode(&b) } - if err != nil { - log.Println("signald-go: error decoding message from signald:", err) - return - } + return }