From d7292a0706f787a84537ee5a281c3652543efbf4 Mon Sep 17 00:00:00 2001 From: "Skip R." Date: Wed, 19 Nov 2025 14:33:26 -0800 Subject: [PATCH] bump discordgo and add support for heartbeat sessions (#203) --- database/json.go | 20 +++++++++++++ database/upgrades/00-latest-revision.sql | 5 ++-- .../upgrades/24-user-heartbeat-session.sql | 2 ++ database/user.go | 30 ++++++++++--------- go.mod | 3 +- go.sum | 6 ++-- user.go | 16 +++++++++- 7 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 database/json.go create mode 100644 database/upgrades/24-user-heartbeat-session.sql diff --git a/database/json.go b/database/json.go new file mode 100644 index 0000000..566a6c4 --- /dev/null +++ b/database/json.go @@ -0,0 +1,20 @@ +package database + +import ( + "go.mau.fi/util/dbutil" +) + +// Backported from mautrix/go-util@e5cb5e96d15cb87ffe6e5970c2f90ee47980e715. + +// JSONPtr is a convenience function for wrapping a pointer to a value in the JSON utility, but removing typed nils +// (i.e. preventing nils from turning into the string "null" in the database). +func JSONPtr[T any](val *T) dbutil.JSON { + return dbutil.JSON{Data: UntypedNil(val)} +} + +func UntypedNil[T any](val *T) any { + if val == nil { + return nil + } + return val +} diff --git a/database/upgrades/00-latest-revision.sql b/database/upgrades/00-latest-revision.sql index 46fbb73..d794530 100644 --- a/database/upgrades/00-latest-revision.sql +++ b/database/upgrades/00-latest-revision.sql @@ -1,4 +1,4 @@ --- v0 -> v23 (compatible with v19+): Latest revision +-- v0 -> v24 (compatible with v19+): Latest revision CREATE TABLE guild ( dcid TEXT PRIMARY KEY, @@ -92,7 +92,8 @@ CREATE TABLE "user" ( space_room TEXT, dm_space_room TEXT, - read_state_version INTEGER NOT NULL DEFAULT 0 + read_state_version INTEGER NOT NULL DEFAULT 0, + heartbeat_session jsonb ); CREATE TABLE user_portal ( diff --git a/database/upgrades/24-user-heartbeat-session.sql b/database/upgrades/24-user-heartbeat-session.sql new file mode 100644 index 0000000..ccb44f9 --- /dev/null +++ b/database/upgrades/24-user-heartbeat-session.sql @@ -0,0 +1,2 @@ +-- v24 (compatible with v19+): Add persisted heartbeat sessions +ALTER TABLE "user" ADD COLUMN heartbeat_session jsonb; diff --git a/database/user.go b/database/user.go index 763625d..eff661b 100644 --- a/database/user.go +++ b/database/user.go @@ -3,6 +3,7 @@ package database import ( "database/sql" + "github.com/bwmarrin/discordgo" "go.mau.fi/util/dbutil" log "maunium.net/go/maulogger/v2" "maunium.net/go/mautrix/id" @@ -21,18 +22,18 @@ func (uq *UserQuery) New() *User { } func (uq *UserQuery) GetByMXID(userID id.UserID) *User { - query := `SELECT mxid, dcid, discord_token, management_room, space_room, dm_space_room, read_state_version FROM "user" WHERE mxid=$1` + query := `SELECT mxid, dcid, discord_token, management_room, space_room, dm_space_room, read_state_version, heartbeat_session FROM "user" WHERE mxid=$1` return uq.New().Scan(uq.db.QueryRow(query, userID)) } func (uq *UserQuery) GetByID(id string) *User { - query := `SELECT mxid, dcid, discord_token, management_room, space_room, dm_space_room, read_state_version FROM "user" WHERE dcid=$1` + query := `SELECT mxid, dcid, discord_token, management_room, space_room, dm_space_room, read_state_version, heartbeat_session FROM "user" WHERE dcid=$1` return uq.New().Scan(uq.db.QueryRow(query, id)) } func (uq *UserQuery) GetAllWithToken() []*User { query := ` - SELECT mxid, dcid, discord_token, management_room, space_room, dm_space_room, read_state_version + SELECT mxid, dcid, discord_token, management_room, space_room, dm_space_room, read_state_version, heartbeat_session FROM "user" WHERE discord_token IS NOT NULL ` rows, err := uq.db.Query(query) @@ -54,19 +55,20 @@ type User struct { db *Database log log.Logger - MXID id.UserID - DiscordID string - DiscordToken string - ManagementRoom id.RoomID - SpaceRoom id.RoomID - DMSpaceRoom id.RoomID + MXID id.UserID + DiscordID string + DiscordToken string + ManagementRoom id.RoomID + SpaceRoom id.RoomID + DMSpaceRoom id.RoomID + HeartbeatSession *discordgo.HeartbeatSession ReadStateVersion int } func (u *User) Scan(row dbutil.Scannable) *User { var discordID, managementRoom, spaceRoom, dmSpaceRoom, discordToken sql.NullString - err := row.Scan(&u.MXID, &discordID, &discordToken, &managementRoom, &spaceRoom, &dmSpaceRoom, &u.ReadStateVersion) + err := row.Scan(&u.MXID, &discordID, &discordToken, &managementRoom, &spaceRoom, &dmSpaceRoom, &u.ReadStateVersion, dbutil.JSON{Data: &u.HeartbeatSession}) if err != nil { if err != sql.ErrNoRows { u.log.Errorln("Database scan failed:", err) @@ -83,8 +85,8 @@ func (u *User) Scan(row dbutil.Scannable) *User { } func (u *User) Insert() { - query := `INSERT INTO "user" (mxid, dcid, discord_token, management_room, space_room, dm_space_room, read_state_version) VALUES ($1, $2, $3, $4, $5, $6, $7)` - _, err := u.db.Exec(query, u.MXID, strPtr(u.DiscordID), strPtr(u.DiscordToken), strPtr(string(u.ManagementRoom)), strPtr(string(u.SpaceRoom)), strPtr(string(u.DMSpaceRoom)), u.ReadStateVersion) + query := `INSERT INTO "user" (mxid, dcid, discord_token, management_room, space_room, dm_space_room, read_state_version, heartbeat_session) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` + _, err := u.db.Exec(query, u.MXID, strPtr(u.DiscordID), strPtr(u.DiscordToken), strPtr(string(u.ManagementRoom)), strPtr(string(u.SpaceRoom)), strPtr(string(u.DMSpaceRoom)), u.ReadStateVersion, JSONPtr(u.HeartbeatSession)) if err != nil { u.log.Warnfln("Failed to insert %s: %v", u.MXID, err) panic(err) @@ -92,8 +94,8 @@ func (u *User) Insert() { } func (u *User) Update() { - query := `UPDATE "user" SET dcid=$1, discord_token=$2, management_room=$3, space_room=$4, dm_space_room=$5, read_state_version=$6 WHERE mxid=$7` - _, err := u.db.Exec(query, strPtr(u.DiscordID), strPtr(u.DiscordToken), strPtr(string(u.ManagementRoom)), strPtr(string(u.SpaceRoom)), strPtr(string(u.DMSpaceRoom)), u.ReadStateVersion, u.MXID) + query := `UPDATE "user" SET dcid=$1, discord_token=$2, management_room=$3, space_room=$4, dm_space_room=$5, read_state_version=$6, heartbeat_session=$7 WHERE mxid=$8` + _, err := u.db.Exec(query, strPtr(u.DiscordID), strPtr(u.DiscordToken), strPtr(string(u.ManagementRoom)), strPtr(string(u.SpaceRoom)), strPtr(string(u.DMSpaceRoom)), u.ReadStateVersion, JSONPtr(u.HeartbeatSession), u.MXID) if err != nil { u.log.Warnfln("Failed to update %q: %v", u.MXID, err) panic(err) diff --git a/go.mod b/go.mod index 5cb664e..255ce83 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( require ( github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -42,4 +43,4 @@ require ( maunium.net/go/mauflag v1.0.0 // indirect ) -replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20251029151721-c53d6229e2fd +replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20251117165013-20c39e9899ec diff --git a/go.sum b/go.sum index a16ce05..aeac7db 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/beeper/discordgo v0.0.0-20251029151721-c53d6229e2fd h1:RXB0a8lTNN9vB838lZXm11inXwvILpOzXi3j978P8RE= -github.com/beeper/discordgo v0.0.0-20251029151721-c53d6229e2fd/go.mod h1:59+AOzzjmL6onAh62nuLXmn7dJCaC/owDLWbGtjTcFA= +github.com/beeper/discordgo v0.0.0-20251117165013-20c39e9899ec h1:5yvEHHd6f4GharWjdBVCjdvL0C09h9wZlayBaI75q1I= +github.com/beeper/discordgo v0.0.0-20251117165013-20c39e9899ec/go.mod h1:lioivnibvB8j1KcF5TVpLdRLKCKHtcl8A03GpxRCre4= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -11,6 +11,8 @@ github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFA github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= diff --git a/user.go b/user.go index 2dd727a..b5ff572 100644 --- a/user.go +++ b/user.go @@ -550,6 +550,17 @@ func (user *User) Connect() error { if err != nil { return err } + + if user.HeartbeatSession == nil || user.HeartbeatSession.IsExpired() { + user.log.Debug().Msg("Creating new heartbeat session") + sess := discordgo.NewHeartbeatSession() + user.HeartbeatSession = &sess + } + user.HeartbeatSession.BumpLastUsed() + user.Update() + // make discordgo use our session instead of the one it creates automatically + session.HeartbeatSession = *user.HeartbeatSession + if user.bridge.Config.Bridge.Proxy != "" { u, _ := url.Parse(user.bridge.Config.Bridge.Proxy) tlsConf := &tls.Config{ @@ -569,7 +580,10 @@ func (user *User) Connect() error { } else { session.LogLevel = discordgo.LogInformational } - userDiscordLog := user.log.With().Str("component", "discordgo").Logger() + userDiscordLog := user.log.With(). + Str("component", "discordgo"). + Str("heartbeat_session", session.HeartbeatSession.ID.String()). + Logger() session.Logger = func(msgL, caller int, format string, a ...interface{}) { userDiscordLog.WithLevel(discordToZeroLevel(msgL)).Caller(caller+1).Msgf(strings.TrimSpace(format), a...) // zerolog-allow-msgf }