Republish Discord announcements to Abner's Matrix server
This commit is contained in:
parent
594860a080
commit
76be9b668a
|
@ -28,6 +28,7 @@ type HMNConfig struct {
|
|||
DigitalOcean DigitalOceanConfig
|
||||
Discord DiscordConfig
|
||||
Twitch TwitchConfig
|
||||
Matrix MatrixConfig
|
||||
EpisodeGuide EpisodeGuide
|
||||
DevConfig DevConfig
|
||||
PreviewGeneration PreviewGenerationConfig
|
||||
|
@ -93,6 +94,13 @@ type TwitchConfig struct {
|
|||
BaseIDUrl string
|
||||
}
|
||||
|
||||
type MatrixConfig struct {
|
||||
Username string
|
||||
Password string
|
||||
BaseUrl string
|
||||
AnnouncementsRoomID string
|
||||
}
|
||||
|
||||
type EpisodeGuide struct {
|
||||
CineraOutputPath string
|
||||
Projects map[string]string // NOTE(asaf): Maps from slugs to default episode guide topic
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
package discord
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -19,6 +23,7 @@ import (
|
|||
"git.handmade.network/hmn/hmn/src/models"
|
||||
"git.handmade.network/hmn/hmn/src/oops"
|
||||
"git.handmade.network/hmn/hmn/src/parsing"
|
||||
"git.handmade.network/hmn/hmn/src/utils"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
|
@ -36,6 +41,10 @@ func HandleIncomingMessage(ctx context.Context, dbConn db.ConnOrTx, msg *Message
|
|||
deleted, err = CleanUpShowcase(ctx, dbConn, msg)
|
||||
}
|
||||
|
||||
if !deleted && err == nil {
|
||||
err = ShareToMatrix(ctx, msg)
|
||||
}
|
||||
|
||||
if !deleted && err == nil {
|
||||
err = MaybeInternMessage(ctx, dbConn, msg)
|
||||
}
|
||||
|
@ -137,6 +146,97 @@ func CleanUpLibrary(ctx context.Context, dbConn db.ConnOrTx, msg *Message) (bool
|
|||
return deleted, nil
|
||||
}
|
||||
|
||||
func ShareToMatrix(ctx context.Context, msg *Message) error {
|
||||
if msg.Flags&MessageFlagCrossposted == 0 {
|
||||
return nil
|
||||
}
|
||||
if config.Config.Matrix.Username == "" {
|
||||
logging.ExtractLogger(ctx).Warn().Msg("No Matrix user provided; Discord announcement will not be shared")
|
||||
}
|
||||
|
||||
fullMsg, err := GetChannelMessage(ctx, msg.ChannelID, msg.ID)
|
||||
if err != nil {
|
||||
return oops.New(err, "failed to get published message contents")
|
||||
}
|
||||
|
||||
bodyMarkdown := CleanUpMarkdown(ctx, fullMsg.Content)
|
||||
bodyHTML := parsing.ParseMarkdown(bodyMarkdown, parsing.DiscordMarkdown)
|
||||
|
||||
// Log in to Matrix (we don't bother to keep access tokens around)
|
||||
var accessToken string
|
||||
{
|
||||
type MatrixLogin struct {
|
||||
Type string `json:"type"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
type MatrixLoginResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
body := MatrixLogin{
|
||||
Type: "m.login.password",
|
||||
User: config.Config.Matrix.Username,
|
||||
Password: config.Config.Matrix.Password,
|
||||
}
|
||||
bodyBytes := utils.Must1(json.Marshal(body))
|
||||
res, err := http.Post(
|
||||
"https://matrix.handmadecities.com/_matrix/client/r0/login",
|
||||
"application/json",
|
||||
bytes.NewReader(bodyBytes),
|
||||
)
|
||||
if err != nil || res.StatusCode >= 300 {
|
||||
return oops.New(err, "failed to log into Matrix")
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resBodyBytes := utils.Must1(io.ReadAll(res.Body))
|
||||
var resBody MatrixLoginResponse
|
||||
utils.Must(json.Unmarshal(resBodyBytes, &resBody))
|
||||
|
||||
accessToken = resBody.AccessToken
|
||||
}
|
||||
|
||||
// Create message
|
||||
{
|
||||
type MessageEvent struct {
|
||||
MsgType string `json:"msgtype"`
|
||||
Body string `json:"body"`
|
||||
Format string `json:"format,omitempty"`
|
||||
FormattedBody string `json:"formatted_body,omitempty"`
|
||||
}
|
||||
tid := "hmn" + strconv.Itoa(rand.Int())
|
||||
body := MessageEvent{
|
||||
MsgType: "m.text",
|
||||
Body: bodyMarkdown,
|
||||
Format: "org.matrix.custom.html",
|
||||
FormattedBody: bodyHTML,
|
||||
}
|
||||
bodyBytes := utils.Must1(json.Marshal(body))
|
||||
req := utils.Must1(http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPut,
|
||||
fmt.Sprintf(
|
||||
"%s/_matrix/client/v3/rooms/%s/send/m.room.message/%s",
|
||||
config.Config.Matrix.BaseUrl,
|
||||
config.Config.Matrix.AnnouncementsRoomID,
|
||||
tid,
|
||||
),
|
||||
bytes.NewReader(bodyBytes),
|
||||
))
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil || res.StatusCode >= 300 {
|
||||
return oops.New(err, "failed to send Matrix message")
|
||||
}
|
||||
}
|
||||
|
||||
logging.ExtractLogger(ctx).Info().
|
||||
Str("contents", bodyMarkdown).
|
||||
Msg("Published Discord announcement to Matrix")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func MaybeInternMessage(ctx context.Context, dbConn db.ConnOrTx, msg *Message) error {
|
||||
if msg.ChannelID == config.Config.Discord.ShowcaseChannelID {
|
||||
err := InternMessage(ctx, dbConn, msg)
|
||||
|
|
|
@ -244,15 +244,32 @@ const (
|
|||
MessageTypeGuildInviteReminder MessageType = 22
|
||||
)
|
||||
|
||||
type MessageFlags int
|
||||
|
||||
const (
|
||||
MessageFlagCrossposted MessageFlags = 1 << iota
|
||||
MessageFlagIsCrosspost
|
||||
MessageFlagSuppressEmbeds
|
||||
MessageFlagSourceMessageDeleted
|
||||
MessageFlagUrgent
|
||||
MessageFlagHasThread
|
||||
MessageFlagEphemeral
|
||||
MessageFlagLoading
|
||||
MessageFlagFailedToMentionSomeRolesInThread
|
||||
MessageFlagSuppressNotifications
|
||||
MessageFlagIsVoiceMessage
|
||||
)
|
||||
|
||||
// https://discord.com/developers/docs/resources/channel#message-object
|
||||
type Message struct {
|
||||
ID string `json:"id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
GuildID *string `json:"guild_id"`
|
||||
Content string `json:"content"`
|
||||
Author *User `json:"author"` // note that this may not be an actual valid user (see the docs)
|
||||
Timestamp string `json:"timestamp"`
|
||||
Type MessageType `json:"type"`
|
||||
ID string `json:"id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
GuildID *string `json:"guild_id"`
|
||||
Content string `json:"content"`
|
||||
Author *User `json:"author"` // note that this may not be an actual valid user (see the docs)
|
||||
Timestamp string `json:"timestamp"`
|
||||
Type MessageType `json:"type"`
|
||||
Flags MessageFlags `json:"flags"`
|
||||
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
Embeds []Embed `json:"embeds"`
|
||||
|
@ -317,6 +334,7 @@ func MessageFromMap(m interface{}, k string) *Message {
|
|||
Author: UserFromMap(m, "author"),
|
||||
Timestamp: maybeString(mmap, "timestamp"),
|
||||
Type: MessageType(maybeInt(mmap, "type")),
|
||||
Flags: MessageFlags(maybeInt(mmap, "flags")),
|
||||
|
||||
originalMap: mmap,
|
||||
}
|
||||
|
@ -1003,3 +1021,11 @@ func maybeBoolP(m map[string]interface{}, k string) *bool {
|
|||
boolval := val.(bool)
|
||||
return &boolval
|
||||
}
|
||||
|
||||
func maybeArray(m map[string]any, k string) []any {
|
||||
val, ok := m[k]
|
||||
if !ok || val == nil {
|
||||
return nil
|
||||
}
|
||||
return val.([]any)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue