From 47656ca0bb1db87ed27559df6eb722688da18b18 Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Sun, 19 Mar 2023 18:31:16 -0600 Subject: [PATCH] much more cleanup Signed-off-by: Sumner Evans --- .gitignore | 1 + .pre-commit-config.yaml | 4 +- ROADMAP.md | 10 +- config/config.go | 4 +- custompuppet.go | 11 +- database/upgrades/upgrades.go | 2 +- example-config.yaml | 56 ++--- formatting.go | 138 ++---------- go.mod | 25 ++- go.sum | 61 ++--- main.go | 67 +++--- messagetracking.go | 1 - portal.go | 302 +------------------------ provisioning.go | 330 +-------------------------- segment.go | 9 +- user.go | 409 +--------------------------------- 16 files changed, 148 insertions(+), 1282 deletions(-) diff --git a/.gitignore b/.gitignore index 31f546f..63d61a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +logs/* *.session *.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f28fca..f24619f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.4.0 hooks: - id: trailing-whitespace exclude_types: [markdown] @@ -9,7 +9,7 @@ repos: - id: check-added-large-files - repo: https://github.com/tekwizely/pre-commit-golang - rev: v1.0.0-beta.5 + rev: v1.0.0-rc.1 hooks: - id: go-imports-repo args: diff --git a/ROADMAP.md b/ROADMAP.md index d945dd1..e3eb792 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -69,10 +69,10 @@ * [x] At startup * [x] When receiving invite * [x] When receiving message - * [ ] Private chat creation by inviting Matrix puppet of WhatsApp user to new room - * [ ] Option to use own Matrix account for messages sent from WhatsApp mobile/other web clients + * [ ] Private chat creation by inviting Matrix puppet of GroupMe user to new room + * [ ] Option to use own Matrix account for messages sent from GroupMe mobile/other web clients * [ ] Shared group chat portals -[1] Basic feature works. Improvements are TODO. -[2] May already work -[3] May not be possible +[1] Basic feature works. Improvements are TODO. +[2] May already work +[3] May not be possible diff --git a/config/config.go b/config/config.go index 5ec3548..27cdad4 100644 --- a/config/config.go +++ b/config/config.go @@ -24,13 +24,15 @@ import ( type Config struct { *bridgeconfig.BaseConfig `yaml:",inline"` - SegmentKey string `yaml:"segment_key"` + SegmentKey string `yaml:"segment_key"` + SegmentUserID string `yaml:"segment_user_id"` Metrics struct { Enabled bool `yaml:"enabled"` Listen string `yaml:"listen"` } `yaml:"metrics"` + // TODO need these? GroupMe struct { OSName string `yaml:"os_name"` BrowserName string `yaml:"browser_name"` diff --git a/custompuppet.go b/custompuppet.go index d32d346..847e57d 100644 --- a/custompuppet.go +++ b/custompuppet.go @@ -98,7 +98,7 @@ func (br *GMBridge) newDoublePuppetClient(mxid id.UserID, accessToken string) (* homeserverURL, found := br.Config.Bridge.DoublePuppetServerMap[homeserver] if !found { if homeserver == br.AS.HomeserverDomain { - homeserverURL = br.AS.HomeserverURL + homeserverURL = "" } else if br.Config.Bridge.DoublePuppetAllowDiscovery { resp, err := mautrix.DiscoverClientAPI(homeserver) if err != nil { @@ -110,14 +110,7 @@ func (br *GMBridge) newDoublePuppetClient(mxid id.UserID, accessToken string) (* return nil, fmt.Errorf("double puppeting from %s is not allowed", homeserver) } } - client, err := mautrix.NewClient(homeserverURL, mxid, accessToken) - if err != nil { - return nil, err - } - client.Logger = br.AS.Log.Sub(mxid.String()) - client.Client = br.AS.HTTPClient - client.DefaultHTTPRetries = br.AS.DefaultHTTPRetries - return client, nil + return br.AS.NewExternalMautrixClient(mxid, accessToken, homeserverURL) } func (puppet *Puppet) newCustomIntent() (*appservice.IntentAPI, error) { diff --git a/database/upgrades/upgrades.go b/database/upgrades/upgrades.go index 25ffe6f..b651547 100644 --- a/database/upgrades/upgrades.go +++ b/database/upgrades/upgrades.go @@ -1,4 +1,4 @@ -// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge. +// mautrix-groupme - A Matrix-GroupMe puppeting bridge. // Copyright (C) 2022 Tulir Asokan // // This program is free software: you can redistribute it and/or modify diff --git a/example-config.yaml b/example-config.yaml index 9618aee..15ebe4d 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -9,7 +9,7 @@ homeserver: # Standard Matrix homeservers like Synapse, Dendrite and Conduit should just use "standard" here. software: standard # The URL to push real-time bridge status to. - # If set, the bridge will make POST requests to this URL whenever a user's whatsapp connection state changes. + # If set, the bridge will make POST requests to this URL whenever a user's GroupMe connection state changes. # The bridge will use the appservice as_token to authorize requests. status_endpoint: null # Endpoint for reporting per-message status. @@ -29,10 +29,11 @@ appservice: # Database config. database: - # The database type. "sqlite3" and "postgres" are supported. + # The database type. "sqlite3-fk-wal" and "postgres" are supported. type: postgres # The database URI. - # SQLite: File name is enough. https://github.com/mattn/go-sqlite3#connection-string + # SQLite: A raw file path is supported, but `file:?_txlock=immediate` is recommended. + # https://github.com/mattn/go-sqlite3#connection-string # Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable # To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql uri: postgres://user:password@host/database?sslmode=disable @@ -53,7 +54,11 @@ appservice: # Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty # to leave display name/avatar as-is. displayname: GroupMe bridge bot - avatar: mxc://malhotra.cc/YTWNAdhgJhYOPsKIxyfFZsrA + avatar: mxc://nevarro.space/eoAJPcSuTEvffoNycrXjvsmj + + # Whether or not to receive ephemeral events via appservice transactions. + # Requires MSC2409 support (i.e. Synapse 1.22+). + ephemeral_events: true # Authentication tokens for AS <-> HS communication. Autogenerated; do not modify. as_token: "This value is generated when generating the registration" @@ -80,8 +85,8 @@ groupme: # Bridge config bridge: - # Localpart template of MXIDs for WhatsApp users. - # {{.}} is replaced with the phone number of the WhatsApp user. + # Localpart template of MXIDs for GroupMe users. + # {{.}} is replaced with the phone number of the GroupMe user. username_template: groupme_{{.}} # Displayname template for GroupMe users. # {{call .UserID.String}} - the number GroupMe assigns to the user @@ -91,7 +96,7 @@ bridge: # Should the bridge create a space for each logged-in user and add bridged rooms to it? # Users who logged in before turning this on should run `!wa sync space` to create and fill the space for the first time. personal_filtering_spaces: false - # Should the bridge send a read receipt from the bridge bot when a message has been sent to WhatsApp? + # Should the bridge send a read receipt from the bridge bot when a message has been sent to GroupMe? delivery_receipts: false # Whether the bridge should send the message status as a custom com.beeper.message_send_status event. message_status_events: false @@ -158,7 +163,7 @@ bridge: # manually. login_shared_secret: null - # Whether or not to invite own WhatsApp user's Matrix puppet into private + # Whether or not to invite own GroupMe user's Matrix puppet into private # chat portals when backfilling if needed. # This always uses the default puppet instead of custom puppets due to # rate limits and timestamp massaging. @@ -172,11 +177,11 @@ bridge: # except if the config file is not writable. resend_bridge_info: false - # Whether or not thumbnails from WhatsApp should be sent. + # Whether or not thumbnails from GroupMe should be sent. # They're disabled by default due to very low resolution. - whatsapp_thumbnail: false + groupme_thumbnail: false - # Allow invite permission for user. User can invite any bots to room with whatsapp + # Allow invite permission for user. User can invite any bots to room with GroupMe # users (private chat and groups) allow_user_invite: false @@ -222,7 +227,7 @@ bridge: # verified - Require manual per-device verification # (currently only possible by modifying the `trust` column in the `crypto_device` database table). verification_levels: - # Minimum level for which the bridge should send keys to when bridging messages from WhatsApp to Matrix. + # Minimum level for which the bridge should send keys to when bridging messages from GroupMe to Matrix. receive: unverified # Minimum level that the bridge should accept for incoming Matrix messages. send: unverified @@ -256,7 +261,7 @@ bridge: # Permissions for using the bridge. # Permitted values: # relaybot - Talk through the relaybot (if enabled), no access otherwise - # user - Access to use the bridge to chat with a WhatsApp account. + # user - Access to use the bridge to chat with a GroupMe account. # admin - User level and some additional administration tools # Permitted keys: # * - All Matrix users @@ -267,18 +272,15 @@ bridge: "example.com": user "@admin:example.com": admin -# Logging config. +# Logging config. See https://github.com/tulir/zeroconfig for details. logging: - # The directory for log files. Will be created if not found. - directory: ./logs - # Available variables: .Date for the file date and .Index for different log files on the same day. - file_name_format: "{{.Date}}-{{.Index}}.log" - # Date format for file names in the Go time format: https://golang.org/pkg/time/#pkg-constants - file_date_format: 2006-01-02 - # Log file permissions. - file_mode: 0600 - # Timestamp format for log entries in the Go time format. - timestamp_format: Jan _2, 2006 15:04:05 - # Minimum severity for log messages. - # Options: debug, info, warn, error, fatal - print_level: debug + min_level: debug + writers: + - type: stdout + format: pretty-colored + - type: file + format: json + filename: ./logs/mautrix-groupme.log + max_size: 100 + max_backups: 10 + compress: true diff --git a/formatting.go b/formatting.go index ac2273c..67fa750 100644 --- a/formatting.go +++ b/formatting.go @@ -18,127 +18,33 @@ package main import ( "fmt" - "regexp" - "strings" + "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" - "maunium.net/go/mautrix/id" - - "github.com/beeper/groupme-lib" + "maunium.net/go/mautrix/util/variationselector" ) -var italicRegex = regexp.MustCompile("([\\s>~*]|^)_(.+?)_([^a-zA-Z\\d]|$)") -var boldRegex = regexp.MustCompile("([\\s>_~]|^)\\*(.+?)\\*([^a-zA-Z\\d]|$)") -var strikethroughRegex = regexp.MustCompile("([\\s>_*]|^)~(.+?)~([^a-zA-Z\\d]|$)") -var codeBlockRegex = regexp.MustCompile("```(?:.|\n)+?```") +const formatterContextAllowedMentionsKey = "com.beeper.groupme.allowed_mentions" -const mentionedGMIDsContextKey = "net.maunium.groupme.mentioned_gmids" - -type Formatter struct { - bridge *GMBridge - - matrixHTMLParser *format.HTMLParser - - waReplString map[*regexp.Regexp]string - waReplFunc map[*regexp.Regexp]func(string) string - waReplFuncText map[*regexp.Regexp]func(string) string -} - -func NewFormatter(bridge *GMBridge) *Formatter { - formatter := &Formatter{ - bridge: bridge, - matrixHTMLParser: &format.HTMLParser{ - TabsToSpaces: 4, - Newline: "\n", - - PillConverter: func(displayname, mxid, eventID string, ctx format.Context) string { - if mxid[0] == '@' { - puppet := bridge.GetPuppetByMXID(id.UserID(mxid)) - if puppet != nil { - gmids, ok := ctx[mentionedGMIDsContextKey].([]groupme.ID) - if !ok { - ctx[mentionedGMIDsContextKey] = []groupme.ID{puppet.GMID} - } else { - ctx[mentionedGMIDsContextKey] = append(gmids, puppet.GMID) - } - return "@" + puppet.PhoneNumber() - } - } - return mxid - }, - BoldConverter: func(text string, _ format.Context) string { - return fmt.Sprintf("*%s*", text) - }, - ItalicConverter: func(text string, _ format.Context) string { - return fmt.Sprintf("_%s_", text) - }, - StrikethroughConverter: func(text string, _ format.Context) string { - return fmt.Sprintf("~%s~", text) - }, - MonospaceConverter: func(text string, _ format.Context) string { - return fmt.Sprintf("```%s```", text) - }, - MonospaceBlockConverter: func(text, language string, _ format.Context) string { - return fmt.Sprintf("```%s```", text) - }, - }, - waReplString: map[*regexp.Regexp]string{ - italicRegex: "$1$2$3", - boldRegex: "$1$2$3", - strikethroughRegex: "$1$2$3", - }, +func (br *GMBridge) pillConverter(displayname, mxid, eventID string, ctx format.Context) string { + // GroupMe only supports user mentions. + if len(mxid) == 0 || mxid[0] != '@' { + return displayname } - formatter.waReplFunc = map[*regexp.Regexp]func(string) string{ - codeBlockRegex: func(str string) string { - str = str[3 : len(str)-3] - if strings.ContainsRune(str, '\n') { - return fmt.Sprintf("
%s
", str) - } - return fmt.Sprintf("%s", str) - }, + + return fmt.Sprintf("@%s", displayname) +} + +var matrixHTMLParser = &format.HTMLParser{ + TabsToSpaces: 4, + Newline: "\n", + HorizontalLine: "\n---\n", +} + +func (portal *Portal) parseMatrixHTML(content *event.MessageEventContent) string { + if content.Format == event.FormatHTML && len(content.FormattedBody) > 0 { + return variationselector.FullyQualify(matrixHTMLParser.Parse(content.FormattedBody, format.NewContext())) + } else { + return variationselector.FullyQualify(content.Body) } - formatter.waReplFuncText = map[*regexp.Regexp]func(string) string{} - return formatter -} - -//func (formatter *Formatter) getMatrixInfoByJID(jid groupme.ID) (mxid id.UserID, displayname string) { -// if user := formatter.bridge.GetUserByJID(jid); user != nil { -// mxid = user.MXID -// displayname = string(user.MXID) -// } else if puppet := formatter.bridge.GetPuppetByJID(jid); puppet != nil { -// mxid = puppet.MXID -// displayname = puppet.Displayname -// } -// return -//} - -//func (formatter *Formatter) ParseWhatsApp(content *event.MessageEventContent, mentionedJIDs []groupme.ID) { -// output := html.EscapeString(content.Body) -// for regex, replacement := range formatter.waReplString { -// output = regex.ReplaceAllString(output, replacement) -// } -// for regex, replacer := range formatter.waReplFunc { -// output = regex.ReplaceAllStringFunc(output, replacer) -// } -// for _, jid := range mentionedJIDs { -// mxid, displayname := formatter.getMatrixInfoByJID(jid) -// number := "@" + strings.Replace(jid, whatsappExt.NewUserSuffix, "", 1) -// output = strings.Replace(output, number, fmt.Sprintf(`%s`, mxid, displayname), -1) -// content.Body = strings.Replace(content.Body, number, displayname, -1) -// } -// if output != content.Body { -// output = strings.Replace(output, "\n", "
", -1) -// content.FormattedBody = output -// content.Format = event.FormatHTML -// for regex, replacer := range formatter.waReplFuncText { -// content.Body = regex.ReplaceAllStringFunc(content.Body, replacer) -// } -// } -//} - -func (formatter *Formatter) ParseMatrix(html string) (string, []groupme.ID) { - ctx := make(format.Context) - result := formatter.matrixHTMLParser.Parse(html, ctx) - mentionedJIDs, _ := ctx[mentionedGMIDsContextKey].([]groupme.ID) - return result, mentionedJIDs } diff --git a/go.mod b/go.mod index ef5dba8..5221a25 100644 --- a/go.mod +++ b/go.mod @@ -3,42 +3,43 @@ module github.com/beeper/groupme go 1.19 require ( - github.com/Rhymen/go-whatsapp v0.1.1 github.com/beeper/groupme-lib v0.2.1-0.20221021205945-8f23e04eea71 github.com/gabriel-vasile/mimetype v1.1.2 - github.com/gorilla/websocket v1.5.0 github.com/karmanyaahm/wray v0.0.0-20210303233435-756d58657c14 github.com/lib/pq v1.10.7 - github.com/mattn/go-sqlite3 v1.14.15 + github.com/mattn/go-sqlite3 v1.14.16 github.com/prometheus/client_golang v1.9.0 - maunium.net/go/maulogger/v2 v2.3.2 - maunium.net/go/mautrix v0.12.3-0.20221020190005-d0c13d2f04a1 + maunium.net/go/maulogger/v2 v2.4.1 + maunium.net/go/mautrix v0.15.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect github.com/golang/protobuf v1.4.3 // indirect github.com/google/uuid v1.2.0 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.15.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect - github.com/rs/zerolog v1.28.0 // indirect - github.com/tidwall/gjson v1.14.3 // indirect + github.com/rs/zerolog v1.29.0 // indirect + github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/sjson v1.2.5 // indirect - github.com/yuin/goldmark v1.5.2 // indirect - golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect - golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect + github.com/yuin/goldmark v1.5.4 // indirect + go.mau.fi/zeroconfig v0.1.2 // indirect + golang.org/x/crypto v0.6.0 // indirect + golang.org/x/net v0.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/protobuf v1.25.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect maunium.net/go/mauflag v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index 2f806a8..73c99d9 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA= -github.com/Rhymen/go-whatsapp v0.1.1 h1:OK+bCugQcr2YjyYKeDzULqCtM50TPUFM6LvQtszKfcw= -github.com/Rhymen/go-whatsapp v0.1.1/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk= -github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME= -github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU= -github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw= -github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:suwzklatySS3Q0+NCxCDh5hYfgXdQUWU1DNcxwAxStM= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= @@ -48,6 +40,7 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -89,7 +82,6 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= 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/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -124,8 +116,6 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -184,17 +174,15 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -241,7 +229,6 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 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/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.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= @@ -279,8 +266,8 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn 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/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= -github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= +github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= +github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= 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/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -289,7 +276,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -307,10 +293,10 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= -github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= @@ -321,10 +307,12 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= -github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= +github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto= +go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -337,14 +325,13 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 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-20190131182504-b8fe1690c613/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-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= -golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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= @@ -370,8 +357,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -390,7 +377,6 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -405,8 +391,8 @@ golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/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-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -434,7 +420,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/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-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= @@ -469,6 +454,8 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= @@ -487,9 +474,9 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/maulogger/v2 v2.3.2 h1:1XmIYmMd3PoQfp9J+PaHhpt80zpfmMqaShzUTC7FwY0= -maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= -maunium.net/go/mautrix v0.12.3-0.20221020190005-d0c13d2f04a1 h1:daraaP+GcSrFLgVckFpp+ciVrtQeG5s2w3Fi8AInaj8= -maunium.net/go/mautrix v0.12.3-0.20221020190005-d0c13d2f04a1/go.mod h1:bCw45Qx/m9qsz7eazmbe7Rzq5ZbTPzwRE1UgX2S9DXs= +maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8= +maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho= +maunium.net/go/mautrix v0.15.0 h1:gkK9HXc1SSPwY7qOAqchzj2xxYqiOYeee8lr28A2g/o= +maunium.net/go/mautrix v0.15.0/go.mod h1:1v8QVDd7q/eJ+eg4sgeOSEafBAFhkt4ab2i97M3IkNQ= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/main.go b/main.go index 0d8971e..05a1992 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( _ "embed" "sync" + "maunium.net/go/mautrix" "maunium.net/go/mautrix/bridge" "maunium.net/go/mautrix/bridge/commands" "maunium.net/go/mautrix/bridge/status" @@ -48,7 +49,6 @@ type GMBridge struct { Config *config.Config DB *database.Database Provisioning *ProvisioningAPI - Formatter *Formatter Metrics *MetricsHandler usersByMXID map[id.UserID]*User @@ -70,10 +70,16 @@ func (br *GMBridge) Init() { br.CommandProcessor = commands.NewProcessor(&br.Bridge) br.RegisterCommands() + matrixHTMLParser.PillConverter = br.pillConverter + Segment.log = br.Log.Sub("Segment") Segment.key = br.Config.SegmentKey + Segment.userID = br.Config.SegmentUserID if Segment.IsEnabled() { Segment.log.Infoln("Segment metrics are enabled") + if Segment.userID != "" { + Segment.log.Infoln("Overriding Segment user_id with %v", Segment.userID) + } } br.DB = database.New(br.Bridge.DB, br.Log.Sub("Database")) @@ -83,47 +89,18 @@ func (br *GMBridge) Init() { br.Provisioning = &ProvisioningAPI{bridge: br} } - br.Formatter = NewFormatter(br) br.Metrics = NewMetricsHandler(br.Config.Metrics.Listen, br.Log.Sub("Metrics"), br.DB) br.MatrixHandler.TrackEventDuration = br.Metrics.TrackMatrixEvent } -func (bridge *GMBridge) Start() { - if bridge.Provisioning != nil { - bridge.Log.Debugln("Initializing provisioning API") - bridge.Provisioning.Init() +func (br *GMBridge) Start() { + if br.Provisioning != nil { + br.Log.Debugln("Initializing provisioning API") + br.Provisioning.Init() } - go bridge.StartUsers() - if bridge.Config.Metrics.Enabled { - go bridge.Metrics.Start() - } -} - -func (bridge *GMBridge) UpdateBotProfile() { - bridge.Log.Debugln("Updating bot profile") - botConfig := bridge.Config.AppService.Bot - - var err error - var mxc id.ContentURI - if botConfig.Avatar == "remove" { - err = bridge.Bot.SetAvatarURL(mxc) - } else if len(botConfig.Avatar) > 0 { - mxc, err = id.ParseContentURI(botConfig.Avatar) - if err == nil { - err = bridge.Bot.SetAvatarURL(mxc) - } - } - if err != nil { - bridge.Log.Warnln("Failed to update bot avatar:", err) - } - - if botConfig.Displayname == "remove" { - err = bridge.Bot.SetDisplayName("") - } else if len(botConfig.Avatar) > 0 { - err = bridge.Bot.SetDisplayName(botConfig.Displayname) - } - if err != nil { - bridge.Log.Warnln("Failed to update bot displayname:", err) + go br.StartUsers() + if br.Config.Metrics.Enabled { + go br.Metrics.Start() } } @@ -131,7 +108,7 @@ func (br *GMBridge) StartUsers() { br.Log.Debugln("Starting users") foundAnySessions := false for _, user := range br.GetAllUsers() { - if user.GMID.String() != "" { + if user.GMID.Valid() { foundAnySessions = true } go user.Connect() @@ -174,6 +151,20 @@ func (br *GMBridge) GetConfigPtr() interface{} { return br.Config } +const unstableFeatureBatchSending = "org.matrix.msc2716" + +func (br *GMBridge) CheckFeatures(versions *mautrix.RespVersions) (string, bool) { + if br.Config.Bridge.HistorySync.Backfill { + supported, known := versions.UnstableFeatures[unstableFeatureBatchSending] + if !known { + return "Backfilling is enabled in bridge config, but homeserver does not support MSC2716 batch sending", false + } else if !supported { + return "Backfilling is enabled in bridge config, but MSC2716 batch sending is not enabled on homeserver", false + } + } + return "", true +} + func main() { br := &GMBridge{ usersByMXID: make(map[id.UserID]*User), diff --git a/messagetracking.go b/messagetracking.go index a264f8f..346da66 100644 --- a/messagetracking.go +++ b/messagetracking.go @@ -97,7 +97,6 @@ func (portal *Portal) sendStatusEvent(evtID, lastRetry id.EventID, err error) { content.Reason, content.Status, _, _, content.Message = errorToStatusReason(err) content.Error = err.Error() } - content.FillLegacyBooleans() _, err = intent.SendMessageEvent(portal.MXID, event.BeeperMessageStatus, &content) if err != nil { portal.log.Warnln("Failed to send message status event:", err) diff --git a/portal.go b/portal.go index e26b461..5f7b10d 100644 --- a/portal.go +++ b/portal.go @@ -37,7 +37,6 @@ import ( "maunium.net/go/mautrix/bridge/bridgeconfig" "maunium.net/go/mautrix/crypto/attachment" - "github.com/Rhymen/go-whatsapp" "github.com/gabriel-vasile/mimetype" "github.com/beeper/groupme-lib" @@ -385,7 +384,7 @@ func (portal *Portal) SyncParticipants(metadata *groupme.Group) { if !shouldBePresent { _, err := portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{ UserID: member, - Reason: "User had left this WhatsApp chat", + Reason: "User had left this GroupMe chat", }) if err != nil { portal.log.Warnfln("Failed to kick user %s who had left: %v", member, err) @@ -1158,7 +1157,6 @@ func (portal *Portal) HandleTextMessage(source *User, message *groupme.Message) sendText = sendText && text } - // portal.bridge.Formatter.ParseWhatsApp(content, message.ContextInfo.MentionedJID) // portal.SetReply(content, message.ContextInfo) //TODO: mentions content := &event.MessageEventContent{ @@ -1262,108 +1260,6 @@ func (portal *Portal) HandleTextMessage(source *User, message *groupme.Message) // return // } -func (portal *Portal) HandleLocationMessage(source *User, message whatsapp.LocationMessage) { - // intent := portal.startHandling(source, message.Info) - // if intent == nil { - // return - // } - // - // url := message.Url - // if len(url) == 0 { - // url = fmt.Sprintf("https://maps.google.com/?q=%.5f,%.5f", message.DegreesLatitude, message.DegreesLongitude) - // } - // name := message.Name - // if len(name) == 0 { - // latChar := 'N' - // if message.DegreesLatitude < 0 { - // latChar = 'S' - // } - // longChar := 'E' - // if message.DegreesLongitude < 0 { - // longChar = 'W' - // } - // name = fmt.Sprintf("%.4f° %c %.4f° %c", math.Abs(message.DegreesLatitude), latChar, math.Abs(message.DegreesLongitude), longChar) - // } - // - // content := &event.MessageEventContent{ - // MsgType: event.MsgLocation, - // Body: fmt.Sprintf("Location: %s\n%s\n%s", name, message.Address, url), - // Format: event.FormatHTML, - // FormattedBody: fmt.Sprintf("Location: %s
%s", url, name, message.Address), - // GeoURI: fmt.Sprintf("geo:%.5f,%.5f", message.DegreesLatitude, message.DegreesLongitude), - // } - // - // if len(message.JpegThumbnail) > 0 { - // thumbnailMime := http.DetectContentType(message.JpegThumbnail) - // uploadedThumbnail, _ := intent.UploadBytes(message.JpegThumbnail, thumbnailMime) - // if uploadedThumbnail != nil { - // cfg, _, _ := image.DecodeConfig(bytes.NewReader(message.JpegThumbnail)) - // content.Info = &event.FileInfo{ - // ThumbnailInfo: &event.FileInfo{ - // Size: len(message.JpegThumbnail), - // Width: cfg.Width, - // Height: cfg.Height, - // MimeType: thumbnailMime, - // }, - // ThumbnailURL: uploadedThumbnail.ContentURI.CUString(), - // } - // } - // } - // - // portal.SetReply(content, message.ContextInfo) - // - // _, _ = intent.UserTyping(portal.MXID, false, 0) - // resp, err := portal.sendMessage(intent, event.EventMessage, content, int64(message.Info.Timestamp*1000)) - // if err != nil { - // portal.log.Errorfln("Failed to handle message %s: %v", message.Info.Id, err) - // return - // } - // portal.finishHandling(source, message.Info.Source, resp.EventID) - //} - - //func (portal *Portal) HandleContactMessage(source *User, message whatsapp.ContactMessage) { - // intent := portal.startHandling(source, message.Info) - // if intent == nil { - // return - // } - // - // fileName := fmt.Sprintf("%s.vcf", message.DisplayName) - // data := []byte(message.Vcard) - // mimeType := "text/vcard" - // data, uploadMimeType, file := portal.encryptFile(data, mimeType) - // - // uploadResp, err := intent.UploadBytesWithName(data, uploadMimeType, fileName) - // if err != nil { - // portal.log.Errorfln("Failed to upload vcard of %s: %v", message.DisplayName, err) - // return - // } - // - // content := &event.MessageEventContent{ - // Body: fileName, - // MsgType: event.MsgFile, - // File: file, - // Info: &event.FileInfo{ - // MimeType: mimeType, - // Size: len(message.Vcard), - // }, - // } - // if content.File != nil { - // content.File.URL = uploadResp.ContentURI.CUString() - // } else { - // content.URL = uploadResp.ContentURI.CUString() - // } - // - // portal.SetReply(content, message.ContextInfo) - // - // _, _ = intent.UserTyping(portal.MXID, false, 0) - // resp, err := portal.sendMessage(intent, event.EventMessage, content, int64(message.Info.Timestamp*1000)) - // if err != nil { - // portal.log.Errorfln("Failed to handle message %s: %v", message.Info.Id, err) - // return - // } - // portal.finishHandling(source, message.Info.Source, resp.EventID) -} - func (portal *Portal) sendMediaBridgeFailure(source *User, intent *appservice.IntentAPI, message groupme.Message, bridgeErr error) { portal.log.Errorfln("Failed to bridge media for %s: %v", message.UserID.String(), bridgeErr) resp, err := portal.sendMessage(intent, event.EventMessage, &event.MessageEventContent{ @@ -1467,93 +1363,13 @@ func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) ([]*g case event.MsgText, event.MsgEmote, event.MsgNotice: text := content.Body if content.Format == event.FormatHTML { - text, _ = portal.bridge.Formatter.ParseMatrix(content.FormattedBody) - //TODO mentions + text = portal.parseMatrixHTML(content) } if content.MsgType == event.MsgEmote && !relaybotFormatted { text = "/me " + text } info.Text = text - // if ctxInfo.StanzaId != nil || ctxInfo.MentionedJid != nil { - // info.Message.ExtendedTextMessage = &waProto.ExtendedTextMessage{ - // Text: &text, - // ContextInfo: ctxInfo, - // } - // } - //else { - // info.Message.Conversation = &text - // } - // case event.MsgImage: - // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaImage) - // if media == nil { - // return nil, sender - // } - // ctxInfo.MentionedJid = media.MentionedJIDs - // info.Message.ImageMessage = &waProto.ImageMessage{ - // ContextInfo: ctxInfo, - // Caption: &media.Caption, - // JpegThumbnail: media.Thumbnail, - // Url: &media.URL, - // MediaKey: media.MediaKey, - // Mimetype: &content.GetInfo().MimeType, - // FileEncSha256: media.FileEncSHA256, - // FileSha256: media.FileSHA256, - // FileLength: &media.FileLength, - // } - // case event.MsgVideo: - // gifPlayback := content.GetInfo().MimeType == "image/gif" - // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaVideo) - // if media == nil { - // return nil, sender - // } - // duration := uint32(content.GetInfo().Duration) - // ctxInfo.MentionedJid = media.MentionedJIDs - // info.Message.VideoMessage = &waProto.VideoMessage{ - // ContextInfo: ctxInfo, - // Caption: &media.Caption, - // JpegThumbnail: media.Thumbnail, - // Url: &media.URL, - // MediaKey: media.MediaKey, - // Mimetype: &content.GetInfo().MimeType, - // GifPlayback: &gifPlayback, - // Seconds: &duration, - // FileEncSha256: media.FileEncSHA256, - // FileSha256: media.FileSHA256, - // FileLength: &media.FileLength, - // } - // case event.MsgAudio: - // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaAudio) - // if media == nil { - // return nil, sender - // } - // duration := uint32(content.GetInfo().Duration) - // info.Message.AudioMessage = &waProto.AudioMessage{ - // ContextInfo: ctxInfo, - // Url: &media.URL, - // MediaKey: media.MediaKey, - // Mimetype: &content.GetInfo().MimeType, - // Seconds: &duration, - // FileEncSha256: media.FileEncSHA256, - // FileSha256: media.FileSHA256, - // FileLength: &media.FileLength, - // } - // case event.MsgFile: - // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaDocument) - // if media == nil { - // return nil, sender - // } - // info.Message.DocumentMessage = &waProto.DocumentMessage{ - // ContextInfo: ctxInfo, - // Url: &media.URL, - // Title: &content.Body, - // FileName: &content.Body, - // MediaKey: media.MediaKey, - // Mimetype: &content.GetInfo().MimeType, - // FileEncSha256: media.FileEncSHA256, - // FileSha256: media.FileSHA256, - // FileLength: &media.FileLength, - // } default: portal.log.Debugln("Unhandled Matrix event %s: unknown msgtype %s", evt.ID, content.MsgType) return nil, sender @@ -1562,13 +1378,6 @@ func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) ([]*g } func (portal *Portal) wasMessageSent(sender *User, id string) bool { - // _, err := sender.Conn.LoadMessagesAfter(portal.Key.JID, id, true, 0) - // if err != nil { - // if err != whatsapp.ErrServerRespondedWith404 { - // portal.log.Warnfln("Failed to check if message was bridged without response: %v", err) - // } - // return false - // } return true } @@ -1620,98 +1429,9 @@ func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *groupme.Mess } } return m, nil - // errChan := make(chan error, 1) - // go sender.Conn.SendRaw(info, errChan) - - // var err error - // var errorEventID id.EventID - // select { - // case err = <-errChan: - // var statusResp whatsapp.StatusResponse - // if !isRetry && errors.As(err, &statusResp) && statusResp.Status == 599 { - // portal.log.Debugfln("599 status response sending %s to WhatsApp (%+v), retrying...", evt.ID, statusResp) - // errorEventID = portal.sendErrorMessage(fmt.Sprintf("%v. The bridge will retry in 5 seconds.", err)) - // time.Sleep(5 * time.Second) - // portal.sendRaw(sender, evt, info, true) - // } - // case <-time.After(time.Duration(portal.bridge.Config.Bridge.ConnectionTimeout) * time.Second): - // if portal.bridge.Config.Bridge.FetchMessageOnTimeout && portal.wasMessageSent(sender, info.Key.GetId()) { - // portal.log.Debugln("Matrix event %s was bridged, but response didn't arrive within timeout") - // portal.sendDeliveryReceipt(evt.ID) - // } else { - // portal.log.Warnfln("Response when bridging Matrix event %s is taking long to arrive", evt.ID) - // errorEventID = portal.sendErrorMessage(timeout.Error()) - // } - // err = <-errChan - // } - // if err != nil { - // portal.log.Errorfln("Error handling Matrix event %s: %v", evt.ID, err) - // var statusResp whatsapp.StatusResponse - // if errors.As(err, &statusResp) && statusResp.Status == 599 { - // portal.log.Debugfln("599 status response data: %+v", statusResp) - // } - // portal.sendErrorMessage(err.Error()) - // } else { - // portal.log.Debugfln("Handled Matrix event %s", evt.ID) - // portal.sendDeliveryReceipt(evt.ID) - // } - // if errorEventID != "" { - // _, err = portal.MainIntent().RedactEvent(portal.MXID, errorEventID) - // if err != nil { - // portal.log.Warnfln("Failed to redact timeout warning message %s: %v", errorEventID, err) - // } - // } } func (portal *Portal) HandleMatrixRedaction(sender *User, evt *event.Event) { - // if portal.IsPrivateChat() && sender.JID != portal.Key.Receiver { - // return - // } - - // msg := portal.bridge.DB.Message.GetByMXID(evt.Redacts) - // if msg == nil || msg.Sender != sender.JID { - // return - // } - - // ts := uint64(evt.Timestamp / 1000) - // status := waProto.WebMessageInfo_PENDING - // protoMsgType := waProto.ProtocolMessage_REVOKE - // fromMe := true - // info := &waProto.WebMessageInfo{ - // Key: &waProto.MessageKey{ - // FromMe: &fromMe, - // Id: makeMessageID(), - // RemoteJid: &portal.Key.JID, - // }, - // MessageTimestamp: &ts, - // Message: &waProto.Message{ - // ProtocolMessage: &waProto.ProtocolMessage{ - // Type: &protoMsgType, - // Key: &waProto.MessageKey{ - // FromMe: &fromMe, - // Id: &msg.JID, - // RemoteJid: &portal.Key.JID, - // }, - // }, - // }, - // Status: &status, - // } - // errChan := make(chan error, 1) - // go sender.Conn.SendRaw(info, errChan) - - // var err error - // select { - // case err = <-errChan: - // case <-time.After(time.Duration(portal.bridge.Config.Bridge.ConnectionTimeout) * time.Second): - // portal.log.Warnfln("Response when bridging Matrix redaction %s is taking long to arrive", evt.ID) - // err = <-errChan - // } - // if err != nil { - // portal.log.Errorfln("Error handling Matrix redaction %s: %v", evt.ID, err) - // } else { - // portal.log.Debugln("Handled Matrix redaction %s of %s", evt.ID, evt.Redacts) - // portal.sendDeliveryReceipt(evt.ID) - // } } func (portal *Portal) Delete() { @@ -1811,25 +1531,7 @@ func (portal *Portal) HandleMatrixLeave(sender *User) { } func (portal *Portal) HandleMatrixKick(sender *User, evt *event.Event) { - // puppet := portal.bridge.GetPuppetByMXID(id.UserID(evt.GetStateKey())) - // if puppet != nil { - // resp, err := sender.Conn.RemoveMember(portal.Key.JID, []string{puppet.JID}) - // if err != nil { - // portal.log.Errorfln("Failed to kick %s from group as %s: %v", puppet.JID, sender.MXID, err) - // return - // } - // portal.log.Infoln("Kick %s response: %s", puppet.JID, <-resp) - // } } func (portal *Portal) HandleMatrixInvite(sender *User, evt *event.Event) { - // puppet := portal.bridge.GetPuppetByMXID(id.UserID(evt.GetStateKey())) - // if puppet != nil { - // resp, err := sender.Conn.AddMember(portal.Key.JID, []string{puppet.JID}) - // if err != nil { - // portal.log.Errorfln("Failed to add %s to group as %s: %v", puppet.JID, sender.MXID, err) - // return - // } - // portal.log.Infoln("Add %s response: %s", puppet.JID, <-resp) - // } } diff --git a/provisioning.go b/provisioning.go index 0afa7e3..fe3fdcb 100644 --- a/provisioning.go +++ b/provisioning.go @@ -21,7 +21,6 @@ import ( "net/http" "strings" - "github.com/gorilla/websocket" log "maunium.net/go/maulogger/v2" "maunium.net/go/mautrix/id" @@ -37,13 +36,6 @@ func (prov *ProvisioningAPI) Init() { prov.log.Debugln("Enabling provisioning API at", prov.bridge.Config.Bridge.Provisioning.Prefix) r := prov.bridge.AS.Router.PathPrefix(prov.bridge.Config.Bridge.Provisioning.Prefix).Subrouter() r.Use(prov.AuthMiddleware) - r.HandleFunc("/ping", prov.Ping).Methods(http.MethodGet) - r.HandleFunc("/login", prov.Login) - r.HandleFunc("/logout", prov.Logout).Methods(http.MethodPost) - r.HandleFunc("/delete_session", prov.DeleteSession).Methods(http.MethodPost) - r.HandleFunc("/delete_connection", prov.DeleteConnection).Methods(http.MethodPost) - r.HandleFunc("/disconnect", prov.Disconnect).Methods(http.MethodPost) - r.HandleFunc("/reconnect", prov.Reconnect).Methods(http.MethodPost) } func (prov *ProvisioningAPI) AuthMiddleware(h http.Handler) http.Handler { @@ -53,8 +45,8 @@ func (prov *ProvisioningAPI) AuthMiddleware(h http.Handler) http.Handler { authParts := strings.Split(r.Header.Get("Sec-WebSocket-Protocol"), ",") for _, part := range authParts { part = strings.TrimSpace(part) - if strings.HasPrefix(part, "net.maunium.whatsapp.auth-") { - auth = part[len("net.maunium.whatsapp.auth-"):] + if strings.HasPrefix(part, "com.beeper.groupme.auth-") { + auth = part[len("com.beeper.groupme.auth-"):] break } } @@ -85,326 +77,8 @@ type Response struct { Status string `json:"status"` } -func (prov *ProvisioningAPI) DeleteSession(w http.ResponseWriter, r *http.Request) { - // user := r.Context().Value("user").(*User) - // if user.Session == nil && user.Conn == nil { - // jsonResponse(w, http.StatusNotFound, Error{ - // Error: "Nothing to purge: no session information stored and no active connection.", - // ErrCode: "no session", - // }) - // return - // } - // user.SetSession(nil) - // if user.Conn != nil { - // _, _ = user.Conn.Disconnect() - // user.Conn.RemoveHandlers() - // user.Conn = nil - // user.bridge.Metrics.TrackConnectionState(user.JID, false) - // } - // jsonResponse(w, http.StatusOK, Response{true, "Session information purged"}) -} - -func (prov *ProvisioningAPI) DeleteConnection(w http.ResponseWriter, r *http.Request) { - // user := r.Context().Value("user").(*User) - // if user.Conn == nil { - // jsonResponse(w, http.StatusNotFound, Error{ - // Error: "You don't have a WhatsApp connection.", - // ErrCode: "not connected", - // }) - // return - // } - // sess, err := user.Conn.Disconnect() - // if err == nil && len(sess.Wid) > 0 { - // user.SetSession(&sess) - // } - // user.Conn.RemoveHandlers() - // user.Conn = nil - // user.bridge.Metrics.TrackConnectionState(user.JID, false) - // jsonResponse(w, http.StatusOK, Response{true, "Disconnected from WhatsApp and connection deleted"}) -} - -func (prov *ProvisioningAPI) Disconnect(w http.ResponseWriter, r *http.Request) { - user := r.Context().Value("user").(*User) - if user.Conn == nil { - jsonResponse(w, http.StatusNotFound, Error{ - Error: "You don't have a WhatsApp connection.", - ErrCode: "no connection", - }) - return - } - //sess, err := - //user.Conn.Stop(context.TODO()) - // if err == whatsapp.ErrNotConnected { - // jsonResponse(w, http.StatusNotFound, Error{ - // Error: "You were not connected", - // ErrCode: "not connected", - // }) - // return - // } else if err != nil { - // user.log.Warnln("Error while disconnecting:", err) - // jsonResponse(w, http.StatusInternalServerError, Error{ - // Error: fmt.Sprintf("Unknown error while disconnecting: %v", err), - // ErrCode: err.Error(), - // }) - // return - // } else if len(sess.Wid) > 0 { - // user.SetSession(&sess) - // } - user.bridge.Metrics.TrackConnectionState(user.GMID, false) - jsonResponse(w, http.StatusOK, Response{true, "Disconnected from WhatsApp"}) -} - -func (prov *ProvisioningAPI) Reconnect(w http.ResponseWriter, r *http.Request) { - // user := r.Context().Value("user").(*User) - // if user.Conn == nil { - // if user.Session == nil { - // jsonResponse(w, http.StatusForbidden, Error{ - // Error: "No existing connection and no session. Please log in first.", - // ErrCode: "no session", - // }) - // } else { - // user.Connect(false) - // jsonResponse(w, http.StatusOK, Response{true, "Created connection to WhatsApp."}) - // } - // return - // } - - // wasConnected := true - // sess, err := user.Conn.Disconnect() - // if err == whatsapp.ErrNotConnected { - // wasConnected = false - // } else if err != nil { - // user.log.Warnln("Error while disconnecting:", err) - // } else if len(sess.Wid) > 0 { - // user.SetSession(&sess) - // } - - // err = user.Conn.Restore() - // if err == whatsapp.ErrInvalidSession { - // if user.Session != nil { - // user.log.Debugln("Got invalid session error when reconnecting, but user has session. Retrying using RestoreWithSession()...") - // var sess whatsapp.Session - // sess, err = user.Conn.RestoreWithSession(*user.Session) - // if err == nil { - // user.SetSession(&sess) - // } - // } else { - // jsonResponse(w, http.StatusForbidden, Error{ - // Error: "You're not logged in", - // ErrCode: "not logged in", - // }) - // return - // } - // } else if err == whatsapp.ErrLoginInProgress { - // jsonResponse(w, http.StatusConflict, Error{ - // Error: "A login or reconnection is already in progress.", - // ErrCode: "login in progress", - // }) - // return - // } else if err == whatsapp.ErrAlreadyLoggedIn { - // jsonResponse(w, http.StatusConflict, Error{ - // Error: "You were already connected.", - // ErrCode: err.Error(), - // }) - // return - // } - // if err != nil { - // user.log.Warnln("Error while reconnecting:", err) - // if err.Error() == "restore session connection timed out" { - // jsonResponse(w, http.StatusForbidden, Error{ - // Error: "Reconnection timed out. Is WhatsApp on your phone reachable?", - // ErrCode: err.Error(), - // }) - // } else { - // jsonResponse(w, http.StatusForbidden, Error{ - // Error: fmt.Sprintf("Unknown error while reconnecting: %v", err), - // ErrCode: err.Error(), - // }) - // } - // user.log.Debugln("Disconnecting due to failed session restore in reconnect command...") - // sess, err := user.Conn.Disconnect() - // if err != nil { - // user.log.Errorln("Failed to disconnect after failed session restore in reconnect command:", err) - // } else if len(sess.Wid) > 0 { - // user.SetSession(&sess) - // } - // return - // } - // user.ConnectionErrors = 0 - // user.PostLogin() - - // var msg string - // if wasConnected { - // msg = "Reconnected successfully." - // } else { - // msg = "Connected successfully." - // } - - // jsonResponse(w, http.StatusOK, Response{true, msg}) -} - -func (prov *ProvisioningAPI) Ping(w http.ResponseWriter, r *http.Request) { - // user := r.Context().Value("user").(*User) - // wa := map[string]interface{}{ - // "has_session": user.Client != nil, - // "management_room": user.ManagementRoom, - // "jid": user.JID, - // "conn": nil, - // "ping": nil, - // } - // if user.Conn != nil { - // wa["conn"] = map[string]interface{}{ - // "is_connected": user.IsConnected(), - // "is_logged_in": user.IsLoggedIn(), - // "is_login_in_progress": user.IsLoginInProgress(), - // } - // err := user.Conn.AdminTest() - // wa["ping"] = map[string]interface{}{ - // "ok": err == nil, - // "err": err, - // } - // } - // resp := map[string]interface{}{ - // "mxid": user.MXID, - // "admin": user.Admin, - // "whitelisted": user.Whitelisted, - // "relaybot_whitelisted": user.RelaybotWhitelisted, - // "whatsapp": wa, - // } - // jsonResponse(w, http.StatusOK, resp) -} - func jsonResponse(w http.ResponseWriter, status int, response interface{}) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(response) } - -func (prov *ProvisioningAPI) Logout(w http.ResponseWriter, r *http.Request) { - // user := r.Context().Value("user").(*User) - // if user.Session == nil { - // jsonResponse(w, http.StatusNotFound, Error{ - // Error: "You're not logged in", - // ErrCode: "not logged in", - // }) - // return - // } - - // force := strings.ToLower(r.URL.Query().Get("force")) != "false" - - // if user.Conn == nil { - // if !force { - // jsonResponse(w, http.StatusNotFound, Error{ - // Error: "You're not connected", - // ErrCode: "not connected", - // }) - // } - // } else { - // err := user.Conn.Logout() - // if err != nil { - // user.log.Warnln("Error while logging out:", err) - // if !force { - // jsonResponse(w, http.StatusInternalServerError, Error{ - // Error: fmt.Sprintf("Unknown error while logging out: %v", err), - // ErrCode: err.Error(), - // }) - // return - // } - // } - // _, err = user.Conn.Disconnect() - // if err != nil { - // user.log.Warnln("Error while disconnecting after logout:", err) - // } - // user.Conn.RemoveHandlers() - // user.Conn = nil - // } - - // user.bridge.Metrics.TrackConnectionState(user.JID, false) - // user.removeFromJIDMap() - - // // TODO this causes a foreign key violation, which should be fixed - // //ce.User.JID = "" - // user.SetSession(nil) - // jsonResponse(w, http.StatusOK, Response{true, "Logged out successfully."}) -} - -var upgrader = websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - return true - }, - Subprotocols: []string{"net.maunium.whatsapp.login"}, -} - -func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) { - // userID := r.URL.Query().Get("user_id") - // user := prov.bridge.GetUserByMXID(id.UserID(userID)) - - // if len(ce.Args) < 1 { - // // Return error that the token needs to be longer than 0 length - // // ce.Reply(`Get your access token from https://dev.groupme.com/ which should be the first argument to login`) - // return - // } - // user.Token = ce.Args[0] - - // user.addToJIDMap() - // // ce.Reply("Successfully logged in, synchronizing chats...") - // user.PostLogin() - // user.Connect() - - // c, err := upgrader.Upgrade(w, r, nil) - // if err != nil { - // prov.log.Errorfln("Failed to upgrade connection to websocket:", err) - // return - // } - // defer c.Close() - - // if !user.Connect(true) { - // user.log.Debugln("Connect() returned false, assuming error was logged elsewhere and canceling login.") - // _ = c.WriteJSON(Error{ - // Error: "Failed to connect to WhatsApp", - // ErrCode: "connection error", - // }) - // return - // } - - // qrChan := make(chan string, 3) - // go func() { - // for code := range qrChan { - // if code == "stop" { - // return - // } - // _ = c.WriteJSON(map[string]interface{}{ - // "code": code, - // }) - // } - // }() - // session, err := user.Conn.LoginWithRetry(qrChan, user.bridge.Config.Bridge.LoginQRRegenCount) - // qrChan <- "stop" - // if err != nil { - // var msg string - // if err == whatsapp.ErrAlreadyLoggedIn { - // msg = "You're already logged in" - // } else if err == whatsapp.ErrLoginInProgress { - // msg = "You have a login in progress already." - // } else if err == whatsapp.ErrLoginTimedOut { - // msg = "QR code scan timed out. Please try again." - // } else { - // user.log.Warnln("Failed to log in:", err) - // msg = fmt.Sprintf("Unknown error while logging in: %v", err) - // } - // _ = c.WriteJSON(Error{ - // Error: msg, - // ErrCode: err.Error(), - // }) - // return - // } - // user.ConnectionErrors = 0 - // user.JID = strings.Replace(user.Conn.Info.Wid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, 1) - // user.addToJIDMap() - // user.SetSession(&session) - // _ = c.WriteJSON(map[string]interface{}{ - // "success": true, - // "jid": user.JID, - // }) - // user.PostLogin() -} diff --git a/segment.go b/segment.go index f026ad8..b5a90d9 100644 --- a/segment.go +++ b/segment.go @@ -30,6 +30,7 @@ const SegmentURL = "https://api.segment.io/v1/track" type SegmentClient struct { key string + userID string log log.Logger client http.Client } @@ -38,8 +39,14 @@ var Segment SegmentClient func (sc *SegmentClient) trackSync(userID id.UserID, event string, properties map[string]interface{}) error { var buf bytes.Buffer + var segmentUserID string + if Segment.userID != "" { + segmentUserID = Segment.userID + } else { + segmentUserID = userID.String() + } err := json.NewEncoder(&buf).Encode(map[string]interface{}{ - "userId": userID, + "userId": segmentUserID, "event": event, "properties": properties, }) diff --git a/user.go b/user.go index 2c0494e..469579d 100644 --- a/user.go +++ b/user.go @@ -26,9 +26,6 @@ import ( "time" log "maunium.net/go/maulogger/v2" - - "github.com/Rhymen/go-whatsapp" - "maunium.net/go/mautrix" "maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/bridge" @@ -206,7 +203,7 @@ func (br *GMBridge) NewUser(dbUser *database.User) *User { user.PermissionLevel = user.bridge.Config.Bridge.Permissions.Get(user.MXID) user.Whitelisted = user.PermissionLevel >= bridgeconfig.PermissionLevelUser user.Admin = user.PermissionLevel >= bridgeconfig.PermissionLevelAdmin - user.BridgeState = br.NewBridgeStateQueue(user, user.log) + user.BridgeState = br.NewBridgeStateQueue(user) go user.handleMessageLoop() go user.runMessageRingBuffer() return user @@ -332,14 +329,6 @@ func (user *User) SetManagementRoom(roomID id.RoomID) { user.Update() } -func (user *User) SetSession(session *whatsapp.Session) { - // user.Session = session - // if session == nil { - // user.LastConnection = 0 - // } - // user.Update() -} - func (user *User) Connect() bool { if user.Conn != nil { return true @@ -347,14 +336,14 @@ func (user *User) Connect() bool { return false } - user.log.Debugln("Connecting to GroupMe") + user.log.Debugfln("Connecting to GroupMe") timeout := time.Duration(user.bridge.Config.GroupMe.ConnectionTimeout) if timeout == 0 { timeout = 20 } conn := groupme.NewPushSubscription(context.Background()) user.Conn = &conn - user.Conn.StartListening(context.TODO(), groupmeext.NewFayeClient(user.log)) + user.Conn.StartListening(context.Background(), groupmeext.NewFayeClient(user.log)) user.Conn.AddFullHandler(user) //TODO: typing notification? @@ -737,112 +726,7 @@ func (user *User) UpdateDirectChats(chats map[id.UserID][]id.RoomID) { } } -func (user *User) HandleContactList(contacts []whatsapp.Contact) { - contactMap := make(map[string]whatsapp.Contact) - for _, contact := range contacts { - contactMap[contact.Jid] = contact - } - go user.syncPuppets(contactMap) -} - -func (user *User) syncPuppets(contacts map[string]whatsapp.Contact) { - // if contacts == nil { - // contacts = user.Conn.Store.Contacts - // } - // user.log.Infoln("Syncing puppet info from contacts") - // for jid, contact := range contacts { - // if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) { - // puppet := user.bridge.GetPuppetByJID(contact.Jid) - // puppet.Sync(user, contact) - // } - // } - // user.log.Infoln("Finished syncing puppet info from contacts") -} - -func (user *User) updateLastConnectionIfNecessary() { - // if user.LastConnection+60 < uint64(time.Now().Unix()) { - // user.UpdateLastConnection() - // } -} - func (user *User) HandleError(err error) { - if !errors.Is(err, whatsapp.ErrInvalidWsData) { - user.log.Errorfln("WhatsApp error: %v", err) - } - if closed, ok := err.(*whatsapp.ErrConnectionClosed); ok { - user.bridge.Metrics.TrackDisconnection(user.MXID) - if closed.Code == 1000 && user.cleanDisconnection { - user.bridge.Metrics.TrackConnectionState(user.GMID, false) - user.cleanDisconnection = false - user.log.Infoln("Clean disconnection by server") - return - } - go user.tryReconnect(fmt.Sprintf("Your WhatsApp connection was closed with websocket status code %d", closed.Code)) - } else if failed, ok := err.(*whatsapp.ErrConnectionFailed); ok { - user.bridge.Metrics.TrackDisconnection(user.MXID) - user.ConnectionErrors++ - go user.tryReconnect(fmt.Sprintf("Your WhatsApp connection failed: %v", failed.Err)) - } - // Otherwise unknown error, probably mostly harmless -} - -func (user *User) tryReconnect(msg string) { - // user.bridge.Metrics.TrackConnectionState(user.JID, false) - // if user.ConnectionErrors > user.bridge.Config.Bridge.MaxConnectionAttempts { - // user.sendMarkdownBridgeAlert("%s. Use the `reconnect` command to reconnect.", msg) - // return - // } - // if user.bridge.Config.Bridge.ReportConnectionRetry { - // user.sendBridgeNotice("%s. Reconnecting...", msg) - // // Don't want the same error to be repeated - // msg = "" - // } - // var tries uint - // var exponentialBackoff bool - // baseDelay := time.Duration(user.bridge.Config.Bridge.ConnectionRetryDelay) - // if baseDelay < 0 { - // exponentialBackoff = true - // baseDelay = -baseDelay + 1 - // } - // delay := baseDelay - // for user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts { - // err := user.Conn.Restore() - // if err == nil { - // user.ConnectionErrors = 0 - // if user.bridge.Config.Bridge.ReportConnectionRetry { - // user.sendBridgeNotice("Reconnected successfully") - // } - // user.PostLogin() - // return - // } else if err.Error() == "init responded with 400" { - // user.log.Infoln("Got init 400 error when trying to reconnect, resetting connection...") - // sess, err := user.Conn.Disconnect() - // if err != nil { - // user.log.Debugln("Error while disconnecting for connection reset:", err) - // } - // if len(sess.Wid) > 0 { - // user.SetSession(&sess) - // } - // } - // user.log.Errorln("Error while trying to reconnect after disconnection:", err) - // tries++ - // user.ConnectionErrors++ - // if user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts { - // if exponentialBackoff { - // delay = (1 << tries) + baseDelay - // } - // if user.bridge.Config.Bridge.ReportConnectionRetry { - // user.sendBridgeNotice("Reconnection attempt failed: %v. Retrying in %d seconds...", err, delay) - // } - // time.Sleep(delay * time.Second) - // } - // } - - // if user.bridge.Config.Bridge.ReportConnectionRetry { - // user.sendMarkdownBridgeAlert("%d reconnection attempts failed. Use the `reconnect` command to try to reconnect manually.", tries) - // } else { - // user.sendMarkdownBridgeAlert("\u26a0 %s. Additionally, %d reconnection attempts failed. Use the `reconnect` command to try to reconnect.", msg, tries) - // } } func (user *User) ShouldCallSynchronously() bool { @@ -850,7 +734,7 @@ func (user *User) ShouldCallSynchronously() bool { } func (user *User) HandleJSONParseError(err error) { - user.log.Errorln("WhatsApp JSON parse error:", err) + user.log.Errorln("GroupMe JSON parse error:", err) } func (user *User) PortalKey(gmid groupme.ID) database.PortalKey { @@ -893,34 +777,6 @@ func (user *User) handleMessageLoop() { } } -//func (user *User) HandleNewContact(contact whatsapp.Contact) { -// user.log.Debugfln("Contact message: %+v", contact) -// go func() { -// if strings.HasSuffix(contact.Jid, whatsappExt.OldUserSuffix) { -// contact.Jid = strings.Replace(contact.Jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1) -// } -// puppet := user.bridge.GetPuppetByJID(contact.Jid) -// puppet.UpdateName(user, contact) -// }() -//} - -//func (user *User) HandleBatteryMessage(battery whatsapp.BatteryMessage) { -// user.log.Debugfln("Battery message: %+v", battery) -// var notice string -// if !battery.Plugged && battery.Percentage < 15 && user.batteryWarningsSent < 1 { -// notice = fmt.Sprintf("Phone battery low (%d %% remaining)", battery.Percentage) -// user.batteryWarningsSent = 1 -// } else if !battery.Plugged && battery.Percentage < 5 && user.batteryWarningsSent < 2 { -// notice = fmt.Sprintf("Phone battery very low (%d %% remaining)", battery.Percentage) -// user.batteryWarningsSent = 2 -// } else if battery.Percentage > 15 || battery.Plugged { -// user.batteryWarningsSent = 0 -// } -// if notice != "" { -// go user.sendBridgeNotice("%s", notice) -// } -//} - func (user *User) HandleTextMessage(message groupme.Message) { id := database.ParsePortalKey(message.GroupID.String()) @@ -983,270 +839,15 @@ func (user *User) HandleNewNickname(groupID, userID groupme.ID, name string) { func (user *User) HandleNewAvatarInGroup(groupID, userID groupme.ID, url string) { puppet := user.bridge.GetPuppetByGMID(userID) - if puppet != nil { - puppet.UpdateAvatar(user, false) - } + puppet.UpdateAvatar(user, false) } func (user *User) HandleMembers(_ groupme.ID, _ []groupme.Member, _ bool) { user.HandleChatList() } -//func (user *User) HandleImageMessage(message whatsapp.ImageMessage) { -// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp} -//} -// -//func (user *User) HandleStickerMessage(message whatsapp.StickerMessage) { -// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp} -//} -// -//func (user *User) HandleVideoMessage(message whatsapp.VideoMessage) { -// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp} -//} -// -//func (user *User) HandleAudioMessage(message whatsapp.AudioMessage) { -// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp} -//} -// -//func (user *User) HandleDocumentMessage(message whatsapp.DocumentMessage) { -// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp} -//} -// -//func (user *User) HandleContactMessage(message whatsapp.ContactMessage) { -// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp} -//} -// -//func (user *User) HandleLocationMessage(message whatsapp.LocationMessage) { -// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp} -//} -// -//func (user *User) HandleMessageRevoke(message whatsappExt.MessageRevocation) { -// user.messageInput <- PortalMessage{message.RemoteJid, user, message, 0} -//} - type FakeMessage struct { Text string ID string Alert bool } - -//func (user *User) HandleCallInfo(info whatsappExt.CallInfo) { -// if info.Data != nil { -// return -// } -// data := FakeMessage{ -// ID: info.ID, -// } -// switch info.Type { -// case whatsappExt.CallOffer: -// if !user.bridge.Config.Bridge.CallNotices.Start { -// return -// } -// data.Text = "Incoming call" -// data.Alert = true -// case whatsappExt.CallOfferVideo: -// if !user.bridge.Config.Bridge.CallNotices.Start { -// return -// } -// data.Text = "Incoming video call" -// data.Alert = true -// case whatsappExt.CallTerminate: -// if !user.bridge.Config.Bridge.CallNotices.End { -// return -// } -// data.Text = "Call ended" -// data.ID += "E" -// default: -// return -// } -// portal := user.GetPortalByJID(info.From) -// if portal != nil { -// portal.messages <- PortalMessage{info.From, user, data, 0} -// } -//} - -//func (user *User) HandlePresence(info whatsappExt.Presence) { -// puppet := user.bridge.GetPuppetByJID(info.SenderJID) -// switch info.Status { -// case whatsapp.PresenceUnavailable: -// _ = puppet.DefaultIntent().SetPresence("offline") -// case whatsapp.PresenceAvailable: -// if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() { -// portal := user.bridge.GetPortalByMXID(puppet.typingIn) -// _, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0) -// puppet.typingIn = "" -// puppet.typingAt = 0 -// } else { -// _ = puppet.DefaultIntent().SetPresence("online") -// } -// case whatsapp.PresenceComposing: -// portal := user.GetPortalByJID(info.JID) -// if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() { -// if puppet.typingIn == portal.MXID { -// return -// } -// _, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0) -// } -// puppet.typingIn = portal.MXID -// puppet.typingAt = time.Now().Unix() -// _, _ = puppet.IntentFor(portal).UserTyping(portal.MXID, true, 15*1000) -// } -//} -// -//func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) { -// if (info.Command == whatsappExt.MsgInfoCommandAck || info.Command == whatsappExt.MsgInfoCommandAcks) && info.Acknowledgement == whatsappExt.AckMessageRead { -// portal := user.GetPortalByJID(info.ToJID) -// if len(portal.MXID) == 0 { -// return -// } -// -// go func() { -// intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal) -// for _, msgID := range info.IDs { -// msg := user.bridge.DB.Message.GetByJID(portal.Key, msgID) -// if msg == nil { -// continue -// } -// -// err := intent.MarkRead(portal.MXID, msg.MXID) -// if err != nil { -// user.log.Warnln("Failed to mark message %s as read by %s: %v", msg.MXID, info.SenderJID, err) -// } -// } -// }() -// } -//} - -//func (user *User) HandleReceivedMessage(received whatsapp.ReceivedMessage) { -// if received.Type == "read" { -// user.markSelfRead(received.Jid, received.Index) -// } else { -// user.log.Debugfln("Unknown received message type: %+v", received) -// } -//} -// -//func (user *User) HandleReadMessage(read whatsapp.ReadMessage) { -// user.log.Debugfln("Received chat read message: %+v", read) -// user.markSelfRead(read.Jid, "") -//} -// -//func (user *User) markSelfRead(jid, messageID string) { -// if strings.HasSuffix(jid, whatsappExt.OldUserSuffix) { -// jid = strings.Replace(jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1) -// } -// puppet := user.bridge.GetPuppetByJID(user.JID) -// if puppet == nil { -// return -// } -// intent := puppet.CustomIntent() -// if intent == nil { -// return -// } -// portal := user.GetPortalByJID(jid) -// if portal == nil { -// return -// } -// var message *database.Message -// if messageID == "" { -// message = user.bridge.DB.Message.GetLastInChat(portal.Key) -// if message == nil { -// return -// } -// user.log.Debugfln("User read chat %s/%s in WhatsApp mobile (last known event: %s/%s)", portal.Key.JID, portal.MXID, message.JID, message.MXID) -// } else { -// message = user.bridge.DB.Message.GetByJID(portal.Key, messageID) -// if message == nil { -// return -// } -// user.log.Debugfln("User read message %s/%s in %s/%s in WhatsApp mobile", message.JID, message.MXID, portal.Key.JID, portal.MXID) -// } -// err := intent.MarkRead(portal.MXID, message.MXID) -// if err != nil { -// user.log.Warnfln("Failed to bridge own read receipt in %s: %v", jid, err) -// } -//} -// -//func (user *User) HandleCommand(cmd whatsappExt.Command) { -// switch cmd.Type { -// case whatsappExt.CommandPicture: -// if strings.HasSuffix(cmd.JID, whatsappExt.NewUserSuffix) { -// puppet := user.bridge.GetPuppetByJID(cmd.JID) -// go puppet.UpdateAvatar(user, cmd.ProfilePicInfo) -// } else { -// portal := user.GetPortalByJID(cmd.JID) -// go portal.UpdateAvatar(user, cmd.ProfilePicInfo, true) -// } -// case whatsappExt.CommandDisconnect: -// user.cleanDisconnection = true -// if cmd.Kind == "replaced" { -// go user.sendMarkdownBridgeAlert("\u26a0 Your WhatsApp connection was closed by the server because you opened another WhatsApp Web client.\n\n" + -// "Use the `reconnect` command to disconnect the other client and resume bridging.") -// } else { -// user.log.Warnln("Unknown kind of disconnect:", string(cmd.Raw)) -// go user.sendMarkdownBridgeAlert("\u26a0 Your WhatsApp connection was closed by the server (reason code: %s).\n\n"+ -// "Use the `reconnect` command to reconnect.", cmd.Kind) -// } -// } -//} -// -//func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) { -// if cmd.Command != whatsappExt.ChatUpdateCommandAction { -// return -// } -// -// portal := user.GetPortalByJID(cmd.JID) -// if len(portal.MXID) == 0 { -// if cmd.Data.Action == whatsappExt.ChatActionIntroduce || cmd.Data.Action == whatsappExt.ChatActionCreate { -// go func() { -// err := portal.CreateMatrixRoom(user) -// if err != nil { -// user.log.Errorln("Failed to create portal room after receiving join event:", err) -// } -// }() -// } -// return -// } -// -// switch cmd.Data.Action { -// case whatsappExt.ChatActionNameChange: -// go portal.UpdateName(cmd.Data.NameChange.Name, cmd.Data.SenderJID, true) -// case whatsappExt.ChatActionAddTopic: -// go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, true) -// case whatsappExt.ChatActionRemoveTopic: -// go portal.UpdateTopic("", cmd.Data.SenderJID, true) -// case whatsappExt.ChatActionPromote: -// go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, true) -// case whatsappExt.ChatActionDemote: -// go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, false) -// case whatsappExt.ChatActionAnnounce: -// go portal.RestrictMessageSending(cmd.Data.Announce) -// case whatsappExt.ChatActionRestrict: -// go portal.RestrictMetadataChanges(cmd.Data.Restrict) -// case whatsappExt.ChatActionRemove: -// go portal.HandleWhatsAppKick(cmd.Data.SenderJID, cmd.Data.UserChange.JIDs) -// case whatsappExt.ChatActionAdd: -// go portal.HandleWhatsAppInvite(cmd.Data.SenderJID, cmd.Data.UserChange.JIDs) -// //case whatsappExt.ChatActionIntroduce: -// // if cmd.Data.SenderJID != "unknown" { -// // go portal.Sync(user, whatsapp.Contact{Jid: portal.Key.JID}) -// // } -// } -//} - -//func (user *User) HandleJsonMessage(message string) { -// var msg json.RawMessage -// err := json.Unmarshal([]byte(message), &msg) -// if err != nil { -// return -// } -// user.log.Debugln("JSON message:", message) -// user.updateLastConnectionIfNecessary() -//} - -//func (user *User) HandleRawMessage(message *waProto.WebMessageInfo) { -// user.updateLastConnectionIfNecessary() -//} -// -//func (user *User) HandleUnknownBinaryNode(node *waBinary.Node) { -// user.log.Debugfln("Unknown binary message: %+v", node) -//}