Add initial support for relay mode with webhooks
This commit is contained in:
86
commands.go
86
commands.go
@@ -55,6 +55,7 @@ func (br *DiscordBridge) RegisterCommands() {
|
|||||||
cmdLogout,
|
cmdLogout,
|
||||||
cmdReconnect,
|
cmdReconnect,
|
||||||
cmdDisconnect,
|
cmdDisconnect,
|
||||||
|
cmdSetRelay,
|
||||||
cmdGuilds,
|
cmdGuilds,
|
||||||
cmdRejoinSpace,
|
cmdRejoinSpace,
|
||||||
cmdDeleteAllPortals,
|
cmdDeleteAllPortals,
|
||||||
@@ -352,6 +353,91 @@ func fnRejoinSpace(ce *WrappedCommandEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cmdSetRelay = &commands.FullHandler{
|
||||||
|
Func: wrapCommand(fnSetRelay),
|
||||||
|
Name: "set-relay",
|
||||||
|
Help: commands.HelpMeta{
|
||||||
|
Section: commands.HelpSectionUnclassified,
|
||||||
|
Description: "Create or set a relay webhook for a portal",
|
||||||
|
Args: "[room ID] <--url URL> OR <--create [name]>",
|
||||||
|
},
|
||||||
|
RequiresLogin: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const webhookURLFormat = "https://discord.com/api/webhooks/%d/%s"
|
||||||
|
|
||||||
|
const selectRelayHelp = "Usage: `$cmdprefix [room ID] <--url URL> OR <--create [name]>`"
|
||||||
|
|
||||||
|
func fnSetRelay(ce *WrappedCommandEvent) {
|
||||||
|
portal := ce.Portal
|
||||||
|
if len(ce.Args) > 0 && strings.HasPrefix(ce.Args[0], "!") {
|
||||||
|
portal = ce.Bridge.GetPortalByMXID(id.RoomID(ce.Args[0]))
|
||||||
|
if portal == nil {
|
||||||
|
ce.Reply("Portal with room ID %s not found", ce.Args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ce.Args = ce.Args[1:]
|
||||||
|
} else if portal == nil {
|
||||||
|
ce.Reply("You must either run the command in a portal, or specify an internal room ID as the first parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ce.Args) == 0 {
|
||||||
|
ce.Reply(selectRelayHelp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log := ce.ZLog.With().Str("channel_id", portal.Key.ChannelID).Logger()
|
||||||
|
createType := strings.ToLower(strings.TrimLeft(ce.Args[0], "-"))
|
||||||
|
var webhookID int64
|
||||||
|
var webhookSecret string
|
||||||
|
switch createType {
|
||||||
|
case "url":
|
||||||
|
if len(ce.Args) < 2 {
|
||||||
|
ce.Reply("Usage: `$cmdprefix [room ID] --url <URL>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ce.Redact()
|
||||||
|
_, err := fmt.Sscanf(ce.Args[1], webhookURLFormat, &webhookID, &webhookSecret)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Str("webhook_url", ce.Args[1]).Err(err).Msg("Failed to parse provided webhook URL")
|
||||||
|
ce.Reply("Invalid webhook URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "create":
|
||||||
|
perms, err := ce.User.Session.UserChannelPermissions(ce.User.DiscordID, portal.Key.ChannelID)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Msg("Failed to check user permissions")
|
||||||
|
ce.Reply("Failed to check if you have permission to create webhooks")
|
||||||
|
return
|
||||||
|
} else if perms&discordgo.PermissionManageWebhooks == 0 {
|
||||||
|
log.Debug().Int64("perms", perms).Msg("User doesn't have permissions to manage webhooks in channel")
|
||||||
|
ce.Reply("You don't have permission to manage webhooks in that channel")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := "mautrix"
|
||||||
|
if len(ce.Args) > 1 {
|
||||||
|
name = strings.Join(ce.Args[1:], " ")
|
||||||
|
}
|
||||||
|
log.Debug().Str("webhook_name", name).Msg("Creating webhook")
|
||||||
|
webhook, err := ce.User.Session.WebhookCreate(portal.Key.ChannelID, name, "")
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Msg("Failed to create webhook")
|
||||||
|
ce.Reply("Failed to create webhook: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
webhookID, _ = strconv.ParseInt(webhook.ID, 10, 64)
|
||||||
|
ce.Reply("Created webhook %s", webhook.Name)
|
||||||
|
webhookSecret = webhook.Token
|
||||||
|
default:
|
||||||
|
ce.Reply(selectRelayHelp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug().Int64("webhook_id", webhookID).Msg("Setting portal relay webhook")
|
||||||
|
portal.RelayWebhookID = strconv.FormatInt(webhookID, 10)
|
||||||
|
portal.RelayWebhookSecret = webhookSecret
|
||||||
|
portal.Update()
|
||||||
|
ce.Reply("Saved webhook ID %s as portal relay webhook", portal.RelayWebhookID)
|
||||||
|
}
|
||||||
|
|
||||||
var cmdGuilds = &commands.FullHandler{
|
var cmdGuilds = &commands.FullHandler{
|
||||||
Func: wrapCommand(fnGuilds),
|
Func: wrapCommand(fnGuilds),
|
||||||
Name: "guilds",
|
Name: "guilds",
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import (
|
|||||||
func DoUpgrade(helper *up.Helper) {
|
func DoUpgrade(helper *up.Helper) {
|
||||||
bridgeconfig.Upgrader.DoUpgrade(helper)
|
bridgeconfig.Upgrader.DoUpgrade(helper)
|
||||||
|
|
||||||
|
helper.Copy(up.Str|up.Null, "homeserver", "public_address")
|
||||||
|
|
||||||
helper.Copy(up.Str, "bridge", "username_template")
|
helper.Copy(up.Str, "bridge", "username_template")
|
||||||
helper.Copy(up.Str, "bridge", "displayname_template")
|
helper.Copy(up.Str, "bridge", "displayname_template")
|
||||||
helper.Copy(up.Str, "bridge", "channel_name_template")
|
helper.Copy(up.Str, "bridge", "channel_name_template")
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const (
|
|||||||
portalSelect = `
|
portalSelect = `
|
||||||
SELECT dcid, receiver, type, other_user_id, dc_guild_id, dc_parent_id, mxid,
|
SELECT dcid, receiver, type, other_user_id, dc_guild_id, dc_parent_id, mxid,
|
||||||
plain_name, name, name_set, topic, topic_set, avatar, avatar_url, avatar_set,
|
plain_name, name, name_set, topic, topic_set, avatar, avatar_url, avatar_set,
|
||||||
encrypted, in_space, first_event_id
|
encrypted, in_space, first_event_id, relay_webhook_id, relay_webhook_secret
|
||||||
FROM portal
|
FROM portal
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@@ -121,16 +121,19 @@ type Portal struct {
|
|||||||
InSpace id.RoomID
|
InSpace id.RoomID
|
||||||
|
|
||||||
FirstEventID id.EventID
|
FirstEventID id.EventID
|
||||||
|
|
||||||
|
RelayWebhookID string
|
||||||
|
RelayWebhookSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Portal) Scan(row dbutil.Scannable) *Portal {
|
func (p *Portal) Scan(row dbutil.Scannable) *Portal {
|
||||||
var otherUserID, guildID, parentID, mxid, firstEventID sql.NullString
|
var otherUserID, guildID, parentID, mxid, firstEventID, relayWebhookID, relayWebhookSecret sql.NullString
|
||||||
var chanType int32
|
var chanType int32
|
||||||
var avatarURL string
|
var avatarURL string
|
||||||
|
|
||||||
err := row.Scan(&p.Key.ChannelID, &p.Key.Receiver, &chanType, &otherUserID, &guildID, &parentID,
|
err := row.Scan(&p.Key.ChannelID, &p.Key.Receiver, &chanType, &otherUserID, &guildID, &parentID,
|
||||||
&mxid, &p.PlainName, &p.Name, &p.NameSet, &p.Topic, &p.TopicSet, &p.Avatar, &avatarURL, &p.AvatarSet,
|
&mxid, &p.PlainName, &p.Name, &p.NameSet, &p.Topic, &p.TopicSet, &p.Avatar, &avatarURL, &p.AvatarSet,
|
||||||
&p.Encrypted, &p.InSpace, &firstEventID)
|
&p.Encrypted, &p.InSpace, &firstEventID, &relayWebhookID, &relayWebhookSecret)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != sql.ErrNoRows {
|
if err != sql.ErrNoRows {
|
||||||
@@ -148,6 +151,8 @@ func (p *Portal) Scan(row dbutil.Scannable) *Portal {
|
|||||||
p.Type = discordgo.ChannelType(chanType)
|
p.Type = discordgo.ChannelType(chanType)
|
||||||
p.FirstEventID = id.EventID(firstEventID.String)
|
p.FirstEventID = id.EventID(firstEventID.String)
|
||||||
p.AvatarURL, _ = id.ParseContentURI(avatarURL)
|
p.AvatarURL, _ = id.ParseContentURI(avatarURL)
|
||||||
|
p.RelayWebhookID = relayWebhookID.String
|
||||||
|
p.RelayWebhookSecret = relayWebhookSecret.String
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
@@ -156,13 +161,13 @@ func (p *Portal) Insert() {
|
|||||||
query := `
|
query := `
|
||||||
INSERT INTO portal (dcid, receiver, type, other_user_id, dc_guild_id, dc_parent_id, mxid,
|
INSERT INTO portal (dcid, receiver, type, other_user_id, dc_guild_id, dc_parent_id, mxid,
|
||||||
plain_name, name, name_set, topic, topic_set, avatar, avatar_url, avatar_set,
|
plain_name, name, name_set, topic, topic_set, avatar, avatar_url, avatar_set,
|
||||||
encrypted, in_space, first_event_id)
|
encrypted, in_space, first_event_id, relay_webhook_id, relay_webhook_secret)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
|
||||||
`
|
`
|
||||||
_, err := p.db.Exec(query, p.Key.ChannelID, p.Key.Receiver, p.Type,
|
_, err := p.db.Exec(query, p.Key.ChannelID, p.Key.Receiver, p.Type,
|
||||||
strPtr(p.OtherUserID), strPtr(p.GuildID), strPtr(p.ParentID), strPtr(string(p.MXID)),
|
strPtr(p.OtherUserID), strPtr(p.GuildID), strPtr(p.ParentID), strPtr(string(p.MXID)),
|
||||||
p.PlainName, p.Name, p.NameSet, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet,
|
p.PlainName, p.Name, p.NameSet, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet,
|
||||||
p.Encrypted, p.InSpace, p.FirstEventID.String())
|
p.Encrypted, p.InSpace, p.FirstEventID.String(), strPtr(p.RelayWebhookID), strPtr(p.RelayWebhookSecret))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Warnfln("Failed to insert %s: %v", p.Key, err)
|
p.log.Warnfln("Failed to insert %s: %v", p.Key, err)
|
||||||
@@ -175,13 +180,13 @@ func (p *Portal) Update() {
|
|||||||
UPDATE portal
|
UPDATE portal
|
||||||
SET type=$1, other_user_id=$2, dc_guild_id=$3, dc_parent_id=$4, mxid=$5,
|
SET type=$1, other_user_id=$2, dc_guild_id=$3, dc_parent_id=$4, mxid=$5,
|
||||||
plain_name=$6, name=$7, name_set=$8, topic=$9, topic_set=$10, avatar=$11, avatar_url=$12, avatar_set=$13,
|
plain_name=$6, name=$7, name_set=$8, topic=$9, topic_set=$10, avatar=$11, avatar_url=$12, avatar_set=$13,
|
||||||
encrypted=$14, in_space=$15, first_event_id=$16
|
encrypted=$14, in_space=$15, first_event_id=$16, relay_webhook_id=$17, relay_webhook_secret=$18
|
||||||
WHERE dcid=$17 AND receiver=$18
|
WHERE dcid=$19 AND receiver=$20
|
||||||
`
|
`
|
||||||
_, err := p.db.Exec(query,
|
_, err := p.db.Exec(query,
|
||||||
p.Type, strPtr(p.OtherUserID), strPtr(p.GuildID), strPtr(p.ParentID), strPtr(string(p.MXID)),
|
p.Type, strPtr(p.OtherUserID), strPtr(p.GuildID), strPtr(p.ParentID), strPtr(string(p.MXID)),
|
||||||
p.PlainName, p.Name, p.NameSet, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet,
|
p.PlainName, p.Name, p.NameSet, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet,
|
||||||
p.Encrypted, p.InSpace, p.FirstEventID.String(),
|
p.Encrypted, p.InSpace, p.FirstEventID.String(), strPtr(p.RelayWebhookID), strPtr(p.RelayWebhookSecret),
|
||||||
p.Key.ChannelID, p.Key.Receiver)
|
p.Key.ChannelID, p.Key.Receiver)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-- v0 -> v14: Latest revision
|
-- v0 -> v15: Latest revision
|
||||||
|
|
||||||
CREATE TABLE guild (
|
CREATE TABLE guild (
|
||||||
dcid TEXT PRIMARY KEY,
|
dcid TEXT PRIMARY KEY,
|
||||||
@@ -39,6 +39,9 @@ CREATE TABLE portal (
|
|||||||
|
|
||||||
first_event_id TEXT NOT NULL,
|
first_event_id TEXT NOT NULL,
|
||||||
|
|
||||||
|
relay_webhook_id TEXT,
|
||||||
|
relay_webhook_secret TEXT,
|
||||||
|
|
||||||
PRIMARY KEY (dcid, receiver),
|
PRIMARY KEY (dcid, receiver),
|
||||||
CONSTRAINT portal_parent_fkey FOREIGN KEY (dc_parent_id, dc_parent_receiver) REFERENCES portal (dcid, receiver) ON DELETE CASCADE,
|
CONSTRAINT portal_parent_fkey FOREIGN KEY (dc_parent_id, dc_parent_receiver) REFERENCES portal (dcid, receiver) ON DELETE CASCADE,
|
||||||
CONSTRAINT portal_guild_fkey FOREIGN KEY (dc_guild_id) REFERENCES guild(dcid) ON DELETE CASCADE
|
CONSTRAINT portal_guild_fkey FOREIGN KEY (dc_guild_id) REFERENCES guild(dcid) ON DELETE CASCADE
|
||||||
|
|||||||
3
database/upgrades/15-portal-relay-webhook.sql
Normal file
3
database/upgrades/15-portal-relay-webhook.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
-- v15: Store relay webhook URL for portals
|
||||||
|
ALTER TABLE portal ADD COLUMN relay_webhook_id TEXT;
|
||||||
|
ALTER TABLE portal ADD COLUMN relay_webhook_secret TEXT;
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
homeserver:
|
homeserver:
|
||||||
# The address that this appservice can use to connect to the homeserver.
|
# The address that this appservice can use to connect to the homeserver.
|
||||||
address: https://matrix.example.com
|
address: https://matrix.example.com
|
||||||
|
# Publicly accessible base URL for media, used for avatars in relay mode.
|
||||||
|
# If not set, the connection address above will be used.
|
||||||
|
public_address: null
|
||||||
# The domain of the homeserver (also known as server_name, used for MXIDs, etc).
|
# The domain of the homeserver (also known as server_name, used for MXIDs, etc).
|
||||||
domain: example.com
|
domain: example.com
|
||||||
|
|
||||||
|
|||||||
26
formatter.go
26
formatter.go
@@ -21,6 +21,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
@@ -91,6 +92,16 @@ func (portal *Portal) renderDiscordMarkdownOnlyHTML(text string, allowInlineLink
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatterContextPortalKey = "fi.mau.discord.portal"
|
const formatterContextPortalKey = "fi.mau.discord.portal"
|
||||||
|
const formatterContextAllowedMentionsKey = "fi.mau.discord.allowed_mentions"
|
||||||
|
|
||||||
|
func appendIfNotContains(arr []string, newItem string) []string {
|
||||||
|
for _, item := range arr {
|
||||||
|
if item == newItem {
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(arr, newItem)
|
||||||
|
}
|
||||||
|
|
||||||
func (br *DiscordBridge) pillConverter(displayname, mxid, eventID string, ctx format.Context) string {
|
func (br *DiscordBridge) pillConverter(displayname, mxid, eventID string, ctx format.Context) string {
|
||||||
if len(mxid) == 0 {
|
if len(mxid) == 0 {
|
||||||
@@ -124,12 +135,15 @@ func (br *DiscordBridge) pillConverter(displayname, mxid, eventID string, ctx fo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if mxid[0] == '@' {
|
} else if mxid[0] == '@' {
|
||||||
|
mentions := ctx.ReturnData[formatterContextAllowedMentionsKey].(*discordgo.MessageAllowedMentions)
|
||||||
parsedID, ok := br.ParsePuppetMXID(id.UserID(mxid))
|
parsedID, ok := br.ParsePuppetMXID(id.UserID(mxid))
|
||||||
if ok {
|
if ok {
|
||||||
|
mentions.Users = appendIfNotContains(mentions.Users, parsedID)
|
||||||
return fmt.Sprintf("<@%s>", parsedID)
|
return fmt.Sprintf("<@%s>", parsedID)
|
||||||
}
|
}
|
||||||
mentionedUser := br.GetUserByMXID(id.UserID(mxid))
|
mentionedUser := br.GetUserByMXID(id.UserID(mxid))
|
||||||
if mentionedUser != nil && mentionedUser.DiscordID != "" {
|
if mentionedUser != nil && mentionedUser.DiscordID != "" {
|
||||||
|
mentions.Users = appendIfNotContains(mentions.Users, mentionedUser.DiscordID)
|
||||||
return fmt.Sprintf("<@%s>", mentionedUser.DiscordID)
|
return fmt.Sprintf("<@%s>", mentionedUser.DiscordID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,12 +209,18 @@ var matrixHTMLParser = &format.HTMLParser{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) parseMatrixHTML(content *event.MessageEventContent) string {
|
func (portal *Portal) parseMatrixHTML(content *event.MessageEventContent) (string, *discordgo.MessageAllowedMentions) {
|
||||||
|
allowedMentions := &discordgo.MessageAllowedMentions{
|
||||||
|
Parse: []discordgo.AllowedMentionType{},
|
||||||
|
Users: []string{},
|
||||||
|
RepliedUser: true,
|
||||||
|
}
|
||||||
if content.Format == event.FormatHTML && len(content.FormattedBody) > 0 {
|
if content.Format == event.FormatHTML && len(content.FormattedBody) > 0 {
|
||||||
ctx := format.NewContext()
|
ctx := format.NewContext()
|
||||||
ctx.ReturnData[formatterContextPortalKey] = portal
|
ctx.ReturnData[formatterContextPortalKey] = portal
|
||||||
return variationselector.FullyQualify(matrixHTMLParser.Parse(content.FormattedBody, ctx))
|
ctx.ReturnData[formatterContextAllowedMentionsKey] = allowedMentions
|
||||||
|
return variationselector.FullyQualify(matrixHTMLParser.Parse(content.FormattedBody, ctx)), allowedMentions
|
||||||
} else {
|
} else {
|
||||||
return variationselector.FullyQualify(escapeDiscordMarkdown(content.Body))
|
return variationselector.FullyQualify(escapeDiscordMarkdown(content.Body)), allowedMentions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -15,7 +15,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/yuin/goldmark v1.5.4
|
github.com/yuin/goldmark v1.5.4
|
||||||
maunium.net/go/maulogger/v2 v2.4.1
|
maunium.net/go/maulogger/v2 v2.4.1
|
||||||
maunium.net/go/mautrix v0.15.0-beta.1.0.20230226232632-00f40652f33d
|
maunium.net/go/mautrix v0.15.0-beta.1.0.20230227211640-c8b3566fb7ba
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -37,4 +37,4 @@ require (
|
|||||||
maunium.net/go/mauflag v1.0.0 // indirect
|
maunium.net/go/mauflag v1.0.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20230226184350-ef6bcfe94f07
|
replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20230227224009-daaee0136f88
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -1,6 +1,6 @@
|
|||||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||||
github.com/beeper/discordgo v0.0.0-20230226184350-ef6bcfe94f07 h1:YajAt8iJkBn4aavUuftybeXUaeN4p0DPCE3a4wxE2Oc=
|
github.com/beeper/discordgo v0.0.0-20230227224009-daaee0136f88 h1:sZUZP+ClkQk1uC0KB6dYJ+v6Ygao3RaPKp/3leRjYik=
|
||||||
github.com/beeper/discordgo v0.0.0-20230226184350-ef6bcfe94f07/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
github.com/beeper/discordgo v0.0.0-20230227224009-daaee0136f88/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||||
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 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U=
|
||||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -82,5 +82,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.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
|
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/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
|
||||||
maunium.net/go/mautrix v0.15.0-beta.1.0.20230226232632-00f40652f33d h1:16Q4co5TusYEovGLiHSkT6FY6fFn5tNNLCR3FvGCLFk=
|
maunium.net/go/mautrix v0.15.0-beta.1.0.20230227211640-c8b3566fb7ba h1:OS+zjLTyeqxzMcgnbBbXlZSr0B2yfalCo2lNhC2wP5A=
|
||||||
maunium.net/go/mautrix v0.15.0-beta.1.0.20230226232632-00f40652f33d/go.mod h1:AE3TCX9q4W7fYfrL/1RsuOell9rTUBO27XUULuwArH4=
|
maunium.net/go/mautrix v0.15.0-beta.1.0.20230227211640-c8b3566fb7ba/go.mod h1:AE3TCX9q4W7fYfrL/1RsuOell9rTUBO27XUULuwArH4=
|
||||||
|
|||||||
147
portal.go
147
portal.go
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -41,6 +42,8 @@ type portalMatrixMessage struct {
|
|||||||
user *User
|
user *User
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var relayClient, _ = discordgo.New("")
|
||||||
|
|
||||||
type Portal struct {
|
type Portal struct {
|
||||||
*database.Portal
|
*database.Portal
|
||||||
|
|
||||||
@@ -85,7 +88,7 @@ func (portal *Portal) MarkEncrypted() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) ReceiveMatrixEvent(user bridge.User, evt *event.Event) {
|
func (portal *Portal) ReceiveMatrixEvent(user bridge.User, evt *event.Event) {
|
||||||
if user.GetPermissionLevel() >= bridgeconfig.PermissionLevelUser /*|| portal.HasRelaybot()*/ {
|
if user.GetPermissionLevel() >= bridgeconfig.PermissionLevelUser || portal.RelayWebhookID != "" {
|
||||||
portal.matrixMessages <- portalMatrixMessage{user: user.(*User), evt: evt}
|
portal.matrixMessages <- portalMatrixMessage{user: user.(*User), evt: evt}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -971,6 +974,7 @@ var (
|
|||||||
errUnknownRelationType = errors.New("unknown relation type")
|
errUnknownRelationType = errors.New("unknown relation type")
|
||||||
errTargetNotFound = errors.New("target event not found")
|
errTargetNotFound = errors.New("target event not found")
|
||||||
errUnknownEmoji = errors.New("unknown emoji")
|
errUnknownEmoji = errors.New("unknown emoji")
|
||||||
|
errCantStartThread = errors.New("can't create thread without being logged into Discord")
|
||||||
)
|
)
|
||||||
|
|
||||||
func errorToStatusReason(err error) (reason event.MessageStatusReason, status event.MessageStatus, isCertain, sendNotice bool, humanMessage string) {
|
func errorToStatusReason(err error) (reason event.MessageStatusReason, status event.MessageStatus, isCertain, sendNotice bool, humanMessage string) {
|
||||||
@@ -981,7 +985,8 @@ func errorToStatusReason(err error) (reason event.MessageStatusReason, status ev
|
|||||||
errors.Is(err, errUnknownEmoji),
|
errors.Is(err, errUnknownEmoji),
|
||||||
errors.Is(err, id.InvalidContentURI),
|
errors.Is(err, id.InvalidContentURI),
|
||||||
errors.Is(err, attachment.UnsupportedVersion),
|
errors.Is(err, attachment.UnsupportedVersion),
|
||||||
errors.Is(err, attachment.UnsupportedAlgorithm):
|
errors.Is(err, attachment.UnsupportedAlgorithm),
|
||||||
|
errors.Is(err, errCantStartThread):
|
||||||
return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, ""
|
return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, ""
|
||||||
case errors.Is(err, attachment.HashMismatch),
|
case errors.Is(err, attachment.HashMismatch),
|
||||||
errors.Is(err, attachment.InvalidKey),
|
errors.Is(err, attachment.InvalidKey),
|
||||||
@@ -1065,6 +1070,22 @@ func (portal *Portal) sendMessageMetrics(evt *event.Event, err error, part strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) getRelayUserMeta(sender *User) (name, avatarURL string) {
|
||||||
|
member := portal.bridge.StateStore.GetMember(portal.MXID, sender.MXID)
|
||||||
|
name = member.Displayname
|
||||||
|
if name == "" {
|
||||||
|
name = sender.MXID.String()
|
||||||
|
}
|
||||||
|
mxc := member.AvatarURL.ParseOrIgnore()
|
||||||
|
if !mxc.IsEmpty() {
|
||||||
|
avatarURL = mautrix.BuildURL(
|
||||||
|
portal.bridge.PublicHSAddress,
|
||||||
|
"_matrix", "media", "v3", "download", mxc.Homeserver, mxc.FileID,
|
||||||
|
).String()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
||||||
if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver {
|
if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver {
|
||||||
go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring")
|
go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring")
|
||||||
@@ -1078,14 +1099,23 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
channelID := portal.Key.ChannelID
|
channelID := portal.Key.ChannelID
|
||||||
|
sess := sender.Session
|
||||||
var threadID string
|
var threadID string
|
||||||
|
|
||||||
if editMXID := content.GetRelatesTo().GetReplaceID(); editMXID != "" && content.NewContent != nil {
|
if editMXID := content.GetRelatesTo().GetReplaceID(); editMXID != "" && content.NewContent != nil {
|
||||||
edits := portal.bridge.DB.Message.GetByMXID(portal.Key, editMXID)
|
edits := portal.bridge.DB.Message.GetByMXID(portal.Key, editMXID)
|
||||||
if edits != nil {
|
if edits != nil {
|
||||||
discordContent := portal.parseMatrixHTML(content.NewContent)
|
discordContent, allowedMentions := portal.parseMatrixHTML(content.NewContent)
|
||||||
// TODO save edit in message table
|
var err error
|
||||||
_, err := sender.Session.ChannelMessageEdit(edits.DiscordProtoChannelID(), edits.DiscordID, discordContent)
|
if sess != nil {
|
||||||
|
// TODO save edit in message table
|
||||||
|
_, err = sess.ChannelMessageEdit(edits.DiscordProtoChannelID(), edits.DiscordID, discordContent)
|
||||||
|
} else {
|
||||||
|
_, err = relayClient.WebhookMessageEdit(portal.RelayWebhookID, portal.RelayWebhookSecret, edits.DiscordID, &discordgo.WebhookEdit{
|
||||||
|
Content: &discordContent,
|
||||||
|
AllowedMentions: allowedMentions,
|
||||||
|
})
|
||||||
|
}
|
||||||
go portal.sendMessageMetrics(evt, err, "Failed to edit")
|
go portal.sendMessageMetrics(evt, err, "Failed to edit")
|
||||||
} else {
|
} else {
|
||||||
go portal.sendMessageMetrics(evt, fmt.Errorf("%w %s", errUnknownEditTarget, editMXID), "Ignoring")
|
go portal.sendMessageMetrics(evt, fmt.Errorf("%w %s", errUnknownEditTarget, editMXID), "Ignoring")
|
||||||
@@ -1096,6 +1126,11 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|||||||
if existingThread != nil {
|
if existingThread != nil {
|
||||||
threadID = existingThread.ID
|
threadID = existingThread.ID
|
||||||
} else {
|
} else {
|
||||||
|
if sess == nil {
|
||||||
|
// TODO start thread with bot?
|
||||||
|
go portal.sendMessageMetrics(evt, errCantStartThread, "Dropping")
|
||||||
|
return
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
threadID, err = portal.startThreadFromMatrix(sender, threadRoot)
|
threadID, err = portal.startThreadFromMatrix(sender, threadRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1129,48 +1164,85 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sendReq.Content = portal.parseMatrixHTML(content)
|
sendReq.Content, sendReq.AllowedMentions = portal.parseMatrixHTML(content)
|
||||||
case event.MsgAudio, event.MsgFile, event.MsgImage, event.MsgVideo:
|
case event.MsgAudio, event.MsgFile, event.MsgImage, event.MsgVideo:
|
||||||
data, err := downloadMatrixAttachment(portal.MainIntent(), content)
|
data, err := downloadMatrixAttachment(portal.MainIntent(), content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
go portal.sendMessageMetrics(evt, err, "Error downloading media in")
|
go portal.sendMessageMetrics(evt, err, "Error downloading media in")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
filename := content.Body
|
||||||
att := &discordgo.MessageAttachment{
|
|
||||||
ID: "0",
|
|
||||||
Filename: content.Body,
|
|
||||||
Description: description,
|
|
||||||
}
|
|
||||||
sendReq.Attachments = []*discordgo.MessageAttachment{att}
|
|
||||||
if content.FileName != "" && content.FileName != content.Body {
|
if content.FileName != "" && content.FileName != content.Body {
|
||||||
att.Filename = content.FileName
|
filename = content.FileName
|
||||||
sendReq.Content = portal.parseMatrixHTML(content)
|
sendReq.Content, sendReq.AllowedMentions = portal.parseMatrixHTML(content)
|
||||||
}
|
}
|
||||||
prep, err := sender.Session.ChannelAttachmentCreate(channelID, &discordgo.ReqPrepareAttachments{
|
|
||||||
Files: []*discordgo.FilePrepare{{
|
if sess != nil && sess.IsUser {
|
||||||
Size: len(data),
|
att := &discordgo.MessageAttachment{
|
||||||
Name: att.Filename,
|
ID: "0",
|
||||||
ID: sender.NextDiscordUploadID(),
|
Filename: filename,
|
||||||
}},
|
Description: description,
|
||||||
})
|
}
|
||||||
if err != nil {
|
sendReq.Attachments = []*discordgo.MessageAttachment{att}
|
||||||
go portal.sendMessageMetrics(evt, err, "Error preparing to reupload media in")
|
prep, err := sender.Session.ChannelAttachmentCreate(channelID, &discordgo.ReqPrepareAttachments{
|
||||||
return
|
Files: []*discordgo.FilePrepare{{
|
||||||
}
|
Size: len(data),
|
||||||
prepared := prep.Attachments[0]
|
Name: att.Filename,
|
||||||
att.UploadedFilename = prepared.UploadFilename
|
ID: sender.NextDiscordUploadID(),
|
||||||
err = uploadDiscordAttachment(prepared.UploadURL, data)
|
}},
|
||||||
if err != nil {
|
})
|
||||||
go portal.sendMessageMetrics(evt, err, "Error reuploading media in")
|
if err != nil {
|
||||||
return
|
go portal.sendMessageMetrics(evt, err, "Error preparing to reupload media in")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prepared := prep.Attachments[0]
|
||||||
|
att.UploadedFilename = prepared.UploadFilename
|
||||||
|
err = uploadDiscordAttachment(prepared.UploadURL, data)
|
||||||
|
if err != nil {
|
||||||
|
go portal.sendMessageMetrics(evt, err, "Error reuploading media in")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendReq.Files = []*discordgo.File{{
|
||||||
|
Name: filename,
|
||||||
|
ContentType: content.Info.MimeType,
|
||||||
|
Reader: bytes.NewReader(data),
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
go portal.sendMessageMetrics(evt, fmt.Errorf("%w %q", errUnknownMsgType, content.MsgType), "Ignoring")
|
go portal.sendMessageMetrics(evt, fmt.Errorf("%w %q", errUnknownMsgType, content.MsgType), "Ignoring")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if sess != nil {
|
||||||
|
// AllowedMentions must not be set for real users, and it's also not that useful for personal bots.
|
||||||
|
// It's only important for relaying, where the webhook may have higher permissions than the user on Matrix.
|
||||||
|
sendReq.AllowedMentions = nil
|
||||||
|
} else if strings.Contains(sendReq.Content, "@everyone") || strings.Contains(sendReq.Content, "@here") {
|
||||||
|
powerLevels, err := portal.MainIntent().PowerLevels(portal.MXID)
|
||||||
|
if err != nil {
|
||||||
|
portal.log.Warnfln("Failed to get power levels in %s to check if %s can @everyone: %v", portal.MXID, sender.MXID, err)
|
||||||
|
} else if powerLevels.GetUserLevel(sender.MXID) >= powerLevels.Notifications.Room() {
|
||||||
|
sendReq.AllowedMentions.Parse = append(sendReq.AllowedMentions.Parse, discordgo.AllowedMentionTypeEveryone)
|
||||||
|
}
|
||||||
|
}
|
||||||
sendReq.Nonce = generateNonce()
|
sendReq.Nonce = generateNonce()
|
||||||
msg, err := sender.Session.ChannelMessageSendComplex(channelID, &sendReq)
|
var msg *discordgo.Message
|
||||||
|
var err error
|
||||||
|
if sess != nil {
|
||||||
|
msg, err = sess.ChannelMessageSendComplex(channelID, &sendReq)
|
||||||
|
} else {
|
||||||
|
username, avatarURL := portal.getRelayUserMeta(sender)
|
||||||
|
msg, err = relayClient.WebhookThreadExecute(portal.RelayWebhookID, portal.RelayWebhookSecret, true, threadID, &discordgo.WebhookParams{
|
||||||
|
Content: sendReq.Content,
|
||||||
|
Username: username,
|
||||||
|
AvatarURL: avatarURL,
|
||||||
|
TTS: sendReq.TTS,
|
||||||
|
Files: sendReq.Files,
|
||||||
|
Components: sendReq.Components,
|
||||||
|
Embeds: sendReq.Embeds,
|
||||||
|
AllowedMentions: sendReq.AllowedMentions,
|
||||||
|
})
|
||||||
|
}
|
||||||
go portal.sendMessageMetrics(evt, err, "Error sending")
|
go portal.sendMessageMetrics(evt, err, "Error sending")
|
||||||
if msg != nil {
|
if msg != nil {
|
||||||
dbMsg := portal.bridge.DB.Message.New()
|
dbMsg := portal.bridge.DB.Message.New()
|
||||||
@@ -1180,7 +1252,11 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|||||||
dbMsg.AttachmentID = msg.Attachments[0].ID
|
dbMsg.AttachmentID = msg.Attachments[0].ID
|
||||||
}
|
}
|
||||||
dbMsg.MXID = evt.ID
|
dbMsg.MXID = evt.ID
|
||||||
dbMsg.SenderID = sender.DiscordID
|
if sess != nil {
|
||||||
|
dbMsg.SenderID = sender.DiscordID
|
||||||
|
} else {
|
||||||
|
dbMsg.SenderID = portal.RelayWebhookID
|
||||||
|
}
|
||||||
dbMsg.Timestamp, _ = discordgo.SnowflakeTimestamp(msg.ID)
|
dbMsg.Timestamp, _ = discordgo.SnowflakeTimestamp(msg.ID)
|
||||||
dbMsg.ThreadID = threadID
|
dbMsg.ThreadID = threadID
|
||||||
dbMsg.Insert()
|
dbMsg.Insert()
|
||||||
@@ -1333,6 +1409,9 @@ func (portal *Portal) handleMatrixReaction(sender *User, evt *event.Event) {
|
|||||||
if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver {
|
if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver {
|
||||||
go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring")
|
go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring")
|
||||||
return
|
return
|
||||||
|
} else if !sender.IsLoggedIn() {
|
||||||
|
//go portal.sendMessageMetrics(evt, errReactionUserNotLoggedIn, "Ignoring")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reaction := evt.Content.AsReaction()
|
reaction := evt.Content.AsReaction()
|
||||||
|
|||||||
Reference in New Issue
Block a user