forked from mirror/mautrix-discord
Compare commits
1 commit
main
...
tulir/maut
Author | SHA1 | Date | |
---|---|---|---|
|
b84e7bb752 |
31 changed files with 528 additions and 598 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,14 +1,6 @@
|
|||
# v0.7.0 (2024-07-16)
|
||||
# v0.7.0 (unreleased)
|
||||
|
||||
* Bumped minimum Go version to 1.21.
|
||||
* Added support for Matrix v1.11 authenticated media.
|
||||
* This also changes how avatars are sent to Discord when using relay webhooks.
|
||||
To keep avatars working, you must configure `public_address` in the *bridge*
|
||||
section of the config and proxy `/mautrix-discord/avatar/*` from that
|
||||
address to the bridge.
|
||||
* Added `create-portal` command to create individual portals bypassing the
|
||||
bridging mode. When used in combination with the `if-portal-exists` bridging
|
||||
mode, this can be used to bridge individual channels from a guild.
|
||||
* Changed how direct media access works to make it compatible with Discord's
|
||||
signed URL requirement. The new system must be enabled manually, see
|
||||
[docs](https://docs.mau.fi/bridges/go/discord/direct-media.html) for info.
|
||||
|
|
|
@ -12,8 +12,7 @@ All setup and usage instructions are located on [docs.mau.fi]. Some quick links:
|
|||
[Relaying with webhooks](https://docs.mau.fi/bridges/go/discord/relay.html)
|
||||
|
||||
### Features & Roadmap
|
||||
[ROADMAP.md](https://github.com/mautrix/discord/blob/main/ROADMAP.md)
|
||||
contains a general overview of what is supported by the bridge.
|
||||
[ROADMAP.md](ROADMAP.md) contains a general overview of what is supported by the bridge.
|
||||
|
||||
## Discussion
|
||||
Matrix room: [#discord:maunium.net](https://matrix.to/#/#discord:maunium.net)
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
16
backfill.go
16
backfill.go
|
@ -1,3 +1,19 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
42
commands.go
42
commands.go
|
@ -62,7 +62,6 @@ func (br *DiscordBridge) RegisterCommands() {
|
|||
cmdBridge,
|
||||
cmdUnbridge,
|
||||
cmdDeletePortal,
|
||||
cmdCreatePortal,
|
||||
cmdSetRelay,
|
||||
cmdUnsetRelay,
|
||||
cmdGuilds,
|
||||
|
@ -758,7 +757,7 @@ func fnBridge(ce *WrappedCommandEvent) {
|
|||
portal.updateRoomName()
|
||||
portal.updateRoomAvatar()
|
||||
portal.updateRoomTopic()
|
||||
portal.updateSpace(ce.User)
|
||||
portal.updateSpace()
|
||||
portal.UpdateBridgeInfo()
|
||||
state, err := portal.MainIntent().State(portal.MXID)
|
||||
if err != nil {
|
||||
|
@ -786,45 +785,6 @@ var cmdUnbridge = &commands.FullHandler{
|
|||
RequiresEventLevel: roomModerator,
|
||||
}
|
||||
|
||||
var cmdCreatePortal = &commands.FullHandler{
|
||||
Func: wrapCommand(fnCreatePortal),
|
||||
Name: "create-portal",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionPortalManagement,
|
||||
Description: "Create a portal for a specific channel",
|
||||
Args: "<_channel ID_>",
|
||||
},
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
func fnCreatePortal(ce *WrappedCommandEvent) {
|
||||
meta, err := ce.User.Session.Channel(ce.Args[0])
|
||||
if err != nil {
|
||||
ce.Reply("Failed to get channel info: %v", err)
|
||||
return
|
||||
} else if meta == nil {
|
||||
ce.Reply("Channel not found")
|
||||
return
|
||||
} else if !ce.User.channelIsBridgeable(meta) {
|
||||
ce.Reply("That channel can't be bridged")
|
||||
return
|
||||
}
|
||||
portal := ce.User.GetPortalByMeta(meta)
|
||||
if portal.Guild != nil && portal.Guild.BridgingMode == database.GuildBridgeNothing {
|
||||
ce.Reply("That guild is set to not bridge any messages. Bridge the guild with `$cmdprefix guilds bridge %s` first", portal.Guild.ID)
|
||||
return
|
||||
} else if portal.MXID != "" {
|
||||
ce.Reply("That channel is already bridged: [%s](%s)", portal.Name, portal.MXID.URI(portal.bridge.Config.Homeserver.Domain).MatrixToURL())
|
||||
return
|
||||
}
|
||||
err = portal.CreateMatrixRoom(ce.User, meta)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to create portal: %v", err)
|
||||
} else {
|
||||
ce.Reply("Portal created: [%s](%s)", portal.Name, portal.MXID.URI(portal.bridge.Config.Homeserver.Domain).MatrixToURL())
|
||||
}
|
||||
}
|
||||
|
||||
var cmdDeletePortal = &commands.FullHandler{
|
||||
Func: wrapCommand(fnUnbridge),
|
||||
Name: "delete-portal",
|
||||
|
|
|
@ -37,9 +37,6 @@ type BridgeConfig struct {
|
|||
|
||||
PortalMessageBuffer int `yaml:"portal_message_buffer"`
|
||||
|
||||
PublicAddress string `yaml:"public_address"`
|
||||
AvatarProxyKey string `yaml:"avatar_proxy_key"`
|
||||
|
||||
DeliveryReceipts bool `yaml:"delivery_receipts"`
|
||||
MessageStatusEvents bool `yaml:"message_status_events"`
|
||||
MessageErrorNotices bool `yaml:"message_error_notices"`
|
||||
|
|
|
@ -26,6 +26,8 @@ import (
|
|||
func DoUpgrade(helper *up.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", "displayname_template")
|
||||
helper.Copy(up.Str, "bridge", "channel_name_template")
|
||||
|
@ -40,12 +42,6 @@ func DoUpgrade(helper *up.Helper) {
|
|||
helper.Copy(up.Str, "bridge", "private_chat_portal_meta")
|
||||
}
|
||||
helper.Copy(up.Int, "bridge", "startup_private_channel_create_limit")
|
||||
helper.Copy(up.Str|up.Null, "bridge", "public_address")
|
||||
if apkey, ok := helper.Get(up.Str, "bridge", "avatar_proxy_key"); !ok || apkey == "generate" {
|
||||
helper.Set(up.Str, random.String(32), "bridge", "avatar_proxy_key")
|
||||
} else {
|
||||
helper.Copy(up.Str, "bridge", "avatar_proxy_key")
|
||||
}
|
||||
helper.Copy(up.Int, "bridge", "portal_message_buffer")
|
||||
helper.Copy(up.Bool, "bridge", "delivery_receipts")
|
||||
helper.Copy(up.Bool, "bridge", "message_status_events")
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
|
@ -33,7 +51,7 @@ func (puppet *Puppet) ClearCustomMXID() {
|
|||
}
|
||||
|
||||
func (puppet *Puppet) StartCustomMXID(reloginOnFail bool) error {
|
||||
newIntent, newAccessToken, err := puppet.bridge.DoublePuppet.Setup(puppet.CustomMXID, puppet.AccessToken, reloginOnFail)
|
||||
newIntent, newAccessToken, err := puppet.bridge.DoublePuppet.Setup(context.TODO(), puppet.CustomMXID, puppet.AccessToken, reloginOnFail)
|
||||
if err != nil {
|
||||
puppet.ClearCustomMXID()
|
||||
return err
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
|
@ -6,7 +22,6 @@ import (
|
|||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"go.mau.fi/util/dbutil"
|
||||
"maunium.net/go/maulogger/v2"
|
||||
|
||||
"go.mau.fi/mautrix-discord/database/upgrades"
|
||||
)
|
||||
|
@ -25,52 +40,18 @@ type Database struct {
|
|||
File *FileQuery
|
||||
}
|
||||
|
||||
func New(baseDB *dbutil.Database, log maulogger.Logger) *Database {
|
||||
db := &Database{Database: baseDB}
|
||||
func New(db *dbutil.Database) *Database {
|
||||
db.UpgradeTable = upgrades.Table
|
||||
db.User = &UserQuery{
|
||||
db: db,
|
||||
log: log.Sub("User"),
|
||||
return &Database{
|
||||
Database: db,
|
||||
User: &UserQuery{dbutil.MakeQueryHelper(db, newUser)},
|
||||
Portal: &PortalQuery{dbutil.MakeQueryHelper(db, newPortal)},
|
||||
Puppet: &PuppetQuery{dbutil.MakeQueryHelper(db, newPuppet)},
|
||||
Message: &MessageQuery{dbutil.MakeQueryHelper(db, newMessage)},
|
||||
Thread: &ThreadQuery{dbutil.MakeQueryHelper(db, newThread)},
|
||||
Reaction: &ReactionQuery{dbutil.MakeQueryHelper(db, newReaction)},
|
||||
Guild: &GuildQuery{dbutil.MakeQueryHelper(db, newGuild)},
|
||||
Role: &RoleQuery{dbutil.MakeQueryHelper(db, newRole)},
|
||||
File: &FileQuery{dbutil.MakeQueryHelper(db, newFile)},
|
||||
}
|
||||
db.Portal = &PortalQuery{
|
||||
db: db,
|
||||
log: log.Sub("Portal"),
|
||||
}
|
||||
db.Puppet = &PuppetQuery{
|
||||
db: db,
|
||||
log: log.Sub("Puppet"),
|
||||
}
|
||||
db.Message = &MessageQuery{
|
||||
db: db,
|
||||
log: log.Sub("Message"),
|
||||
}
|
||||
db.Thread = &ThreadQuery{
|
||||
db: db,
|
||||
log: log.Sub("Thread"),
|
||||
}
|
||||
db.Reaction = &ReactionQuery{
|
||||
db: db,
|
||||
log: log.Sub("Reaction"),
|
||||
}
|
||||
db.Guild = &GuildQuery{
|
||||
db: db,
|
||||
log: log.Sub("Guild"),
|
||||
}
|
||||
db.Role = &RoleQuery{
|
||||
db: db,
|
||||
log: log.Sub("Role"),
|
||||
}
|
||||
db.File = &FileQuery{
|
||||
db: db,
|
||||
log: log.Sub("File"),
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func strPtr[T ~string](val T) *string {
|
||||
if val == "" {
|
||||
return nil
|
||||
}
|
||||
valStr := string(val)
|
||||
return &valStr
|
||||
}
|
||||
|
|
133
database/file.go
133
database/file.go
|
@ -1,51 +1,66 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
log "maunium.net/go/maulogger/v2"
|
||||
"maunium.net/go/mautrix/crypto/attachment"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type FileQuery struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
*dbutil.QueryHelper[*File]
|
||||
}
|
||||
|
||||
// language=postgresql
|
||||
const (
|
||||
fileSelect = "SELECT url, encrypted, mxc, id, emoji_name, size, width, height, mime_type, decryption_info, timestamp FROM discord_file"
|
||||
fileInsert = `
|
||||
getFileByURLQuery = `
|
||||
SELECT url, encrypted, mxc, id, emoji_name, size, width, height, mime_type, decryption_info, timestamp
|
||||
FROM discord_file WHERE url=$1 AND encrypted=$2
|
||||
`
|
||||
getFileByEmojiMXCQuery = `
|
||||
SELECT url, encrypted, mxc, id, emoji_name, size, width, height, mime_type, decryption_info, timestamp
|
||||
FROM discord_file WHERE mxc=$1 AND emoji_name<>'' LIMIT 1
|
||||
`
|
||||
insertFileQuery = `
|
||||
INSERT INTO discord_file (url, encrypted, mxc, id, emoji_name, size, width, height, mime_type, decryption_info, timestamp)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
`
|
||||
deleteFileQuery = "DELETE FROM discord_file WHERE url=$1 AND encrypted=$2"
|
||||
)
|
||||
|
||||
func (fq *FileQuery) New() *File {
|
||||
return &File{
|
||||
db: fq.db,
|
||||
log: fq.log,
|
||||
}
|
||||
func newFile(qh *dbutil.QueryHelper[*File]) *File {
|
||||
return &File{qh: qh}
|
||||
}
|
||||
|
||||
func (fq *FileQuery) Get(url string, encrypted bool) *File {
|
||||
query := fileSelect + " WHERE url=$1 AND encrypted=$2"
|
||||
return fq.New().Scan(fq.db.QueryRow(query, url, encrypted))
|
||||
func (fq *FileQuery) Get(ctx context.Context, url string, encrypted bool) (*File, error) {
|
||||
return fq.QueryOne(ctx, getFileByURLQuery, url, encrypted)
|
||||
}
|
||||
|
||||
func (fq *FileQuery) GetEmojiByMXC(mxc id.ContentURI) *File {
|
||||
query := fileSelect + " WHERE mxc=$1 AND emoji_name<>'' LIMIT 1"
|
||||
return fq.New().Scan(fq.db.QueryRow(query, mxc.String()))
|
||||
func (fq *FileQuery) GetEmojiByMXC(ctx context.Context, mxc id.ContentURI) (*File, error) {
|
||||
return fq.QueryOne(ctx, getFileByEmojiMXCQuery, mxc.String())
|
||||
}
|
||||
|
||||
type File struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
qh *dbutil.QueryHelper[*File]
|
||||
|
||||
URL string
|
||||
Encrypted bool
|
||||
|
@ -63,76 +78,38 @@ type File struct {
|
|||
Timestamp time.Time
|
||||
}
|
||||
|
||||
func (f *File) Scan(row dbutil.Scannable) *File {
|
||||
var fileID, emojiName, decryptionInfo sql.NullString
|
||||
func (f *File) Scan(row dbutil.Scannable) (*File, error) {
|
||||
var fileID, emojiName sql.NullString
|
||||
var width, height sql.NullInt32
|
||||
var timestamp int64
|
||||
var mxc string
|
||||
err := row.Scan(&f.URL, &f.Encrypted, &mxc, &fileID, &emojiName, &f.Size, &width, &height, &f.MimeType, &decryptionInfo, ×tamp)
|
||||
err := row.Scan(
|
||||
&f.URL, &f.Encrypted, &f.MXC, &fileID, &emojiName, &f.Size,
|
||||
&width, &height, &f.MimeType,
|
||||
dbutil.JSON{Data: &f.DecryptionInfo}, ×tamp,
|
||||
)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
f.log.Errorln("Database scan failed:", err)
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
f.ID = fileID.String
|
||||
f.EmojiName = emojiName.String
|
||||
f.Timestamp = time.UnixMilli(timestamp).UTC()
|
||||
f.Width = int(width.Int32)
|
||||
f.Height = int(height.Int32)
|
||||
f.MXC, err = id.ParseContentURI(mxc)
|
||||
if err != nil {
|
||||
f.log.Errorfln("Failed to parse content URI %s: %v", mxc, err)
|
||||
panic(err)
|
||||
}
|
||||
if decryptionInfo.Valid {
|
||||
err = json.Unmarshal([]byte(decryptionInfo.String), &f.DecryptionInfo)
|
||||
if err != nil {
|
||||
f.log.Errorfln("Failed to unmarshal decryption info of %v: %v", f.MXC, err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return f
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func positiveIntToNullInt32(val int) (ptr sql.NullInt32) {
|
||||
if val > 0 {
|
||||
ptr.Valid = true
|
||||
ptr.Int32 = int32(val)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *File) Insert(txn dbutil.Execable) {
|
||||
if txn == nil {
|
||||
txn = f.db
|
||||
}
|
||||
var decryptionInfoStr sql.NullString
|
||||
if f.DecryptionInfo != nil {
|
||||
decryptionInfo, err := json.Marshal(f.DecryptionInfo)
|
||||
if err != nil {
|
||||
f.log.Warnfln("Failed to marshal decryption info of %v: %v", f.MXC, err)
|
||||
panic(err)
|
||||
}
|
||||
decryptionInfoStr.Valid = true
|
||||
decryptionInfoStr.String = string(decryptionInfo)
|
||||
}
|
||||
_, err := txn.Exec(fileInsert,
|
||||
f.URL, f.Encrypted, f.MXC.String(), strPtr(f.ID), strPtr(f.EmojiName), f.Size,
|
||||
positiveIntToNullInt32(f.Width), positiveIntToNullInt32(f.Height), f.MimeType,
|
||||
decryptionInfoStr, f.Timestamp.UnixMilli(),
|
||||
)
|
||||
if err != nil {
|
||||
f.log.Warnfln("Failed to insert copied file %v: %v", f.MXC, err)
|
||||
panic(err)
|
||||
func (f *File) sqlVariables() []any {
|
||||
return []any{
|
||||
f.URL, f.Encrypted, f.MXC.String(), dbutil.StrPtr(f.ID), dbutil.StrPtr(f.EmojiName), f.Size,
|
||||
dbutil.NumPtr(f.Width), dbutil.NumPtr(f.Height), f.MimeType,
|
||||
dbutil.JSONPtr(f.DecryptionInfo), f.Timestamp.UnixMilli(),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *File) Delete() {
|
||||
_, err := f.db.Exec("DELETE FROM discord_file WHERE url=$1 AND encrypted=$2", f.URL, f.Encrypted)
|
||||
if err != nil {
|
||||
f.log.Warnfln("Failed to delete copied file %v: %v", f.MXC, err)
|
||||
panic(err)
|
||||
}
|
||||
func (f *File) Insert(ctx context.Context) error {
|
||||
return f.qh.Exec(ctx, insertFileQuery, f.sqlVariables()...)
|
||||
}
|
||||
|
||||
func (f *File) Delete(ctx context.Context) error {
|
||||
return f.qh.Exec(ctx, deleteFileQuery, f.URL, f.Encrypted)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,27 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
log "maunium.net/go/maulogger/v2"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
|
@ -75,19 +89,15 @@ func (gbm GuildBridgingMode) Description() string {
|
|||
}
|
||||
|
||||
type GuildQuery struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
*dbutil.QueryHelper[*Guild]
|
||||
}
|
||||
|
||||
const (
|
||||
guildSelect = "SELECT dcid, mxid, plain_name, name, name_set, avatar, avatar_url, avatar_set, bridging_mode FROM guild"
|
||||
)
|
||||
|
||||
func (gq *GuildQuery) New() *Guild {
|
||||
return &Guild{
|
||||
db: gq.db,
|
||||
log: gq.log,
|
||||
}
|
||||
func newGuild(qh *dbutil.QueryHelper[*Guild]) *Guild {
|
||||
return &Guild{qh: qh}
|
||||
}
|
||||
|
||||
func (gq *GuildQuery) GetByID(dcid string) *Guild {
|
||||
|
@ -119,8 +129,7 @@ func (gq *GuildQuery) GetAll() []*Guild {
|
|||
}
|
||||
|
||||
type Guild struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
qh *dbutil.QueryHelper[*Guild]
|
||||
|
||||
ID string
|
||||
MXID id.RoomID
|
||||
|
@ -134,24 +143,19 @@ type Guild struct {
|
|||
BridgingMode GuildBridgingMode
|
||||
}
|
||||
|
||||
func (g *Guild) Scan(row dbutil.Scannable) *Guild {
|
||||
func (g *Guild) Scan(row dbutil.Scannable) (*Guild, error) {
|
||||
var mxid sql.NullString
|
||||
var avatarURL string
|
||||
err := row.Scan(&g.ID, &mxid, &g.PlainName, &g.Name, &g.NameSet, &g.Avatar, &avatarURL, &g.AvatarSet, &g.BridgingMode)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
g.log.Errorln("Database scan failed:", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
if g.BridgingMode < GuildBridgeNothing || g.BridgingMode > GuildBridgeEverything {
|
||||
panic(fmt.Errorf("invalid guild bridging mode %d in guild %s", g.BridgingMode, g.ID))
|
||||
}
|
||||
g.MXID = id.RoomID(mxid.String)
|
||||
g.AvatarURL, _ = id.ParseContentURI(avatarURL)
|
||||
return g
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *Guild) mxidPtr() *id.RoomID {
|
||||
|
|
|
@ -1,97 +1,92 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
log "maunium.net/go/maulogger/v2"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type MessageQuery struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
*dbutil.QueryHelper[*Message]
|
||||
}
|
||||
|
||||
const (
|
||||
messageSelect = "SELECT dcid, dc_attachment_id, dc_chan_id, dc_chan_receiver, dc_sender, timestamp, dc_edit_timestamp, dc_thread_id, mxid, sender_mxid FROM message"
|
||||
getMessageBaseQuery = `
|
||||
SELECT dcid, dc_attachment_id, dc_chan_id, dc_chan_receiver, dc_sender, timestamp,
|
||||
dc_edit_timestamp, dc_thread_id, mxid, sender_mxid
|
||||
FROM message
|
||||
`
|
||||
getMessageByDiscordIDQuery = getMessageBaseQuery +
|
||||
"WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND dcid=$3 ORDER BY dc_attachment_id"
|
||||
getFirstMessageByDiscordIDQuery = getMessageByDiscordIDQuery +
|
||||
" LIMIT 1"
|
||||
getLastMessageByDiscordIDQuery = getMessageByDiscordIDQuery +
|
||||
" DESC LIMIT 1"
|
||||
getClosestMessageBeforeTimeQuery = getMessageBaseQuery +
|
||||
"WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND dc_thread_id=$3 AND timestamp<=$4 ORDER BY timestamp DESC, dc_attachment_id DESC LIMIT 1"
|
||||
getLastMessageInThreadQuery = getMessageBaseQuery +
|
||||
" WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND dc_thread_id=$3 ORDER BY timestamp DESC, dc_attachment_id DESC LIMIT 1"
|
||||
getLastMessageInPortalQuery = getMessageBaseQuery +
|
||||
" WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 ORDER BY timestamp DESC LIMIT 1"
|
||||
getMessageByMXIDQuery = getMessageBaseQuery +
|
||||
" WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND mxid=$3"
|
||||
deleteAllMessagesInPortalQuery = "DELETE FROM message WHERE dc_chan_id=$1 AND dc_chan_receiver=$2"
|
||||
)
|
||||
|
||||
func (mq *MessageQuery) New() *Message {
|
||||
return &Message{
|
||||
db: mq.db,
|
||||
log: mq.log,
|
||||
}
|
||||
func newMessage(qh *dbutil.QueryHelper[*Message]) *Message {
|
||||
return &Message{qh: qh}
|
||||
}
|
||||
|
||||
func (mq *MessageQuery) scanAll(rows dbutil.Rows, err error) []*Message {
|
||||
if err != nil {
|
||||
mq.log.Warnfln("Failed to query many messages: %v", err)
|
||||
panic(err)
|
||||
} else if rows == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var messages []*Message
|
||||
for rows.Next() {
|
||||
messages = append(messages, mq.New().Scan(rows))
|
||||
}
|
||||
|
||||
return messages
|
||||
func (mq *MessageQuery) GetByDiscordID(ctx context.Context, key PortalKey, discordID string) ([]*Message, error) {
|
||||
return mq.QueryMany(ctx, getMessageByDiscordIDQuery, key.ChannelID, key.Receiver, discordID)
|
||||
}
|
||||
|
||||
func (mq *MessageQuery) GetByDiscordID(key PortalKey, discordID string) []*Message {
|
||||
query := messageSelect + " WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND dcid=$3 ORDER BY dc_attachment_id ASC"
|
||||
return mq.scanAll(mq.db.Query(query, key.ChannelID, key.Receiver, discordID))
|
||||
func (mq *MessageQuery) GetFirstByDiscordID(ctx context.Context, key PortalKey, discordID string) (*Message, error) {
|
||||
return mq.QueryOne(ctx, getFirstMessageByDiscordIDQuery, key.ChannelID, key.Receiver, discordID)
|
||||
}
|
||||
|
||||
func (mq *MessageQuery) GetFirstByDiscordID(key PortalKey, discordID string) *Message {
|
||||
query := messageSelect + " WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND dcid=$3 ORDER BY dc_attachment_id ASC LIMIT 1"
|
||||
return mq.New().Scan(mq.db.QueryRow(query, key.ChannelID, key.Receiver, discordID))
|
||||
func (mq *MessageQuery) GetLastByDiscordID(ctx context.Context, key PortalKey, discordID string) (*Message, error) {
|
||||
return mq.QueryOne(ctx, getLastMessageByDiscordIDQuery, key.ChannelID, key.Receiver, discordID)
|
||||
}
|
||||
|
||||
func (mq *MessageQuery) GetLastByDiscordID(key PortalKey, discordID string) *Message {
|
||||
query := messageSelect + " WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND dcid=$3 ORDER BY dc_attachment_id DESC LIMIT 1"
|
||||
return mq.New().Scan(mq.db.QueryRow(query, key.ChannelID, key.Receiver, discordID))
|
||||
func (mq *MessageQuery) GetClosestBefore(ctx context.Context, key PortalKey, threadID string, ts time.Time) (*Message, error) {
|
||||
return mq.QueryOne(ctx, getClosestMessageBeforeTimeQuery, key.ChannelID, key.Receiver, threadID, ts.UnixMilli())
|
||||
}
|
||||
|
||||
func (mq *MessageQuery) GetClosestBefore(key PortalKey, threadID string, ts time.Time) *Message {
|
||||
query := messageSelect + " WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND dc_thread_id=$3 AND timestamp<=$4 ORDER BY timestamp DESC, dc_attachment_id DESC LIMIT 1"
|
||||
return mq.New().Scan(mq.db.QueryRow(query, key.ChannelID, key.Receiver, threadID, ts.UnixMilli()))
|
||||
func (mq *MessageQuery) GetLastInThread(ctx context.Context, key PortalKey, threadID string) (*Message, error) {
|
||||
return mq.QueryOne(ctx, getLastMessageInThreadQuery, key.ChannelID, key.Receiver, threadID)
|
||||
}
|
||||
|
||||
func (mq *MessageQuery) GetLastInThread(key PortalKey, threadID string) *Message {
|
||||
query := messageSelect + " WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND dc_thread_id=$3 ORDER BY timestamp DESC, dc_attachment_id DESC LIMIT 1"
|
||||
return mq.New().Scan(mq.db.QueryRow(query, key.ChannelID, key.Receiver, threadID))
|
||||
func (mq *MessageQuery) GetLast(ctx context.Context, key PortalKey) (*Message, error) {
|
||||
return mq.QueryOne(ctx, getLastMessageInPortalQuery, key.ChannelID, key.Receiver)
|
||||
}
|
||||
|
||||
func (mq *MessageQuery) GetLast(key PortalKey) *Message {
|
||||
query := messageSelect + " WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 ORDER BY timestamp DESC LIMIT 1"
|
||||
return mq.New().Scan(mq.db.QueryRow(query, key.ChannelID, key.Receiver))
|
||||
func (mq *MessageQuery) GetByMXID(ctx context.Context, key PortalKey, mxid id.EventID) (*Message, error) {
|
||||
return mq.QueryOne(ctx, getMessageByMXIDQuery, key.ChannelID, key.Receiver, mxid)
|
||||
}
|
||||
|
||||
func (mq *MessageQuery) DeleteAll(key PortalKey) {
|
||||
query := "DELETE FROM message WHERE dc_chan_id=$1 AND dc_chan_receiver=$2"
|
||||
_, err := mq.db.Exec(query, key.ChannelID, key.Receiver)
|
||||
if err != nil {
|
||||
mq.log.Warnfln("Failed to delete messages of %s: %v", key, err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (mq *MessageQuery) GetByMXID(key PortalKey, mxid id.EventID) *Message {
|
||||
query := messageSelect + " WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND mxid=$3"
|
||||
|
||||
row := mq.db.QueryRow(query, key.ChannelID, key.Receiver, mxid)
|
||||
if row == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return mq.New().Scan(row)
|
||||
func (mq *MessageQuery) DeleteAll(ctx context.Context, key PortalKey) error {
|
||||
return mq.Exec(ctx, deleteAllMessagesInPortalQuery, key.ChannelID, key.Receiver)
|
||||
}
|
||||
|
||||
func (mq *MessageQuery) MassInsert(key PortalKey, msgs []Message) {
|
||||
|
@ -126,8 +121,7 @@ func (mq *MessageQuery) MassInsert(key PortalKey, msgs []Message) {
|
|||
}
|
||||
|
||||
type Message struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
qh *dbutil.QueryHelper[*Message]
|
||||
|
||||
DiscordID string
|
||||
AttachmentID string
|
||||
|
@ -149,17 +143,11 @@ func (m *Message) DiscordProtoChannelID() string {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *Message) Scan(row dbutil.Scannable) *Message {
|
||||
func (m *Message) Scan(row dbutil.Scannable) (*Message, error) {
|
||||
var ts, editTS int64
|
||||
|
||||
err := row.Scan(&m.DiscordID, &m.AttachmentID, &m.Channel.ChannelID, &m.Channel.Receiver, &m.SenderID, &ts, &editTS, &m.ThreadID, &m.MXID, &m.SenderMXID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
m.log.Errorln("Database scan failed:", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ts != 0 {
|
||||
|
@ -169,7 +157,7 @@ func (m *Message) Scan(row dbutil.Scannable) *Message {
|
|||
m.EditTimestamp = time.Unix(0, editTS).UTC()
|
||||
}
|
||||
|
||||
return m
|
||||
return m, nil
|
||||
}
|
||||
|
||||
const messageInsertQuery = `
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
|
@ -5,7 +21,6 @@ import (
|
|||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"go.mau.fi/util/dbutil"
|
||||
log "maunium.net/go/maulogger/v2"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
|
@ -39,15 +54,11 @@ func (key PortalKey) String() string {
|
|||
}
|
||||
|
||||
type PortalQuery struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
*dbutil.QueryHelper[*Portal]
|
||||
}
|
||||
|
||||
func (pq *PortalQuery) New() *Portal {
|
||||
return &Portal{
|
||||
db: pq.db,
|
||||
log: pq.log,
|
||||
}
|
||||
func newPortal(qh *dbutil.QueryHelper[*Portal]) *Portal {
|
||||
return &Portal{qh: qh}
|
||||
}
|
||||
|
||||
func (pq *PortalQuery) GetAll() []*Portal {
|
||||
|
@ -100,8 +111,7 @@ func (pq *PortalQuery) get(query string, args ...interface{}) *Portal {
|
|||
}
|
||||
|
||||
type Portal struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
qh *dbutil.QueryHelper[*Portal]
|
||||
|
||||
Key PortalKey
|
||||
Type discordgo.ChannelType
|
||||
|
@ -129,22 +139,15 @@ type Portal struct {
|
|||
RelayWebhookSecret string
|
||||
}
|
||||
|
||||
func (p *Portal) Scan(row dbutil.Scannable) *Portal {
|
||||
func (p *Portal) Scan(row dbutil.Scannable) (*Portal, error) {
|
||||
var otherUserID, guildID, parentID, mxid, firstEventID, relayWebhookID, relayWebhookSecret sql.NullString
|
||||
var chanType int32
|
||||
var avatarURL string
|
||||
|
||||
err := row.Scan(&p.Key.ChannelID, &p.Key.Receiver, &chanType, &otherUserID, &guildID, &parentID,
|
||||
&mxid, &p.PlainName, &p.Name, &p.NameSet, &p.FriendNick, &p.Topic, &p.TopicSet, &p.Avatar, &avatarURL, &p.AvatarSet,
|
||||
&p.Encrypted, &p.InSpace, &firstEventID, &relayWebhookID, &relayWebhookSecret)
|
||||
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
p.log.Errorln("Database scan failed:", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.MXID = id.RoomID(mxid.String)
|
||||
|
@ -156,8 +159,7 @@ func (p *Portal) Scan(row dbutil.Scannable) *Portal {
|
|||
p.AvatarURL, _ = id.ParseContentURI(avatarURL)
|
||||
p.RelayWebhookID = relayWebhookID.String
|
||||
p.RelayWebhookSecret = relayWebhookSecret.String
|
||||
|
||||
return p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Portal) Insert() {
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
log "maunium.net/go/maulogger/v2"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
|
@ -15,15 +30,11 @@ const (
|
|||
)
|
||||
|
||||
type PuppetQuery struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
qh *dbutil.QueryHelper[*Puppet]
|
||||
}
|
||||
|
||||
func (pq *PuppetQuery) New() *Puppet {
|
||||
return &Puppet{
|
||||
db: pq.db,
|
||||
log: pq.log,
|
||||
}
|
||||
func newPuppet(qh *dbutil.QueryHelper[*Puppet]) *Puppet {
|
||||
return &Puppet{qh: qh}
|
||||
}
|
||||
|
||||
func (pq *PuppetQuery) Get(id string) *Puppet {
|
||||
|
@ -62,8 +73,7 @@ func (pq *PuppetQuery) getAll(query string, args ...interface{}) []*Puppet {
|
|||
}
|
||||
|
||||
type Puppet struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
qh *dbutil.QueryHelper[*Puppet]
|
||||
|
||||
ID string
|
||||
Name string
|
||||
|
@ -86,28 +96,20 @@ type Puppet struct {
|
|||
NextBatch string
|
||||
}
|
||||
|
||||
func (p *Puppet) Scan(row dbutil.Scannable) *Puppet {
|
||||
func (p *Puppet) Scan(row dbutil.Scannable) (*Puppet, error) {
|
||||
var avatarURL string
|
||||
var customMXID, accessToken, nextBatch sql.NullString
|
||||
|
||||
err := row.Scan(&p.ID, &p.Name, &p.NameSet, &p.Avatar, &avatarURL, &p.AvatarSet, &p.ContactInfoSet,
|
||||
&p.GlobalName, &p.Username, &p.Discriminator, &p.IsBot, &p.IsWebhook, &p.IsApplication, &customMXID, &accessToken, &nextBatch)
|
||||
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
p.log.Errorln("Database scan failed:", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.AvatarURL, _ = id.ParseContentURI(avatarURL)
|
||||
p.CustomMXID = id.UserID(customMXID.String)
|
||||
p.AccessToken = accessToken.String
|
||||
p.NextBatch = nextBatch.String
|
||||
|
||||
return p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Puppet) Insert() {
|
||||
|
|
|
@ -1,28 +1,36 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
log "maunium.net/go/maulogger/v2"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type ReactionQuery struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
*dbutil.QueryHelper[*Reaction]
|
||||
}
|
||||
|
||||
const (
|
||||
reactionSelect = "SELECT dc_chan_id, dc_chan_receiver, dc_msg_id, dc_sender, dc_emoji_name, dc_thread_id, mxid FROM reaction"
|
||||
)
|
||||
|
||||
func (rq *ReactionQuery) New() *Reaction {
|
||||
return &Reaction{
|
||||
db: rq.db,
|
||||
log: rq.log,
|
||||
}
|
||||
func newReaction(qh *dbutil.QueryHelper[*Reaction]) *Reaction {
|
||||
return &Reaction{qh: qh}
|
||||
}
|
||||
|
||||
func (rq *ReactionQuery) GetAllForMessage(key PortalKey, discordMessageID string) []*Reaction {
|
||||
|
@ -67,8 +75,7 @@ func (rq *ReactionQuery) get(query string, args ...interface{}) *Reaction {
|
|||
}
|
||||
|
||||
type Reaction struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
qh *dbutil.QueryHelper[*Reaction]
|
||||
|
||||
Channel PortalKey
|
||||
MessageID string
|
||||
|
@ -81,17 +88,8 @@ type Reaction struct {
|
|||
FirstAttachmentID string
|
||||
}
|
||||
|
||||
func (r *Reaction) Scan(row dbutil.Scannable) *Reaction {
|
||||
err := row.Scan(&r.Channel.ChannelID, &r.Channel.Receiver, &r.MessageID, &r.Sender, &r.EmojiName, &r.ThreadID, &r.MXID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
r.log.Errorln("Database scan failed:", err)
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return r
|
||||
func (r *Reaction) Scan(row dbutil.Scannable) (*Reaction, error) {
|
||||
return dbutil.ValueOrErr(r, row.Scan(&r.Channel.ChannelID, &r.Channel.Receiver, &r.MessageID, &r.Sender, &r.EmojiName, &r.ThreadID, &r.MXID))
|
||||
}
|
||||
|
||||
func (r *Reaction) DiscordProtoChannelID() string {
|
||||
|
|
|
@ -1,17 +1,30 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"go.mau.fi/util/dbutil"
|
||||
log "maunium.net/go/maulogger/v2"
|
||||
)
|
||||
|
||||
type RoleQuery struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
*dbutil.QueryHelper[*Role]
|
||||
}
|
||||
|
||||
// language=postgresql
|
||||
|
@ -27,11 +40,8 @@ const (
|
|||
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 newRole(qh *dbutil.QueryHelper[*Role]) *Role {
|
||||
return &Role{qh: qh}
|
||||
}
|
||||
|
||||
func (rq *RoleQuery) GetByID(guildID, dcid string) *Role {
|
||||
|
@ -66,27 +76,21 @@ func (rq *RoleQuery) GetAll(guildID string) []*Role {
|
|||
}
|
||||
|
||||
type Role struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
qh *dbutil.QueryHelper[*Role]
|
||||
|
||||
GuildID string
|
||||
|
||||
discordgo.Role
|
||||
}
|
||||
|
||||
func (r *Role) Scan(row dbutil.Scannable) *Role {
|
||||
func (r *Role) Scan(row dbutil.Scannable) (*Role, error) {
|
||||
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
|
||||
return nil, err
|
||||
}
|
||||
r.Icon = icon.String
|
||||
return r
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *Role) Upsert(txn dbutil.Execable) {
|
||||
|
|
|
@ -1,28 +1,36 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
log "maunium.net/go/maulogger/v2"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type ThreadQuery struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
*dbutil.QueryHelper[*Thread]
|
||||
}
|
||||
|
||||
const (
|
||||
threadSelect = "SELECT dcid, parent_chan_id, root_msg_dcid, root_msg_mxid, creation_notice_mxid FROM thread"
|
||||
)
|
||||
|
||||
func (tq *ThreadQuery) New() *Thread {
|
||||
return &Thread{
|
||||
db: tq.db,
|
||||
log: tq.log,
|
||||
}
|
||||
func newThread(qh *dbutil.QueryHelper[*Thread]) *Thread {
|
||||
return &Thread{qh: qh}
|
||||
}
|
||||
|
||||
func (tq *ThreadQuery) GetByDiscordID(discordID string) *Thread {
|
||||
|
@ -59,8 +67,7 @@ func (tq *ThreadQuery) GetByMatrixRootOrCreationNoticeMsg(mxid id.EventID) *Thre
|
|||
}
|
||||
|
||||
type Thread struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
qh *dbutil.QueryHelper[*Thread]
|
||||
|
||||
ID string
|
||||
ParentID string
|
||||
|
@ -71,16 +78,8 @@ type Thread struct {
|
|||
CreationNoticeMXID id.EventID
|
||||
}
|
||||
|
||||
func (t *Thread) Scan(row dbutil.Scannable) *Thread {
|
||||
err := row.Scan(&t.ID, &t.ParentID, &t.RootDiscordID, &t.RootMXID, &t.CreationNoticeMXID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
t.log.Errorln("Database scan failed:", err)
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return t
|
||||
func (t *Thread) Scan(row dbutil.Scannable) (*Thread, error) {
|
||||
return dbutil.ValueOrErr(t, row.Scan(&t.ID, &t.ParentID, &t.RootDiscordID, &t.RootMXID, &t.CreationNoticeMXID))
|
||||
}
|
||||
|
||||
func (t *Thread) Insert() {
|
||||
|
|
|
@ -1,23 +1,34 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
log "maunium.net/go/maulogger/v2"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type UserQuery struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
*dbutil.QueryHelper[*User]
|
||||
}
|
||||
|
||||
func (uq *UserQuery) New() *User {
|
||||
return &User{
|
||||
db: uq.db,
|
||||
log: uq.log,
|
||||
}
|
||||
func newUser(qh *dbutil.QueryHelper[*User]) *User {
|
||||
return &User{qh: qh}
|
||||
}
|
||||
|
||||
func (uq *UserQuery) GetByMXID(userID id.UserID) *User {
|
||||
|
@ -51,8 +62,7 @@ func (uq *UserQuery) GetAllWithToken() []*User {
|
|||
}
|
||||
|
||||
type User struct {
|
||||
db *Database
|
||||
log log.Logger
|
||||
qh *dbutil.QueryHelper[*User]
|
||||
|
||||
MXID id.UserID
|
||||
DiscordID string
|
||||
|
@ -64,22 +74,18 @@ type User struct {
|
|||
ReadStateVersion int
|
||||
}
|
||||
|
||||
func (u *User) Scan(row dbutil.Scannable) *User {
|
||||
func (u *User) Scan(row dbutil.Scannable) (*User, error) {
|
||||
var discordID, managementRoom, spaceRoom, dmSpaceRoom, discordToken sql.NullString
|
||||
err := row.Scan(&u.MXID, &discordID, &discordToken, &managementRoom, &spaceRoom, &dmSpaceRoom, &u.ReadStateVersion)
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
u.log.Errorln("Database scan failed:", err)
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
u.DiscordID = discordID.String
|
||||
u.DiscordToken = discordToken.String
|
||||
u.ManagementRoom = id.RoomID(managementRoom.String)
|
||||
u.SpaceRoom = id.RoomID(spaceRoom.String)
|
||||
u.DMSpaceRoom = id.RoomID(dmSpaceRoom.String)
|
||||
return u
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (u *User) Insert() {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
|
|
|
@ -25,10 +25,8 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
@ -112,11 +110,9 @@ func newDirectMediaAPI(br *DiscordBridge) *DirectMediaAPI {
|
|||
if dma.ks.WellKnownTarget == "" {
|
||||
dma.ks.WellKnownTarget = fmt.Sprintf("%s:443", dma.cfg.ServerName)
|
||||
}
|
||||
federationRouter := r.PathPrefix("/_matrix/federation").Subrouter()
|
||||
mediaRouter := r.PathPrefix("/_matrix/media").Subrouter()
|
||||
clientMediaRouter := r.PathPrefix("/_matrix/client/v1/media").Subrouter()
|
||||
var reqIDCounter atomic.Uint64
|
||||
middleware := func(next http.Handler) http.Handler {
|
||||
mediaRouter.Use(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
|
@ -128,37 +124,21 @@ func newDirectMediaAPI(br *DiscordBridge) *DirectMediaAPI {
|
|||
Logger()
|
||||
next.ServeHTTP(w, r.WithContext(log.WithContext(r.Context())))
|
||||
})
|
||||
}
|
||||
mediaRouter.Use(middleware)
|
||||
federationRouter.Use(middleware)
|
||||
clientMediaRouter.Use(middleware)
|
||||
})
|
||||
addRoutes := func(version string) {
|
||||
mediaRouter.HandleFunc("/"+version+"/download/{serverName}/{mediaID}", dma.DownloadMedia).Methods(http.MethodGet)
|
||||
mediaRouter.HandleFunc("/"+version+"/download/{serverName}/{mediaID}/{fileName}", dma.DownloadMedia).Methods(http.MethodGet)
|
||||
mediaRouter.HandleFunc("/"+version+"/thumbnail/{serverName}/{mediaID}", dma.DownloadMedia).Methods(http.MethodGet)
|
||||
mediaRouter.HandleFunc("/"+version+"/upload/{serverName}/{mediaID}", dma.UploadNotSupported).Methods(http.MethodPut)
|
||||
mediaRouter.HandleFunc("/"+version+"/upload", dma.UploadNotSupported).Methods(http.MethodPost)
|
||||
mediaRouter.HandleFunc("/"+version+"/create", dma.UploadNotSupported).Methods(http.MethodPost)
|
||||
mediaRouter.HandleFunc("/"+version+"/config", dma.UploadNotSupported).Methods(http.MethodGet)
|
||||
mediaRouter.HandleFunc("/"+version+"/preview_url", dma.PreviewURLNotSupported).Methods(http.MethodGet)
|
||||
}
|
||||
clientMediaRouter.HandleFunc("/download/{serverName}/{mediaID}", dma.DownloadMedia).Methods(http.MethodGet)
|
||||
clientMediaRouter.HandleFunc("/download/{serverName}/{mediaID}/{fileName}", dma.DownloadMedia).Methods(http.MethodGet)
|
||||
clientMediaRouter.HandleFunc("/thumbnail/{serverName}/{mediaID}", dma.DownloadMedia).Methods(http.MethodGet)
|
||||
clientMediaRouter.HandleFunc("/upload/{serverName}/{mediaID}", dma.UploadNotSupported).Methods(http.MethodPut)
|
||||
clientMediaRouter.HandleFunc("/upload", dma.UploadNotSupported).Methods(http.MethodPost)
|
||||
clientMediaRouter.HandleFunc("/create", dma.UploadNotSupported).Methods(http.MethodPost)
|
||||
clientMediaRouter.HandleFunc("/config", dma.UploadNotSupported).Methods(http.MethodGet)
|
||||
clientMediaRouter.HandleFunc("/preview_url", dma.PreviewURLNotSupported).Methods(http.MethodGet)
|
||||
addRoutes("v3")
|
||||
addRoutes("r0")
|
||||
addRoutes("v1")
|
||||
federationRouter.HandleFunc("/v1/media/download/{mediaID}", dma.DownloadMedia).Methods(http.MethodGet)
|
||||
federationRouter.HandleFunc("/v1/version", dma.ks.GetServerVersion).Methods(http.MethodGet)
|
||||
mediaRouter.NotFoundHandler = http.HandlerFunc(dma.UnknownEndpoint)
|
||||
mediaRouter.MethodNotAllowedHandler = http.HandlerFunc(dma.UnsupportedMethod)
|
||||
federationRouter.NotFoundHandler = http.HandlerFunc(dma.UnknownEndpoint)
|
||||
federationRouter.MethodNotAllowedHandler = http.HandlerFunc(dma.UnsupportedMethod)
|
||||
dma.ks.Register(r)
|
||||
|
||||
return dma
|
||||
|
@ -552,17 +532,14 @@ func (dma *DirectMediaAPI) proxyDownload(ctx context.Context, w http.ResponseWri
|
|||
func (dma *DirectMediaAPI) DownloadMedia(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
log := zerolog.Ctx(ctx)
|
||||
isNewFederation := strings.HasPrefix(r.URL.Path, "/_matrix/federation/v1/media/download/")
|
||||
vars := mux.Vars(r)
|
||||
if !isNewFederation && vars["serverName"] != dma.cfg.ServerName {
|
||||
if vars["serverName"] != dma.cfg.ServerName {
|
||||
jsonResponse(w, http.StatusNotFound, &mautrix.RespError{
|
||||
ErrCode: mautrix.MNotFound.ErrCode,
|
||||
Err: fmt.Sprintf("This is a Discord media proxy for %q, other media downloads are not available here", dma.cfg.ServerName),
|
||||
})
|
||||
return
|
||||
}
|
||||
// TODO check destination header in X-Matrix auth when isNewFederation
|
||||
|
||||
url, expiresAt, err := dma.getMediaURL(ctx, vars["mediaID"])
|
||||
if err != nil {
|
||||
var respError *RespError
|
||||
|
@ -579,36 +556,7 @@ func (dma *DirectMediaAPI) DownloadMedia(w http.ResponseWriter, r *http.Request)
|
|||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
if isNewFederation {
|
||||
mp := multipart.NewWriter(w)
|
||||
w.Header().Set("Content-Type", strings.Replace(mp.FormDataContentType(), "form-data", "mixed", 1))
|
||||
var metaPart io.Writer
|
||||
metaPart, err = mp.CreatePart(textproto.MIMEHeader{
|
||||
"Content-Type": {"application/json"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to create multipart metadata field")
|
||||
return
|
||||
}
|
||||
_, err = metaPart.Write([]byte(`{}`))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to write multipart metadata field")
|
||||
return
|
||||
}
|
||||
_, err = mp.CreatePart(textproto.MIMEHeader{
|
||||
"Location": {url},
|
||||
})
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to create multipart redirect field")
|
||||
return
|
||||
}
|
||||
err = mp.Close()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to close multipart writer")
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
// Proxy if the config allows proxying and the request doesn't allow redirects.
|
||||
// In any other case, redirect to the Discord CDN.
|
||||
|
|
16
discord.go
16
discord.go
|
@ -1,3 +1,19 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
homeserver:
|
||||
# The address that this appservice can use to connect to the homeserver.
|
||||
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).
|
||||
domain: example.com
|
||||
|
||||
|
@ -110,13 +113,6 @@ bridge:
|
|||
# If set to `never`, DM rooms will never have names and avatars set.
|
||||
private_chat_portal_meta: default
|
||||
|
||||
# Publicly accessible base URL that Discord can use to reach the bridge, used for avatars in relay mode.
|
||||
# If not set, avatars will not be bridged. Only the /mautrix-discord/avatar/{server}/{id}/{hash} endpoint is used on this address.
|
||||
# This should not have a trailing slash, the endpoint above will be appended to the provided address.
|
||||
public_address: null
|
||||
# A random key used to sign the avatar URLs. The bridge will only accept requests with a valid signature.
|
||||
avatar_proxy_key: generate
|
||||
|
||||
portal_message_buffer: 128
|
||||
|
||||
# Number of private channel portals to create on bridge startup.
|
||||
|
|
|
@ -30,7 +30,6 @@ import (
|
|||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"go.mau.fi/mautrix-discord/database"
|
||||
)
|
||||
|
@ -263,19 +262,11 @@ func (r *discordTagHTMLRenderer) renderDiscordMention(w util.BufWriter, source [
|
|||
}
|
||||
switch node := n.(type) {
|
||||
case *astDiscordUserMention:
|
||||
var mxid id.UserID
|
||||
var name string
|
||||
if puppet := node.portal.bridge.GetPuppetByID(strconv.FormatInt(node.id, 10)); puppet != nil {
|
||||
mxid = puppet.MXID
|
||||
name = puppet.Name
|
||||
}
|
||||
if user := node.portal.bridge.GetUserByID(strconv.FormatInt(node.id, 10)); user != nil {
|
||||
mxid = user.MXID
|
||||
if name == "" {
|
||||
name = user.MXID.Localpart()
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, `<a href="%s">%s</a>`, user.MXID.URI().MatrixToURL(), user.MXID)
|
||||
} else if puppet := node.portal.bridge.GetPuppetByID(strconv.FormatInt(node.id, 10)); puppet != nil {
|
||||
_, _ = fmt.Fprintf(w, `<a href="%s">%s</a>`, puppet.MXID.URI().MatrixToURL(), puppet.Name)
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, `<a href="%s">%s</a>`, mxid.URI().MatrixToURL(), name)
|
||||
return
|
||||
case *astDiscordRoleMention:
|
||||
role := node.portal.bridge.DB.Role.GetByID(node.portal.GuildID, strconv.FormatInt(node.id, 10))
|
||||
|
|
24
go.mod
24
go.mod
|
@ -10,15 +10,15 @@ require (
|
|||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/rs/zerolog v1.32.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/yuin/goldmark v1.6.0
|
||||
go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb
|
||||
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848
|
||||
golang.org/x/sync v0.5.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/yuin/goldmark v1.7.0
|
||||
go.mau.fi/util v0.4.1-0.20240311141448-53cb04950f7e
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
|
||||
golang.org/x/sync v0.6.0
|
||||
maunium.net/go/maulogger/v2 v2.4.1
|
||||
maunium.net/go/mautrix v0.16.3-0.20240712164054-e6046fbf432c
|
||||
maunium.net/go/mautrix v0.18.0-beta.1.0.20240312191539-8128b00e0082
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -27,17 +27,17 @@ require (
|
|||
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
|
||||
github.com/tidwall/gjson v1.17.0 // indirect
|
||||
github.com/tidwall/gjson v1.17.1 // 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
|
||||
go.mau.fi/zeroconfig v0.1.2 // indirect
|
||||
golang.org/x/crypto v0.15.0 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sys v0.18.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
|
||||
)
|
||||
|
||||
replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20231013182643-f333f2578a3c
|
||||
replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20240312193245-78a9f3937dd2
|
||||
|
|
52
go.sum
52
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-20231013182643-f333f2578a3c h1:WaJ9eX8eyOBHD8te5t7xzm27uwhfaN94o8vUVFXliyA=
|
||||
github.com/beeper/discordgo v0.0.0-20231013182643-f333f2578a3c/go.mod h1:59+AOzzjmL6onAh62nuLXmn7dJCaC/owDLWbGtjTcFA=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/beeper/discordgo v0.0.0-20240312193245-78a9f3937dd2 h1:VEuPDZd9xUL+yNkqG4WnQrqKRiVdpXpK2wLPo07CKM8=
|
||||
github.com/beeper/discordgo v0.0.0-20240312193245-78a9f3937dd2/go.mod h1:59+AOzzjmL6onAh62nuLXmn7dJCaC/owDLWbGtjTcFA=
|
||||
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=
|
||||
|
@ -28,40 +28,40 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||
github.com/tidwall/gjson v1.17.1/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=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
|
||||
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb h1:Is+6vDKgINRy9KHodvi7NElxoDaWA8sc2S3cF3+QWjs=
|
||||
go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb/go.mod h1:tiBX6nxVSOjU89jVQ7wBh3P8KjM26Lv1k7/I5QdSvBw=
|
||||
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
|
||||
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
go.mau.fi/util v0.4.1-0.20240311141448-53cb04950f7e h1:e1jDj/MjleSS5r9DMRbuCZYKy5Rr+sbsu8eWjtLqrGk=
|
||||
go.mau.fi/util v0.4.1-0.20240311141448-53cb04950f7e/go.mod h1:jOAREC/go8T6rGic01cu6WRa90xi9U4z3QmDjRf8xpo=
|
||||
go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto=
|
||||
go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE=
|
||||
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
|
@ -72,5 +72,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/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
|
||||
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
|
||||
maunium.net/go/mautrix v0.16.3-0.20240712164054-e6046fbf432c h1:LHjqti3fFzrC8LXkkxxKYlLbuI/CJcwa2JN4Ppg2GK0=
|
||||
maunium.net/go/mautrix v0.16.3-0.20240712164054-e6046fbf432c/go.mod h1:gCgLw/4c1a8QsiOWTdUdXlt5cYdE0rJ9wLeZQKPD58Q=
|
||||
maunium.net/go/mautrix v0.18.0-beta.1.0.20240312191539-8128b00e0082 h1:63D5huHDTcOCQ69HVkyuaKlp2/9fFmQcnGpiFddTq9c=
|
||||
maunium.net/go/mautrix v0.18.0-beta.1.0.20240312191539-8128b00e0082/go.mod h1:0sfLB2ejW+lhgio4UlZMmn5i9SuZ8mxFkonFSamrfTE=
|
||||
|
|
6
main.go
6
main.go
|
@ -18,7 +18,6 @@ package main
|
|||
|
||||
import (
|
||||
_ "embed"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"go.mau.fi/util/configupgrade"
|
||||
|
@ -106,9 +105,6 @@ func (br *DiscordBridge) Start() {
|
|||
if br.Config.Bridge.Provisioning.SharedSecret != "disable" {
|
||||
br.provisioning = newProvisioningAPI(br)
|
||||
}
|
||||
if br.Config.Bridge.PublicAddress != "" {
|
||||
br.AS.Router.HandleFunc("/mautrix-discord/avatar/{server}/{mediaID}/{checksum}", br.serveMediaProxy).Methods(http.MethodGet)
|
||||
}
|
||||
br.DMA = newDirectMediaAPI(br)
|
||||
br.WaitWebsocketConnected()
|
||||
go br.startUsers()
|
||||
|
@ -185,7 +181,7 @@ func main() {
|
|||
Name: "mautrix-discord",
|
||||
URL: "https://github.com/mautrix/discord",
|
||||
Description: "A Matrix-Discord puppeting bridge.",
|
||||
Version: "0.7.0",
|
||||
Version: "0.6.5",
|
||||
ProtocolName: "Discord",
|
||||
BeeperServiceName: "discordgo",
|
||||
BeeperNetworkName: "discord",
|
||||
|
|
102
portal.go
102
portal.go
|
@ -1,15 +1,27 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
@ -21,7 +33,6 @@ import (
|
|||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/exsync"
|
||||
"go.mau.fi/util/variationselector"
|
||||
|
@ -464,7 +475,7 @@ func (portal *Portal) CreateMatrixRoom(user *User, channel *discordgo.Channel) e
|
|||
Content: event.Content{Parsed: &event.JoinRulesEventContent{
|
||||
JoinRule: event.JoinRuleRestricted,
|
||||
Allow: []event.JoinRuleAllow{{
|
||||
RoomID: portal.Guild.MXID,
|
||||
RoomID: spaceID,
|
||||
Type: event.JoinRuleAllowRoomMembership,
|
||||
}},
|
||||
}},
|
||||
|
@ -524,7 +535,7 @@ func (portal *Portal) CreateMatrixRoom(user *User, channel *discordgo.Channel) e
|
|||
if portal.GuildID == "" {
|
||||
user.addPrivateChannelToSpace(portal)
|
||||
} else {
|
||||
portal.updateSpace(user)
|
||||
portal.updateSpace()
|
||||
}
|
||||
portal.ensureUserInvited(user, true)
|
||||
user.syncChatDoublePuppetDetails(portal, true)
|
||||
|
@ -1375,64 +1386,6 @@ func (portal *Portal) sendMessageMetrics(evt *event.Event, err error, part strin
|
|||
}
|
||||
}
|
||||
|
||||
func (br *DiscordBridge) serveMediaProxy(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
mxc := id.ContentURI{
|
||||
Homeserver: vars["server"],
|
||||
FileID: vars["mediaID"],
|
||||
}
|
||||
checksum, err := base64.RawURLEncoding.DecodeString(vars["checksum"])
|
||||
if err != nil || len(checksum) != 32 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
_, expectedChecksum := br.hashMediaProxyURL(mxc)
|
||||
if !hmac.Equal(checksum, expectedChecksum) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
reader, err := br.Bot.Download(mxc)
|
||||
if err != nil {
|
||||
br.ZLog.Warn().Err(err).Msg("Failed to download media to proxy")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
buf := make([]byte, 32*1024)
|
||||
n, err := io.ReadFull(reader, buf)
|
||||
if err != nil && (!errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF)) {
|
||||
br.ZLog.Warn().Err(err).Msg("Failed to read first part of media to proxy")
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", http.DetectContentType(buf[:n]))
|
||||
if n < len(buf) {
|
||||
w.Header().Add("Content-Length", strconv.Itoa(n))
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err = w.Write(buf[:n])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n >= len(buf) {
|
||||
_, _ = io.CopyBuffer(w, reader, buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (br *DiscordBridge) hashMediaProxyURL(mxc id.ContentURI) (string, []byte) {
|
||||
path := fmt.Sprintf("/mautrix-discord/avatar/%s/%s/", mxc.Homeserver, mxc.FileID)
|
||||
checksum := hmac.New(sha256.New, []byte(br.Config.Bridge.AvatarProxyKey))
|
||||
checksum.Write([]byte(path))
|
||||
return path, checksum.Sum(nil)
|
||||
}
|
||||
|
||||
func (br *DiscordBridge) makeMediaProxyURL(mxc id.ContentURI) string {
|
||||
if br.Config.Bridge.PublicAddress == "" {
|
||||
return ""
|
||||
}
|
||||
path, checksum := br.hashMediaProxyURL(mxc)
|
||||
return br.Config.Bridge.PublicAddress + path + base64.RawURLEncoding.EncodeToString(checksum)
|
||||
}
|
||||
|
||||
func (portal *Portal) getRelayUserMeta(sender *User) (name, avatarURL string) {
|
||||
member := portal.bridge.StateStore.GetMember(portal.MXID, sender.MXID)
|
||||
name = member.Displayname
|
||||
|
@ -1440,8 +1393,11 @@ func (portal *Portal) getRelayUserMeta(sender *User) (name, avatarURL string) {
|
|||
name = sender.MXID.String()
|
||||
}
|
||||
mxc := member.AvatarURL.ParseOrIgnore()
|
||||
if !mxc.IsEmpty() && portal.bridge.Config.Bridge.PublicAddress != "" {
|
||||
avatarURL = portal.bridge.makeMediaProxyURL(mxc)
|
||||
if !mxc.IsEmpty() {
|
||||
avatarURL = mautrix.BuildURL(
|
||||
portal.bridge.PublicHSAddress,
|
||||
"_matrix", "media", "v3", "download", mxc.Homeserver, mxc.FileID,
|
||||
).String()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -2381,19 +2337,11 @@ func (portal *Portal) ExpectedSpaceID() id.RoomID {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (portal *Portal) updateSpace(source *User) bool {
|
||||
func (portal *Portal) updateSpace() bool {
|
||||
if portal.MXID == "" {
|
||||
return false
|
||||
}
|
||||
if portal.Parent != nil {
|
||||
if portal.Parent.MXID != "" {
|
||||
portal.log.Warn().Str("parent_id", portal.ParentID).Msg("Parent portal has no Matrix room, creating...")
|
||||
err := portal.Parent.CreateMatrixRoom(source, nil)
|
||||
if err != nil {
|
||||
portal.log.Err(err).Str("parent_id", portal.ParentID).Msg("Failed to create Matrix room for parent")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return portal.addToSpace(portal.Parent.MXID)
|
||||
} else if portal.Guild != nil {
|
||||
return portal.addToSpace(portal.Guild.MXID)
|
||||
|
@ -2484,7 +2432,7 @@ func (portal *Portal) UpdateInfo(source *User, meta *discordgo.Channel) *discord
|
|||
changed = portal.UpdateParent(meta.ParentID) || changed
|
||||
// Private channels are added to the space in User.handlePrivateChannel
|
||||
if portal.GuildID != "" && portal.MXID != "" && portal.ExpectedSpaceID() != portal.InSpace {
|
||||
changed = portal.updateSpace(source) || changed
|
||||
changed = portal.updateSpace() || changed
|
||||
}
|
||||
if changed {
|
||||
portal.UpdateBridgeInfo()
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
16
puppet.go
16
puppet.go
|
@ -1,3 +1,19 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
16
thread.go
16
thread.go
|
@ -1,3 +1,19 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
16
user.go
16
user.go
|
@ -1,3 +1,19 @@
|
|||
// mautrix-discord - A Matrix-Discord puppeting bridge.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
Loading…
Reference in a new issue