Compare commits

...

73 commits
0.2.0 ... main

Author SHA1 Message Date
d983bfb9a3 improve error handling for listen 2022-12-29 17:45:22 -08:00
097e95bdb1 Add version 17 migration
Fixes #15
2022-12-18 22:00:58 -08:00
fc7a556b5d download dependant artifacts via the gitlab API since inter-project dependencies are now a premium feature 2022-10-25 17:59:02 -07:00
10dab5a5c9 allow updating more of the profile 2022-10-25 16:17:44 -07:00
finn
3e33f0a3f7 add --overwrite option to signaldctl account link 2022-08-29 11:57:45 -07:00
Andrew Ferrazzutti
5470eb90d9 Avoid more constraint violations during db-move 2022-08-16 17:01:56 +00:00
finn
1ced8e01b2 bump protocol 2022-08-16 09:57:10 -07:00
finn
5e52e5f3bd deb build cut first character (v) out of version string 2022-08-16 09:32:40 -07:00
Andrew Ferrazzutti
4178a582e8 Refactor more migration functions 2022-08-15 17:36:01 -04:00
Andrew Ferrazzutti
67a7576e24 Refactor migration functions & messages
Ensure that success & error migration logs use the same description text
for the migration step that they refer to.
2022-08-12 21:05:12 +00:00
Andrew Ferrazzutti
dd208b6e82 Exclude missing accounts from contact migration
Otherwise, moveContacts fails with a foreign key constraint violation.
2022-08-12 16:33:12 -04:00
finn
9928e5ffc2 fix migration 16 checksum 2022-06-27 11:46:58 -07:00
finn
12874dd6dc fix migrations, closes !10 2022-06-27 10:00:11 -07:00
finn
b5af9d176b bump protocol 2022-05-17 18:14:09 -07:00
finn
7ace3647c7 add new db migration code, add sqlite to postgres migration test in CI
hopefully this will keep migrations in sync
2022-05-17 17:59:24 -07:00
finn
b343c02f77 captcha helper support for send
feedback appriciated, I can't get captchas reliably
2022-05-17 09:02:03 -07:00
schubisu
890417af5d remove incorrect alias 2022-05-12 14:42:09 +02:00
finn
8458f63316 update db-move to support migration version 14 2022-04-25 17:21:52 -07:00
finn
e50f392288 add signaldctl account request-sync 2022-04-25 17:05:37 -07:00
Ben Wiederhake
204657b38b avoid races when sending requests 2022-04-10 22:41:24 +00:00
4e3feffaad force linter job to golang 1.17 as golangci-lint seems to break on 1.18 2022-04-10 15:38:12 -07:00
finn
e8131dc928 fix mode on created directories 2022-03-28 10:06:22 -07:00
finn
0ff6400ce7 signaldctl: create config directory if missing 2022-03-28 09:42:37 -07:00
d8fdb8edb0 add table output to preview subcommand 2022-03-26 01:23:26 -07:00
ec276e5411 add group preview subcommand 2022-03-25 22:26:47 -07:00
7ff5492156 expose close() option for socket 2022-03-19 22:44:31 -07:00
2b1f0dada7 don't panic if we can't unmarshal a message from signald 2022-03-19 16:57:19 -07:00
0f3e6dce72 allow send to accept message from stdin 2022-03-19 01:38:25 -07:00
4bc97fb829 add signaldctl subscribe subcommand with initial test at a parsable output format 2022-03-19 01:37:14 -07:00
3ccb424f53 don't push messages with IDs to the channel 2022-03-15 19:14:42 -07:00
Sumner Evans
689d560eb1
move script: update for new contacts table 2022-03-07 21:25:43 -07:00
finn
8bb40967e5 forget e164s that don't start with a + 2022-03-02 15:16:28 -08:00
finn
fa1f0765d5 allow null recipient UUIDs 2022-03-02 13:42:12 -08:00
finn
21420e0d77 pending account data table uses string account identifier not uuid 2022-03-02 13:10:41 -08:00
finn
56cc98de30 fix queries 2022-03-01 16:56:56 -08:00
finn
17f823064f reset the value generator for SERIAL columns after import 2022-03-01 16:00:37 -08:00
finn
9a371b8f91 fix apt publishing 2022-02-23 07:16:27 -08:00
finn
acc817a043 add db move subcommand 2022-02-22 15:46:27 -08:00
finn
946b27917d bump protocol 2022-02-16 14:52:03 -05:00
219f1df6e5 gofmt no longer automatic on save... will investigate 2022-01-17 18:16:56 -08:00
e454d63249 breaking change: Listen() now returns BasicResponse 2022-01-17 18:15:23 -08:00
dd56e3d6f7 Fix indentation on signaldctl raw docs
closes signald/signald.org#4
2021-11-07 10:22:25 -08:00
149d44b705 Add an example to signaldctl raw
fixes #4
2021-11-07 10:18:27 -08:00
924e9c3767 bring protocol up to date 2021-11-02 21:26:30 -07:00
28b7d16568 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
2021-10-17 15:01:20 -07:00
a535a63ba0 add signaldctl raw subcommand 2021-10-14 01:52:39 -07:00
25050d2f0e fix unmarshal argument pointer 2021-09-30 00:50:10 -07:00
bfcb423cdf add support for errors in the protocol 2021-09-30 00:39:54 -07:00
0f8b17383f add a subcommand to get the current protocol document from a running signald instance 2021-09-24 02:24:04 -07:00
9a52532325 add group join subcommand 2021-09-14 19:01:38 -07:00
3cb60fd14f fix new uuid library dependency 2021-09-01 20:42:59 -07:00
2fa9670efe improve support for handling UUIDs 2021-09-01 20:24:56 -07:00
096152394a Update protocol and add support for remote-config to signaldctl 2021-08-27 02:54:09 -07:00
Bohdan Horbeshko
ec3d46fcf3 Add account set-profile command 2021-08-26 21:10:02 +00:00
1cdfd16f11 force debian builds to buster 2021-08-25 17:00:23 -07:00
474897ea53 Fix version compiled into debian signaldctl 2021-08-07 13:43:56 -07:00
5b98b6b136 Refactor deb build and remove git pull before build 2021-08-07 13:29:42 -07:00
0a0bcd5e93 Try to fix the debian version tags 2021-08-07 13:29:42 -07:00
8567e37af9 Install dependencies from backports when building signaldctl debian packages 2021-07-30 16:10:09 -07:00
Sebastian Haas
f443b0dd48 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
2021-07-30 18:53:34 +00:00
86f23e72c9 build x86 deb doesnt work in MR pipelines either 2021-07-30 11:52:57 -07:00
a5b4d01e10 fix another minor CI thing 2021-07-30 11:40:20 -07:00
2c74bb163c fix up CI to run in 3rd party MR pipelines 2021-07-30 11:36:26 -07:00
0d80576e8a Update some links 2021-07-23 18:38:05 -07:00
c3b79ebe98 trust-all sets trust level to TRUSTED_UNVERIFIED 2021-07-23 17:36:10 -07:00
cf0e4a8b85 add key trust-all
long overdue
2021-07-23 16:55:28 -07:00
fcae0fc903 Add session reset command 2021-07-22 21:42:55 -07:00
033bb59c76 add flag to attach files to outbound messages 2021-07-16 10:49:14 -07:00
7ed5558491 pull after checkout 2021-07-09 18:57:38 -07:00
15e3bd7298 build armhf debs 2021-07-09 18:28:04 -07:00
c5389eb6c2 move some links from gitlab to signald.org 2021-07-09 03:48:04 -07:00
235ec55978 Standardize the deb build template 2021-07-09 01:45:15 -07:00
460fef2f0e Possibly fix building in a branch 2021-07-09 00:39:24 -07:00
52 changed files with 4215 additions and 3496 deletions

View file

@ -1,20 +1,47 @@
stages:
- build
- test
- publish
lint:
image: golang:latest
stage: build
image: golang:1.17
stage: test
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"
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
@ -23,8 +50,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}"
- 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" --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
@ -33,8 +59,42 @@ lint:
- signaldctl
expire_in: 1 month
.build-deb:
stage: build
image: debian:buster
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 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
- ls *.1 > debian/manpages
- gbp dch --ignore-branch --debian-tag="%(version)s" --git-author --new-version="$(./version.sh | cut -c2-)"
- dpkg-buildpackage -us -uc -b
- mv ../*.deb .
variables:
SIGNALDCTL_PUBLIC_DOC_MODE: "on"
artifacts:
paths:
- "*.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
build:aarch64:
tags: [arm-builder]
@ -52,8 +112,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}"
- 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
@ -70,96 +129,57 @@ build:cross-compile:
- amd64
- GOOS: darwin
GOARCH: amd64
rules:
- when: on_success
build:x86:deb:
stage: build
image: debian:latest
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}"
- 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
extends: .build-deb
rules:
- when: on_success
variables:
SIGNALDCTL_PUBLIC_DOC_MODE: "on"
artifacts:
paths:
- "*.deb"
- "*.1"
expire_in: 1 month
ARCH: amd64
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}"
- 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
ARCH: arm64
build:armhf:deb:
extends: .build-deb
image: arm32v7/debian:buster
tags: [arm-builder]
variables:
ARCH: armhf
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
- 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
only:
- main
- tags
rules:
- if: '$CI_PROJECT_NAMESPACE == "signald" && $CI_COMMIT_REF_PROTECTED == "true"'
when: on_success
- when: manual
allow_failure: true
docs.signald.org:
signald.org:
stage: publish
needs: ["build:x86"]
trigger: signald/signald.org
only:
- main
rules:
- if: '$CI_PROJECT_NAMESPACE == "signald" && $CI_COMMIT_REF_PROTECTED == "true"'
when: on_success

View file

@ -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

View file

@ -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)

View file

@ -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://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/)
[see in progress documentation site](https://signald.org/signaldctl/)

View file

@ -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")
}

View file

@ -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")
}

View file

@ -0,0 +1,82 @@
// Copyright © 2021 Finn Herzfeld <finn@janky.solutions>
//
// 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 <http://www.gnu.org/licenses/>.
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")
}

View file

@ -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")
}

View file

@ -22,6 +22,9 @@ 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/requestsync"
"gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/setprofile"
"gitlab.com/signald/signald-go/cmd/signaldctl/cmd/account/verify"
)
@ -35,5 +38,8 @@ func init() {
AccountCmd.AddCommand(link.LinkAccountCmd)
AccountCmd.AddCommand(list.ListAccountCmd)
AccountCmd.AddCommand(register.RegisterAccountCmd)
AccountCmd.AddCommand(remoteconfig.RemoteConfigCmd)
AccountCmd.AddCommand(requestsync.RequestSyncCmd)
AccountCmd.AddCommand(setprofile.SetProfileCmd)
AccountCmd.AddCommand(verify.VerifyAccountCmd)
}

View file

@ -0,0 +1,74 @@
// 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 <http://www.gnu.org/licenses/>.
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
name string
avatar string
emoji string
about string
SetProfileCmd = &cobra.Command{
Use: "set-profile [name]",
Short: "update an account's profile data",
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 {
name = args[0]
}
},
Run: func(_ *cobra.Command, _ []string) {
go common.Signald.Listen(nil)
req := v1.SetProfile{
Account: account,
Name: name,
AvatarFile: avatar,
Emoji: emoji,
About: about,
}
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")
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")
}

View file

@ -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))
}
},
}

View file

@ -0,0 +1,747 @@
package db
import (
"database/sql"
"errors"
"fmt"
"log"
"os"
"os/user"
"strings"
"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"
)
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},
{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
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) (err 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")
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)
}
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)
}
}()
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")
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")
return err
}
log.Println("sqlite file deleted, your data is now in postgres :)")
return nil
},
}
)
func verifyMigration(source *sql.DB) error {
// Lower bound of the database state.
rows, err := source.Query("SELECT version FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 1")
if err != nil {
return err
}
defer rows.Close()
if !rows.Next() {
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
err = rows.Scan(&version)
if err != nil {
return err
}
expectedMigrationVersion := migrations[len(migrations)-1].Version
if version != expectedMigrationVersion {
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
}
func createSchema(dest *sql.DB) error {
_, err := dest.Exec(pgScheme)
if err != nil {
return err
}
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, 'SQL', $4, $5, current_user, 0, true)
`,
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, server FROM accounts")
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
accountUUID uuid.UUID
e164 string
server uuid.UUID
)
err = rows.Scan(&accountUUID, &e164, &server)
if err != nil {
return err
}
_, err = dest.Exec("INSERT INTO signald_accounts (uuid, e164, server) VALUES ($1, $2, $3)", accountUUID, e164, 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, registered FROM recipients" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)",
)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var (
rowID int64
accountUUID uuid.UUID
recipientUUID uuid.NullUUID
e164 sql.NullString
registered bool
)
err = rows.Scan(&rowID, &accountUUID, &recipientUUID, &e164, &registered)
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, registered) VALUES ($1, $2, $3, $4, $5)", rowID, accountUUID, recipientUUID, e164, registered)
if err != nil {
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
}
func movePrekeys(source *sql.DB, dest *sql.DB) error {
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
}
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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)" +
" AND recipient IN (SELECT DISTINCT rowid FROM recipients)",
)
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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)",
)
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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)",
)
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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)",
)
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 (
username string
key string
value []byte
)
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)", username, 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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)",
)
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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)",
)
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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)",
)
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
}
}
// 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
}
func moveGroupCredentials(source *sql.DB, dest *sql.DB) error {
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
}
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
}
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)" +
" AND recipient IN (SELECT DISTINCT rowid FROM recipients)",
)
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
}
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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)" +
" AND recipient IN (SELECT DISTINCT rowid FROM recipients)",
)
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)
`, 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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)" +
" AND recipient IN (SELECT DISTINCT rowid FROM recipients)",
)
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 sql.NullString
)
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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)" +
" AND recipient IN (SELECT DISTINCT rowid FROM recipients)",
)
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" +
" WHERE account_uuid IN (SELECT DISTINCT uuid FROM accounts)",
)
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
}

View file

@ -0,0 +1,284 @@
package db
var (
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,
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,
destination_uuid TEXT
);
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,
server UUID NOT NULL REFERENCES signald_servers(server_uuid) ON DELETE CASCADE,
PRIMARY KEY (uuid, e164, server),
UNIQUE (e164),
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, -- from signald in src/main/resources/db/migration/postgresql/V13__recipient_registration_status.sql
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)
);
-- 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,
name TEXT,
color TEXT,
profile_key BYTEA,
message_expiration_time INTEGER,
inbox_position INTEGER,
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)
);
`
)

View file

@ -25,7 +25,7 @@ import (
var DeviceCmd = &cobra.Command{
Use: "device",
Aliases: []string{"msg", "devices"},
Aliases: []string{"devices"},
}
func init() {

View file

@ -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)
}
},

View file

@ -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)
}

View file

@ -0,0 +1,111 @@
// Copyright © 2021 Finn Herzfeld <finn@janky.solutions>
//
// 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 <http://www.gnu.org/licenses/>.
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 <url>",
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")
}

View file

@ -0,0 +1,102 @@
// Copyright © 2021 Finn Herzfeld <finn@janky.solutions>
//
// 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 <http://www.gnu.org/licenses/>.
package preview
import (
"encoding/json"
"errors"
"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
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:
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
},
}
)

View file

@ -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)
}
},

View file

@ -21,8 +21,10 @@ 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/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"
@ -37,8 +39,10 @@ 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(preview.PreviewGroupCmd)
GroupCmd.AddCommand(removemember.RemoveMemberCmd)
GroupCmd.AddCommand(show.ShowGroupCmd)
GroupCmd.AddCommand(update.UpdateGroupCmd)

View file

@ -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"

View file

@ -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
}
},
@ -67,19 +70,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 +106,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()

View file

@ -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]
}

View file

@ -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)
}

View file

@ -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])

View file

@ -0,0 +1,157 @@
// Copyright © 2021 Finn Herzfeld <finn@janky.solutions>
//
// 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 <http://www.gnu.org/licenses/>.
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 [<phone number or UUID>]",
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 {
address, err := common.StringToAddress(address)
if err != nil {
log.Fatal(err)
}
addresses = append(addresses, 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,
TrustLevel: "TRUSTED_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: &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: "TRUSTED_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")
}

View file

@ -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 <author> <timestamp> <emoji>",
Use: "react thread author timestamp emoji",
Short: "react to a message",
Long: `react to a message with a particular emoji
arguments:
<thread> 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.
<author> the e164 or UUID of the author of the message being reacted to
<timestamp> the timestamp of the message to react to
<emoji> the unicode emoji to send as the reaction
`,
PreRun: func(cmd *cobra.Command, args []string) {
if account == "" {
account = config.Config.DefaultAccount
@ -50,16 +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")
}
author = common.StringToAddress(args[0])
var err error
timestamp, err = strconv.ParseInt(args[1], 10, 64)
threadAddress, group, err = common.StringToAddressOrGroup(args[0])
if err != nil {
log.Fatal("Unable to parse timestamp", args[1], ":", err.Error())
log.Fatal(err)
}
emoji = args[2]
author, err = common.StringToAddress(args[1])
if err != nil {
log.Fatal(err)
}
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)
@ -74,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)

View file

@ -18,8 +18,12 @@ package send
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/jedib0t/go-pretty/v6/table"
@ -28,14 +32,23 @@ 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
account string
toAddress *v1.JsonAddress
toGroup string
attachments []string
message string
captchaHelper bool
SendMessageCmd = &cobra.Command{
Use: "send <group id | phone number> <message>",
Use: "send {group id | phone number} [message]",
Short: "send a message",
PreRun: func(cmd *cobra.Command, args []string) {
if account == "" {
@ -45,29 +58,94 @@ 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 {
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:], " "),
Username: account,
MessageBody: message,
Attachments: []*v1.JsonAttachment{},
RecipientAddress: toAddress,
RecipientGroupID: toGroup,
}
if strings.HasPrefix(args[0], "+") {
req.RecipientAddress = &v1.JsonAddress{Number: args[0]}
} else {
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, &v1.JsonAttachment{Filename: path})
}
resp, err := req.Submit(common.Signald)
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)
@ -119,6 +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.")
}

View file

@ -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)
}

60
cmd/signaldctl/cmd/raw.go Normal file
View file

@ -0,0 +1,60 @@
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 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.
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()
}
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)
}

View file

@ -24,10 +24,12 @@ 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"
"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"
@ -79,10 +81,12 @@ 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)
RootCmd.AddCommand(message.MessageCmd)
RootCmd.AddCommand(session.SessionCmd)
}
// initConfig reads in config file and ENV variables if set.

View file

@ -0,0 +1,29 @@
// Copyright © 2021 Finn Herzfeld <finn@janky.solutions>
//
// 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 <http://www.gnu.org/licenses/>.
package session
import (
"github.com/spf13/cobra"
)
var SessionCmd = &cobra.Command{
Use: "session",
Aliases: []string{"sessions"},
}
func init() {
SessionCmd.AddCommand(ResetCmd)
}

View file

@ -0,0 +1,122 @@
// Copyright © 2021 Finn Herzfeld <finn@janky.solutions>
//
// 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 <http://www.gnu.org/licenses/>.
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 <phone number | UUID>",
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")
}

View file

@ -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 {
continue
}
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)
}

View file

@ -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"
@ -19,6 +21,7 @@ const (
OutputFormatQR = "qr"
OutputFormatMarkdown = "md"
OutputFormatMan = "man"
OutputFormatQuiet = "quiet"
AnnotationNoSocketConnection = "no-socket"
)
@ -29,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)
@ -39,9 +54,28 @@ 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)
}
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
}

View file

@ -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), 0770); err != nil {
return err
}
f, err := os.Create(Path)
if err != nil {
return err

6
debian/control vendored
View file

@ -8,7 +8,11 @@ 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,
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

2
debian/rules vendored
View file

@ -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)

8
go.mod
View file

@ -3,15 +3,19 @@ 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/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/spf13/cobra v1.0.0
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
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
)

209
go.sum
View file

@ -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,116 @@ 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/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=
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/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=
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 +160,169 @@ 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/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=
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=

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,14 @@
package client_protocol
import (
"encoding/json"
)
type BasicResponse struct {
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"`
}

View file

@ -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"`
@ -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"`
}
@ -307,11 +308,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 {
}

View file

@ -0,0 +1,681 @@
package v1
// DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo
import (
"encoding/json"
"fmt"
client_protocol "gitlab.com/signald/signald-go/signald/client-protocol"
)
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 "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)
if err != nil {
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)
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 "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)
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 "ProofRequiredError":
result := ProofRequiredError{}
err := json.Unmarshal(response.Error, &result)
if err != nil {
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)
if err != nil {
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)
if err != nil {
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)
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 "UnregisteredUserError":
result := UnregisteredUserError{}
err := json.Unmarshal(response.Error, &result)
if err != nil {
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)
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 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 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"`
}
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"`
}
func (e CaptchaRequiredError) Error() string {
return e.Message
}
type DuplicateMessageError struct {
Message string `json:"message,omitempty" yaml:"message,omitempty"`
Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
}
func (e DuplicateMessageError) 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
}
// 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"`
}
func (e GroupVerificationError) Error() string {
return e.Message
}
// 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"`
}
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 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 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"`
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 {
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"`
}
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"`
}
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"`
}
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 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
}
// 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"`
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
}

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,8 @@ 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
Pni string `json:"pni,omitempty" yaml:"pni,omitempty"`
}
type AccountList struct {
@ -34,7 +36,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.
@ -61,6 +63,24 @@ 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"`
}
type BusyMessage struct {
ID int64 `json:"id,omitempty" yaml:"id,omitempty"`
}
@ -76,13 +96,18 @@ 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"` // 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 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
@ -113,10 +138,11 @@ 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"`
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"`
}
@ -132,7 +158,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 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
@ -140,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
@ -165,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
@ -172,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"`
@ -187,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 {
@ -196,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"`
@ -234,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"`
@ -241,6 +330,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
@ -254,30 +350,49 @@ 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 sending, 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 []*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
@ -290,29 +405,34 @@ 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"`
}
// JsonGroupV2Info: Information about a Signal group
type JsonGroupV2Info struct {
AccessControl *GroupAccessControl `json:"accessControl,omitempty" yaml:"accessControl,omitempty"` // current access control settings for this group
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"`
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 {
@ -321,31 +441,20 @@ 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
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
@ -370,11 +479,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 *v0.Success `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 {
@ -382,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"`
}
@ -389,10 +500,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"`
@ -452,7 +563,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"`
}
@ -472,6 +583,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
@ -480,16 +596,19 @@ 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
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"`
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 {
@ -499,11 +618,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 {
@ -512,6 +632,15 @@ 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
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
}
// 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
@ -521,6 +650,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"`
}
@ -528,10 +673,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
@ -554,6 +700,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
@ -564,23 +711,35 @@ 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
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"`
Mentions []*JsonMention `json:"mentions,omitempty" yaml:"mentions,omitempty"`
MessageBody string `json:"messageBody,omitempty" yaml:"messageBody,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"`
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"`
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 {
@ -588,28 +747,50 @@ 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"`
}
// 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"`
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 {
Number int32 `json:"number,omitempty" yaml:"number,omitempty"`
Url string `json:"url,omitempty" yaml:"url,omitempty"`
}
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
@ -627,11 +808,84 @@ 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"` // 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
}
// 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"`
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.
@ -640,6 +894,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
@ -666,6 +930,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
@ -682,13 +954,15 @@ 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"`
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"`
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"`
@ -707,3 +981,15 @@ type VerifyRequest struct {
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
State string `json:"state,omitempty" yaml:"state,omitempty"` // One of: DISCONNECTED, CONNECTING, CONNECTED, RECONNECTING, DISCONNECTING, AUTHENTICATION_FAILED, FAILED
}

View file

@ -17,8 +17,8 @@ package signald
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"math/rand"
@ -28,7 +28,7 @@ import (
"strings"
"time"
"gitlab.com/signald/signald-go/signald/client-protocol/v0"
client_protocol "gitlab.com/signald/signald-go/signald/client-protocol"
)
const (
@ -51,17 +51,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,16 +84,27 @@ 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
}
func (s *Signald) Close() error {
return s.socket.Close()
}
// Listen listens for events from signald
func (s *Signald) Listen(c chan v0.LegacyResponse) {
func (s *Signald) Listen(c chan client_protocol.BasicResponse) error {
for {
msg, err := s.readNext()
if err == io.EOF {
@ -108,7 +112,11 @@ func (s *Signald) Listen(c chan v0.LegacyResponse) {
if c != nil {
close(c)
}
return
return nil
}
if err != nil {
return err
}
if msg.Type == "unexpected_error" {
@ -125,13 +133,8 @@ func (s *Signald) Listen(c chan v0.LegacyResponse) {
subscribers <- msg
}
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
}
if c != nil && !(msg.ID == "" && msg.Type == "version") {
c <- msg
}
}
}
@ -146,13 +149,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 +170,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)
@ -175,16 +178,6 @@ func (s *Signald) readNext() (b 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
}
func (b BasicResponse) GetError() error {
if b.Error == nil {
return nil
}
return fmt.Errorf("signald error: %s", string(b.Error))
}

View file

@ -0,0 +1,37 @@
package {{.Version}}
// {{ .Banner }}
import (
"encoding/json"
"fmt"
client_protocol "gitlab.com/signald/signald-go/signald/client-protocol"
)
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 }}

View file

@ -19,6 +19,7 @@ type Type struct {
Fields map[string]*DataType
Request bool `json:"-"`
Doc string
Error bool
}
type DataType struct {
@ -40,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{
@ -68,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 {
@ -76,10 +81,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)
}
}
}
@ -108,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 != "" {
@ -145,7 +152,11 @@ func main() {
}
for version, types := range response.Types {
inputs := StructsTemplateInput{Version: version}
inputs := StructsTemplateInput{
Version: version,
Types: make(map[string]*Type),
Banner: banner,
}
for typeName, t := range types {
for fieldName, field := range t.Fields {
types[typeName].Fields[fieldName].fixForVersion(fieldName, version)
@ -162,8 +173,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 +198,57 @@ func main() {
}
fmt.Println(outputFilename)
}
// errors
for version, types := range response.Types {
inputs := StructsTemplateInput{
Version: version,
Types: make(map[string]*Type),
Banner: banner,
}
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)
}
}

View file

@ -1,10 +1,9 @@
package {{.Version}}
// DO NOT EDIT: this file is automatically generated by ./tools/generator in this repo
// {{ .Banner }}
import ({{if .Responses}}
"encoding/json"
"fmt"
"log"{{end}}
"gitlab.com/signald/signald-go/signald"
@ -23,20 +22,19 @@ 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 = 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)

View file

@ -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}}

2
version.sh Executable file
View file

@ -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)