diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yml similarity index 100% rename from .github/workflows/deploy.yaml rename to .github/workflows/deploy.yml diff --git a/.gitignore b/.gitignore index 37148cf..31f546f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ .profile groupme + +*.yaml +!.pre-commit-config.yaml +!example-config.yaml diff --git a/config/upgrade.go b/config/upgrade.go index c15c58e..fcb3a09 100644 --- a/config/upgrade.go +++ b/config/upgrade.go @@ -57,8 +57,6 @@ func DoUpgrade(helper *up.Helper) { helper.Copy(up.Bool, "bridge", "enable_status_broadcast") helper.Copy(up.Bool, "bridge", "disable_status_broadcast_send") helper.Copy(up.Bool, "bridge", "mute_status_broadcast") - helper.Copy(up.Str|up.Null, "bridge", "status_broadcast_tag") - helper.Copy(up.Bool, "bridge", "whatsapp_thumbnail") helper.Copy(up.Bool, "bridge", "allow_user_invite") helper.Copy(up.Str, "bridge", "command_prefix") helper.Copy(up.Bool, "bridge", "federate_rooms") @@ -67,7 +65,6 @@ func DoUpgrade(helper *up.Helper) { helper.Copy(up.Bool, "bridge", "crash_on_stream_replaced") helper.Copy(up.Bool, "bridge", "url_previews") helper.Copy(up.Bool, "bridge", "caption_in_message") - helper.Copy(up.Bool, "bridge", "send_whatsapp_edits") helper.Copy(up.Str|up.Null, "bridge", "message_handling_timeout", "error_after") helper.Copy(up.Str|up.Null, "bridge", "message_handling_timeout", "deadline") @@ -112,9 +109,6 @@ func DoUpgrade(helper *up.Helper) { helper.Copy(up.Str, "bridge", "provisioning", "shared_secret") } helper.Copy(up.Map, "bridge", "permissions") - helper.Copy(up.Bool, "bridge", "relay", "enabled") - helper.Copy(up.Bool, "bridge", "relay", "admin_only") - helper.Copy(up.Map, "bridge", "relay", "message_formats") } var SpacedBlocks = [][]string{ @@ -133,6 +127,5 @@ var SpacedBlocks = [][]string{ {"bridge", "encryption"}, {"bridge", "provisioning"}, {"bridge", "permissions"}, - {"bridge", "relay"}, {"logging"}, } diff --git a/database/upgrades/00-latest-revision.sql b/database/upgrades/00-latest-revision.sql index fd98175..8cfd6df 100644 --- a/database/upgrades/00-latest-revision.sql +++ b/database/upgrades/00-latest-revision.sql @@ -1,19 +1,19 @@ -- v0 -> v1: Latest revision CREATE TABLE "user" ( - mxid TEXT PRIMARY KEY, - gmid TEXT UNIQUE, + mxid TEXT PRIMARY KEY, + gmid TEXT UNIQUE, auth_token TEXT, - management_room TEXT, - space_room TEXT, + management_room TEXT, + space_room TEXT ); CREATE TABLE portal ( gmid TEXT, - receiver TEXT, - mxid TEXT UNIQUE, + receiver TEXT, + mxid TEXT UNIQUE, name TEXT NOT NULL, name_set BOOLEAN NOT NULL DEFAULT false, @@ -22,38 +22,38 @@ CREATE TABLE portal ( avatar TEXT NOT NULL, avatar_url TEXT, avatar_set BOOLEAN NOT NULL DEFAULT false, - encrypted BOOLEAN NOT NULL DEFAULT false, + encrypted BOOLEAN NOT NULL DEFAULT false, - PRIMARY KEY (gmid, receiver) + PRIMARY KEY (gmid, receiver) ); CREATE TABLE puppet ( - gmid TEXT PRIMARY KEY, - displayname TEXT, + gmid TEXT PRIMARY KEY, + displayname TEXT, name_set BOOLEAN NOT NULL DEFAULT false, - avatar TEXT, - avatar_url TEXT, + avatar TEXT, + avatar_url TEXT, avatar_set BOOLEAN NOT NULL DEFAULT false, - custom_mxid TEXT, - access_token TEXT, - next_batch TEXT, + custom_mxid TEXT, + access_token TEXT, + next_batch TEXT, - enable_presence BOOLEAN NOT NULL DEFAULT true, - enable_receipts BOOLEAN NOT NULL DEFAULT true + enable_presence BOOLEAN NOT NULL DEFAULT true, + enable_receipts BOOLEAN NOT NULL DEFAULT true ); CREATE TABLE message ( - chat_gmid TEXT, - chat_receiver TEXT, - gmid TEXT, - mxid TEXT UNIQUE, - sender TEXT, - timestamp BIGINT, + chat_gmid TEXT, + chat_receiver TEXT, + gmid TEXT, + mxid TEXT UNIQUE, + sender TEXT, + timestamp BIGINT, sent BOOLEAN, - PRIMARY KEY (chat_gmid, chat_receiver, gmid), - FOREIGN KEY (chat_gmid, chat_receiver) REFERENCES portal(gmid, receiver) ON DELETE CASCADE + PRIMARY KEY (chat_gmid, chat_receiver, gmid), + FOREIGN KEY (chat_gmid, chat_receiver) REFERENCES portal(gmid, receiver) ON DELETE CASCADE ); CREATE TABLE reaction ( @@ -68,16 +68,16 @@ CREATE TABLE reaction ( PRIMARY KEY (chat_gmid, chat_receiver, target_gmid, sender), FOREIGN KEY (chat_gmid, chat_receiver, target_gmid) REFERENCES message(chat_gmid, chat_receiver, gmid) ON DELETE CASCADE ON UPDATE CASCADE -) +); CREATE TABLE user_portal ( - user_mxid TEXT, - portal_gmid TEXT, - portal_receiver TEXT, - in_space BOOLEAN NOT NULL DEFAULT false, + user_mxid TEXT, + portal_gmid TEXT, + portal_receiver TEXT, + in_space BOOLEAN NOT NULL DEFAULT false, - PRIMARY KEY (user_mxid, portal_gmid, portal_receiver), + PRIMARY KEY (user_mxid, portal_gmid, portal_receiver), - FOREIGN KEY (user_mxid) REFERENCES "user"(mxid) ON UPDATE CASCADE ON DELETE CASCADE, - FOREIGN KEY (portal_gmid, portal_receiver) REFERENCES portal(gmid, receiver) ON UPDATE CASCADE ON DELETE CASCADE + FOREIGN KEY (user_mxid) REFERENCES "user"(mxid) ON UPDATE CASCADE ON DELETE CASCADE, + FOREIGN KEY (portal_gmid, portal_receiver) REFERENCES portal(gmid, receiver) ON UPDATE CASCADE ON DELETE CASCADE ); diff --git a/database/upgrades/upgrades.go b/database/upgrades/upgrades.go index 7165cdb..25ffe6f 100644 --- a/database/upgrades/upgrades.go +++ b/database/upgrades/upgrades.go @@ -18,7 +18,6 @@ package upgrades import ( "embed" - "errors" "maunium.net/go/mautrix/util/dbutil" ) @@ -29,8 +28,5 @@ var Table dbutil.UpgradeTable var rawUpgrades embed.FS func init() { - Table.Register(-1, 35, "Unsupported version", func(tx dbutil.Transaction, database *dbutil.Database) error { - return errors.New("please upgrade to mautrix-whatsapp v0.4.0 before upgrading to a newer version") - }) Table.RegisterFS(rawUpgrades) } diff --git a/example-config.yaml b/example-config.yaml index 6b6c102..9618aee 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -5,6 +5,18 @@ homeserver: # The domain of the homeserver (for MXIDs, etc). domain: example.com + # What software is the homeserver running? + # 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. + # The bridge will use the appservice as_token to authorize requests. + status_endpoint: null + # Endpoint for reporting per-message status. + message_send_checkpoint_endpoint: null + # Does the homeserver support https://github.com/matrix-org/matrix-spec-proposals/pull/2246? + async_media: false + # Application service host/registration related details. # Changing these values requires regeneration of the registration. appservice: @@ -13,27 +25,24 @@ appservice: # The hostname and port where this appservice should listen. hostname: 0.0.0.0 - port: 29318 + port: 29328 # Database config. database: - # The database type. only "postgres" is supported for now. sqlite is TODO + # The database type. "sqlite3" and "postgres" are supported. type: postgres # The database URI. # SQLite: File name is enough. 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 # Maximum number of connections. Mostly relevant for Postgres. max_open_conns: 20 max_idle_conns: 2 - - # NOT TESTED YET - # Settings for provisioning API - provisioning: - # Prefix for the provisioning API paths. - prefix: /_matrix/provision/v1 - # Shared secret for authentication. If set to "disable", the provisioning API will be disabled. - shared_secret: disable + # Maximum connection idle time and lifetime before they're closed. Disabled if null. + # Parsed with https://pkg.go.dev/time#ParseDuration + max_conn_idle_time: null + max_conn_lifetime: null # The unique ID of this appservice. id: groupme @@ -50,6 +59,9 @@ appservice: as_token: "This value is generated when generating the registration" hs_token: "This value is generated when generating the registration" +# Segment API key to track some events, like provisioning API login and encryption errors. +segment_key: null + # Metrics not yet tested! metrics: # Whether or not to enable prometheus metrics @@ -171,31 +183,75 @@ bridge: # The prefix for commands. Only required in non-management rooms. command_prefix: "!gm" - # NOT TESTED in GroupMe - # End-to-bridge encryption support options. This requires login_shared_secret to be configured - # in order to get a device for the bridge bot. + # Messages sent upon joining a management room. + # Markdown is supported. The defaults are listed below. + management_room_text: + # Sent when joining a room. + welcome: "Hello, I'm a GroupMe bridge bot." + # Sent when joining a management room and the user is already logged in. + welcome_connected: "Use `help` for help." + # Sent when joining a management room and the user is not logged in. + welcome_unconnected: "Use `help` for help or `login` to log in." + # Optional extra text sent when joining a management room. + additional_help: "" + + # End-to-bridge encryption support options. # - # Additionally, https://github.com/matrix-org/synapse/pull/5758 is required if using a normal - # application service. + # See https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html for more info. encryption: # Allow encryption, work in group chat rooms with e2ee enabled allow: false # Default to encryption, force-enable encryption in all portals the bridge creates # This will cause the bridge bot to be in private chats for the encryption to work properly. - # It is recommended to also set private_chat_portal_meta to true when using this. default: false - # Options for automatic key sharing. - key_sharing: - # Enable key sharing? If enabled, key requests for rooms where users are in will be fulfilled. - # You must use a client that supports requesting keys from other users to use this feature. - allow: false - # Require the requesting device to have a valid cross-signing signature? - # This doesn't require that the bridge has verified the device, only that the user has verified it. - # Not yet implemented. - require_cross_signing: false - # Require devices to be verified by the bridge? - # Verification by the bridge is not yet implemented. - require_verification: true + # Whether to use MSC2409/MSC3202 instead of /sync long polling for receiving encryption-related data. + appservice: false + # Require encryption, drop any unencrypted messages. + require: false + # Enable key sharing? If enabled, key requests for rooms where users are in will be fulfilled. + # You must use a client that supports requesting keys from other users to use this feature. + allow_key_sharing: false + # What level of device verification should be required from users? + # + # Valid levels: + # unverified - Send keys to all device in the room. + # cross-signed-untrusted - Require valid cross-signing, but trust all cross-signing keys. + # cross-signed-tofu - Require valid cross-signing, trust cross-signing keys on first use (and reject changes). + # cross-signed-verified - Require valid cross-signing, plus a valid user signature from the bridge bot. + # Note that creating user signatures from the bridge bot is not currently possible. + # 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. + receive: unverified + # Minimum level that the bridge should accept for incoming Matrix messages. + send: unverified + # Minimum level that the bridge should require for accepting key requests. + share: cross-signed-tofu + # Options for Megolm room key rotation. These options allow you to + # configure the m.room.encryption event content. See: + # https://spec.matrix.org/v1.3/client-server-api/#mroomencryption for + # more information about that event. + rotation: + # Enable custom Megolm room key rotation settings. Note that these + # settings will only apply to rooms created after this option is + # set. + enable_custom: false + # The maximum number of milliseconds a session should be used + # before changing it. The Matrix spec recommends 604800000 (a week) + # as the default. + milliseconds: 604800000 + # The maximum number of messages that should be sent with a given a + # session before changing it. The Matrix spec recommends 100 as the + # default. + messages: 100 + + # Settings for provisioning API + provisioning: + # Prefix for the provisioning API paths. + prefix: /_matrix/provision/v1 + # Shared secret for authentication. If set to "disable", the provisioning API will be disabled. + shared_secret: disable # Permissions for using the bridge. # Permitted values: @@ -211,27 +267,6 @@ bridge: "example.com": user "@admin:example.com": admin - # GroupMe not tested - relaybot: - # Whether or not relaybot support is enabled. - enabled: false - # The management room for the bot. This is where all status notifications are posted and - # in this room, you can use `!wa ` instead of `!wa relaybot `. Omitting - # the command prefix completely like in user management rooms is not possible. - management: "!foo:example.com" - # List of users to invite to all created rooms that include the relaybot. - invites: [] - # The formats to use when sending messages to WhatsApp via the relaybot. - message_formats: - m.text: "{{ .Sender.Displayname }}: {{ .Message }}" - m.notice: "{{ .Sender.Displayname }}: {{ .Message }}" - m.emote: "* {{ .Sender.Displayname }} {{ .Message }}" - m.file: "{{ .Sender.Displayname }} sent a file" - m.image: "{{ .Sender.Displayname }} sent an image" - m.audio: "{{ .Sender.Displayname }} sent an audio file" - m.video: "{{ .Sender.Displayname }} sent a video" - m.location: "{{ .Sender.Displayname }} sent a location" - # Logging config. logging: # The directory for log files. Will be created if not found. diff --git a/main.go b/main.go index 6c9bb7c..0d8971e 100644 --- a/main.go +++ b/main.go @@ -52,8 +52,7 @@ type GMBridge struct { Metrics *MetricsHandler usersByMXID map[id.UserID]*User - usersByUsername map[string]*User - usersByGMID map[groupme.ID]*User // TODO REMOVE? + usersByGMID map[groupme.ID]*User usersLock sync.Mutex spaceRooms map[id.RoomID]*User spaceRoomsLock sync.Mutex @@ -155,7 +154,7 @@ func (br *GMBridge) StartUsers() { func (br *GMBridge) Stop() { br.Metrics.Stop() // TODO anything needed to disconnect the users? - for _, user := range br.usersByUsername { + for _, user := range br.usersByGMID { if user.Client == nil { continue } @@ -178,7 +177,7 @@ func (br *GMBridge) GetConfigPtr() interface{} { func main() { br := &GMBridge{ usersByMXID: make(map[id.UserID]*User), - usersByUsername: make(map[string]*User), + usersByGMID: make(map[groupme.ID]*User), spaceRooms: make(map[id.RoomID]*User), managementRooms: make(map[id.RoomID]*User), portalsByMXID: make(map[id.RoomID]*Portal),