Store role list in database and convert role mentions into a readable format

This commit is contained in:
Tulir Asokan
2022-07-08 15:31:03 +03:00
parent 668a77e30d
commit 2611cbfa34
8 changed files with 246 additions and 9 deletions

View File

@@ -22,6 +22,7 @@ type Database struct {
Reaction *ReactionQuery Reaction *ReactionQuery
Emoji *EmojiQuery Emoji *EmojiQuery
Guild *GuildQuery Guild *GuildQuery
Role *RoleQuery
} }
func New(baseDB *dbutil.Database) *Database { func New(baseDB *dbutil.Database) *Database {
@@ -59,6 +60,10 @@ func New(baseDB *dbutil.Database) *Database {
db: db, db: db,
log: db.Log.Sub("Guild"), log: db.Log.Sub("Guild"),
} }
db.Role = &RoleQuery{
db: db,
log: db.Log.Sub("Role"),
}
return db return db
} }

114
database/role.go Normal file
View File

@@ -0,0 +1,114 @@
package database
import (
"database/sql"
"errors"
log "maunium.net/go/maulogger/v2"
"maunium.net/go/mautrix/util/dbutil"
"github.com/bwmarrin/discordgo"
)
type RoleQuery struct {
db *Database
log log.Logger
}
// language=postgresql
const (
roleSelect = "SELECT dc_guild_id, dcid, name, icon, mentionable, managed, hoist, color, position, permissions FROM role"
roleUpsert = `
INSERT INTO role (dc_guild_id, dcid, name, icon, mentionable, managed, hoist, color, position, permissions)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
ON CONFLICT (dc_guild_id, dcid) DO UPDATE
SET name=excluded.name, icon=excluded.icon, mentionable=excluded.mentionable, managed=excluded.managed,
hoist=excluded.hoist, color=excluded.color, position=excluded.position, permissions=excluded.permissions
`
roleDelete = "DELETE FROM role WHERE dc_guild_id=$1 AND dcid=$2"
)
func (rq *RoleQuery) New() *Role {
return &Role{
db: rq.db,
log: rq.log,
}
}
func (rq *RoleQuery) GetByID(guildID, dcid string) *Role {
query := roleSelect + " WHERE dc_guild_id=$1 AND dcid=$2"
return rq.New().Scan(rq.db.QueryRow(query, guildID, dcid))
}
func (rq *RoleQuery) DeleteByID(guildID, dcid string) {
_, err := rq.db.Exec("DELETE FROM role WHERE dc_guild_id=$1 AND dcid=$2", guildID, dcid)
if err != nil {
rq.log.Warnfln("Failed to delete %s/%s: %v", guildID, dcid, err)
panic(err)
}
}
func (rq *RoleQuery) GetAll(guildID string) []*Role {
rows, err := rq.db.Query(roleSelect+" WHERE dc_guild_id=$1", guildID)
if err != nil {
rq.log.Errorfln("Failed to query roles of %s: %v", guildID, err)
return nil
}
var roles []*Role
for rows.Next() {
role := rq.New().Scan(rows)
if role != nil {
roles = append(roles, role)
}
}
return roles
}
type Role struct {
db *Database
log log.Logger
GuildID string
discordgo.Role
}
func (r *Role) Scan(row dbutil.Scannable) *Role {
var icon sql.NullString
err := row.Scan(&r.GuildID, &r.ID, &r.Name, &icon, &r.Mentionable, &r.Managed, &r.Hoist, &r.Color, &r.Position, &r.Permissions)
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
r.log.Errorln("Database scan failed:", err)
panic(err)
}
return nil
}
r.Icon = icon.String
return r
}
func (r *Role) Upsert(txn dbutil.Execable) {
if txn == nil {
txn = r.db
}
_, err := txn.Exec(roleUpsert, r.GuildID, r.ID, r.Name, strPtr(r.Icon), r.Mentionable, r.Managed, r.Hoist, r.Color, r.Position, r.Permissions)
if err != nil {
r.log.Warnfln("Failed to insert %s/%s: %v", r.GuildID, r.ID, err)
panic(err)
}
}
func (r *Role) Delete(txn dbutil.Execable) {
if txn == nil {
txn = r.db
}
_, err := txn.Exec(roleDelete, r.GuildID, r.Icon)
if err != nil {
r.log.Warnfln("Failed to delete %s/%s: %v", r.GuildID, r.ID, err)
panic(err)
}
}

View File

@@ -1,4 +1,4 @@
-- v0 -> v5: Latest revision -- v0 -> v7: Latest revision
CREATE TABLE guild ( CREATE TABLE guild (
dcid TEXT PRIMARY KEY, dcid TEXT PRIMARY KEY,
@@ -74,7 +74,9 @@ CREATE TABLE "user" (
discord_token TEXT, discord_token TEXT,
management_room TEXT, management_room TEXT,
space_room TEXT, space_room TEXT,
dm_space_room TEXT dm_space_room TEXT,
read_state_version INTEGER NOT NULL DEFAULT 0
); );
CREATE TABLE user_portal ( CREATE TABLE user_portal (
@@ -126,3 +128,22 @@ CREATE TABLE emoji (
discord_name TEXT, discord_name TEXT,
matrix_url TEXT matrix_url TEXT
); );
CREATE TABLE role (
dc_guild_id TEXT,
dcid TEXT,
name TEXT NOT NULL,
icon TEXT,
mentionable BOOLEAN NOT NULL,
managed BOOLEAN NOT NULL,
hoist BOOLEAN NOT NULL,
color INTEGER NOT NULL,
position INTEGER NOT NULL,
permissions BIGINT NOT NULL,
PRIMARY KEY (dc_guild_id, dcid),
CONSTRAINT role_guild_fkey FOREIGN KEY (dc_guild_id) REFERENCES guild (dcid) ON DELETE CASCADE
);

View File

@@ -0,0 +1,19 @@
-- v7: Store role info
CREATE TABLE role (
dc_guild_id TEXT,
dcid TEXT,
name TEXT NOT NULL,
icon TEXT,
mentionable BOOLEAN NOT NULL,
managed BOOLEAN NOT NULL,
hoist BOOLEAN NOT NULL,
color INTEGER NOT NULL,
position INTEGER NOT NULL,
permissions BIGINT NOT NULL,
PRIMARY KEY (dc_guild_id, dcid),
CONSTRAINT role_guild_fkey FOREIGN KEY (dc_guild_id) REFERENCES guild (dcid) ON DELETE CASCADE
);

View File

@@ -263,7 +263,11 @@ func (r *discordTagHTMLRenderer) renderDiscordMention(w util.BufWriter, source [
_, _ = fmt.Fprintf(w, `<a href="https://matrix.to/#/%s">%s</a>`, puppet.MXID, puppet.Name) _, _ = fmt.Fprintf(w, `<a href="https://matrix.to/#/%s">%s</a>`, puppet.MXID, puppet.Name)
return return
case *astDiscordRoleMention: case *astDiscordRoleMention:
// TODO role := r.portal.bridge.DB.Role.GetByID(r.portal.GuildID, strconv.FormatInt(node.id, 10))
if role != nil {
_, _ = fmt.Fprintf(w, `<font color="#%06x"><strong>@%s</strong></font>`, role.Color, role.Name)
return
}
case *astDiscordChannelMention: case *astDiscordChannelMention:
portal := r.portal.bridge.GetExistingPortalByID(database.PortalKey{ portal := r.portal.bridge.GetExistingPortalByID(database.PortalKey{
ChannelID: strconv.FormatInt(node.id, 10), ChannelID: strconv.FormatInt(node.id, 10),

4
go.mod
View File

@@ -11,7 +11,7 @@ require (
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/yuin/goldmark v1.4.12 github.com/yuin/goldmark v1.4.12
maunium.net/go/maulogger/v2 v2.3.2 maunium.net/go/maulogger/v2 v2.3.2
maunium.net/go/mautrix v0.11.1-0.20220705131441-28320db1cc9c maunium.net/go/mautrix v0.11.1-0.20220708121944-cda2329dd1df
) )
require ( require (
@@ -26,4 +26,4 @@ require (
maunium.net/go/mauflag v1.0.0 // indirect maunium.net/go/mauflag v1.0.0 // indirect
) )
replace github.com/bwmarrin/discordgo => gitlab.com/beeper/discordgo v0.23.3-0.20220708095310-09da7ef6f6de replace github.com/bwmarrin/discordgo => gitlab.com/beeper/discordgo v0.23.3-0.20220708122002-c27922e0ba67

8
go.sum
View File

@@ -30,8 +30,8 @@ github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0= github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0=
github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/beeper/discordgo v0.23.3-0.20220708095310-09da7ef6f6de h1:XSKsxfGXfUf7KTyzM1NmzYkqetqiLivUULhXr7alZX4= gitlab.com/beeper/discordgo v0.23.3-0.20220708122002-c27922e0ba67 h1:FSxw+90bXpsAJZfH5oz49LL33TAk4L/0U7eJW+He4ys=
gitlab.com/beeper/discordgo v0.23.3-0.20220708095310-09da7ef6f6de/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= gitlab.com/beeper/discordgo v0.23.3-0.20220708122002-c27922e0ba67/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -59,5 +59,5 @@ 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/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 h1:1XmIYmMd3PoQfp9J+PaHhpt80zpfmMqaShzUTC7FwY0=
maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.11.1-0.20220705131441-28320db1cc9c h1:/vVqeoH1CLFEbBpmd6nczEmCxJ9dxImWX6MtkaJjd+8= maunium.net/go/mautrix v0.11.1-0.20220708121944-cda2329dd1df h1:MvSfTply7Vn+02RukSqW02REGy2qYzDWm7tH+0i7Akc=
maunium.net/go/mautrix v0.11.1-0.20220705131441-28320db1cc9c/go.mod h1:Lj4pBam5P0zIvieIFHnGsuaj+xfFtI3y/sC8yGlyna8= maunium.net/go/mautrix v0.11.1-0.20220708121944-cda2329dd1df/go.mod h1:Lj4pBam5P0zIvieIFHnGsuaj+xfFtI3y/sC8yGlyna8=

74
user.go
View File

@@ -459,6 +459,9 @@ func (user *User) Connect() error {
user.Session.AddHandler(user.guildCreateHandler) user.Session.AddHandler(user.guildCreateHandler)
user.Session.AddHandler(user.guildDeleteHandler) user.Session.AddHandler(user.guildDeleteHandler)
user.Session.AddHandler(user.guildUpdateHandler) user.Session.AddHandler(user.guildUpdateHandler)
user.Session.AddHandler(user.guildRoleCreateHandler)
user.Session.AddHandler(user.guildRoleUpdateHandler)
user.Session.AddHandler(user.guildRoleDeleteHandler)
user.Session.AddHandler(user.channelCreateHandler) user.Session.AddHandler(user.channelCreateHandler)
user.Session.AddHandler(user.channelDeleteHandler) user.Session.AddHandler(user.channelDeleteHandler)
@@ -586,6 +589,74 @@ func (user *User) addGuildToSpace(guild *Guild) bool {
return false return false
} }
func (user *User) discordRoleToDB(guildID string, role *discordgo.Role, dbRole *database.Role) (*database.Role, bool) {
var changed bool
if dbRole == nil {
dbRole = user.bridge.DB.Role.New()
dbRole.ID = role.ID
dbRole.GuildID = guildID
changed = true
} else {
changed = dbRole.Name != role.Name ||
dbRole.Icon != role.Icon ||
dbRole.Mentionable != role.Mentionable ||
dbRole.Managed != role.Managed ||
dbRole.Hoist != role.Hoist ||
dbRole.Color != role.Color ||
dbRole.Position != role.Position ||
dbRole.Permissions != role.Permissions
}
dbRole.Role = *role
return dbRole, changed
}
func (user *User) handleGuildRoles(guildID string, newRoles []*discordgo.Role) {
existingRoles := user.bridge.DB.Role.GetAll(guildID)
existingRoleMap := make(map[string]*database.Role, len(existingRoles))
for _, role := range existingRoles {
existingRoleMap[role.ID] = role
}
txn, err := user.bridge.DB.Begin()
if err != nil {
user.log.Errorln("Failed to start transaction for guild role sync:", err)
panic(err)
return
}
for _, role := range newRoles {
dbRole, changed := user.discordRoleToDB(guildID, role, existingRoleMap[role.ID])
delete(existingRoleMap, role.ID)
if changed {
dbRole.Upsert(txn)
}
}
for _, removeRole := range existingRoleMap {
removeRole.Delete(txn)
}
err = txn.Commit()
if err != nil {
user.log.Errorln("Failed to commit guild role sync:", err)
rollbackErr := txn.Rollback()
if rollbackErr != nil {
user.log.Errorln("Failed to rollback errored guild role sync:", rollbackErr)
}
panic(err)
}
}
func (user *User) guildRoleCreateHandler(_ *discordgo.Session, r *discordgo.GuildRoleCreate) {
dbRole, _ := user.discordRoleToDB(r.GuildID, r.Role, nil)
dbRole.Upsert(nil)
}
func (user *User) guildRoleUpdateHandler(_ *discordgo.Session, r *discordgo.GuildRoleUpdate) {
dbRole, _ := user.discordRoleToDB(r.GuildID, r.Role, nil)
dbRole.Upsert(nil)
}
func (user *User) guildRoleDeleteHandler(_ *discordgo.Session, r *discordgo.GuildRoleDelete) {
user.bridge.DB.Role.DeleteByID(r.GuildID, r.RoleID)
}
func (user *User) handleGuild(meta *discordgo.Guild, timestamp time.Time, isInSpace bool) { func (user *User) handleGuild(meta *discordgo.Guild, timestamp time.Time, isInSpace bool) {
guild := user.bridge.GetGuildByID(meta.ID, true) guild := user.bridge.GetGuildByID(meta.ID, true)
guild.UpdateInfo(user, meta) guild.UpdateInfo(user, meta)
@@ -602,6 +673,9 @@ func (user *User) handleGuild(meta *discordgo.Guild, timestamp time.Time, isInSp
} }
} }
} }
if len(meta.Roles) > 0 {
user.handleGuildRoles(meta.ID, meta.Roles)
}
if !isInSpace { if !isInSpace {
isInSpace = user.addGuildToSpace(guild) isInSpace = user.addGuildToSpace(guild)
} }