6756b9f179
Build and Publish Package (amd64) / docker (push) Successful in 4m54s
This is vibecoded shit but i can't be bothered to do it myself. Nobody else besides me is gonna be using this, and this wont ever be extended or anything, so I just don't care.
186 lines
4.6 KiB
Go
186 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
type MatrixHandler struct {
|
|
bridge *Bridge
|
|
ghostPrefix string
|
|
}
|
|
|
|
func NewMatrixHandler(bridge *Bridge, ghostPrefix string) *MatrixHandler {
|
|
return &MatrixHandler{
|
|
bridge: bridge,
|
|
ghostPrefix: ghostPrefix,
|
|
}
|
|
}
|
|
|
|
func (h *MatrixHandler) HandleEvent(ctx context.Context, evt *event.Event) {
|
|
if evt.Sender == h.bridge.as.BotMXID() {
|
|
return
|
|
}
|
|
if h.ghostPrefix != "" && strings.HasPrefix(string(evt.Sender), h.ghostPrefix) {
|
|
return
|
|
}
|
|
|
|
switch evt.Type {
|
|
case event.EventMessage:
|
|
h.onMessage(ctx, evt)
|
|
case event.StateMember:
|
|
h.onMembership(ctx, evt)
|
|
}
|
|
}
|
|
|
|
func (h *MatrixHandler) onMessage(ctx context.Context, evt *event.Event) {
|
|
msg := evt.Content.AsMessage()
|
|
if msg == nil {
|
|
return
|
|
}
|
|
|
|
text := strings.TrimSpace(msg.Body)
|
|
if text == "" {
|
|
return
|
|
}
|
|
|
|
cmd, arg := parseCommand(text)
|
|
|
|
switch cmd {
|
|
case "help", "":
|
|
h.cmdHelp(ctx, evt)
|
|
|
|
case "list":
|
|
h.cmdList(ctx, evt)
|
|
|
|
case "create":
|
|
h.cmdCreate(ctx, evt, arg)
|
|
|
|
case "delete":
|
|
h.cmdDelete(ctx, evt, arg)
|
|
|
|
default:
|
|
if topic, _ := h.bridge.GetBridgeByRoom(string(evt.RoomID)); topic != "" {
|
|
h.forwardToNtfy(ctx, evt, topic)
|
|
} else {
|
|
h.reply(ctx, evt.RoomID, "Type 'help' to see available commands.")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *MatrixHandler) onMembership(ctx context.Context, evt *event.Event) {
|
|
member := evt.Content.AsMember()
|
|
if member == nil {
|
|
return
|
|
}
|
|
|
|
stateKey := evt.GetStateKey()
|
|
if stateKey == "" {
|
|
return
|
|
}
|
|
|
|
if member.Membership == event.MembershipInvite && id.UserID(stateKey) == h.bridge.as.BotMXID() {
|
|
if err := h.bridge.as.BotIntent().EnsureJoined(ctx, evt.RoomID); err != nil {
|
|
log.Printf("[matrix] auto-join room %s failed: %v", evt.RoomID, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *MatrixHandler) cmdHelp(ctx context.Context, evt *event.Event) {
|
|
h.reply(ctx, evt.RoomID,
|
|
"ntfy-Matrix Bridge\n\n"+
|
|
" bridge create <topic> — Bridge an ntfy topic to a new room\n"+
|
|
" bridge list — List all active bridges\n"+
|
|
" bridge delete <topic> — Remove a bridge\n"+
|
|
" help — Show this message\n\n"+
|
|
"Bridged rooms forward messages between Matrix and ntfy.",
|
|
)
|
|
}
|
|
|
|
func (h *MatrixHandler) cmdList(ctx context.Context, evt *event.Event) {
|
|
bridges, err := h.bridge.GetAllBridges()
|
|
if err != nil {
|
|
log.Printf("[matrix] list bridges: %v", err)
|
|
h.reply(ctx, evt.RoomID, "Database error.")
|
|
return
|
|
}
|
|
if len(bridges) == 0 {
|
|
h.reply(ctx, evt.RoomID, "No active bridges.")
|
|
return
|
|
}
|
|
|
|
var b strings.Builder
|
|
b.WriteString("Active bridges:\n")
|
|
for topic, roomID := range bridges {
|
|
fmt.Fprintf(&b, " %s → %s\n", topic, roomID)
|
|
}
|
|
h.reply(ctx, evt.RoomID, strings.TrimRight(b.String(), "\n"))
|
|
}
|
|
|
|
func (h *MatrixHandler) cmdCreate(ctx context.Context, evt *event.Event, topicName string) {
|
|
if topicName == "" {
|
|
h.reply(ctx, evt.RoomID, "Usage: bridge create <topic_name>")
|
|
return
|
|
}
|
|
if !h.bridge.HasPermission(string(evt.Sender)) {
|
|
h.reply(ctx, evt.RoomID, "Admin access required.")
|
|
return
|
|
}
|
|
|
|
roomID, err := h.bridge.CreateBridge(ctx, evt.Sender, topicName)
|
|
if err != nil {
|
|
h.reply(ctx, evt.RoomID, fmt.Sprintf("Failed: %v", err))
|
|
return
|
|
}
|
|
|
|
h.reply(ctx, evt.RoomID, fmt.Sprintf("Bridge created!\n Topic: %s\n Room: %s", topicName, roomID))
|
|
}
|
|
|
|
func (h *MatrixHandler) cmdDelete(ctx context.Context, evt *event.Event, topicName string) {
|
|
if topicName == "" {
|
|
h.reply(ctx, evt.RoomID, "Usage: bridge delete <topic_name>")
|
|
return
|
|
}
|
|
if !h.bridge.HasPermission(string(evt.Sender)) {
|
|
h.reply(ctx, evt.RoomID, "Admin access required.")
|
|
return
|
|
}
|
|
|
|
if err := h.bridge.DeleteBridge(topicName); err != nil {
|
|
log.Printf("[matrix] delete bridge: %v", err)
|
|
h.reply(ctx, evt.RoomID, "Failed to delete bridge.")
|
|
return
|
|
}
|
|
|
|
h.reply(ctx, evt.RoomID, fmt.Sprintf("Bridge for '%s' deleted.", topicName))
|
|
}
|
|
|
|
func (h *MatrixHandler) forwardToNtfy(ctx context.Context, evt *event.Event, topic string) {
|
|
if err := h.bridge.PublishToNtfy(topic, evt.Content.AsMessage().Body); err != nil {
|
|
log.Printf("[matrix] publish to ntfy: %v", err)
|
|
h.reply(ctx, evt.RoomID, fmt.Sprintf("Failed to publish: %v", err))
|
|
}
|
|
}
|
|
|
|
func (h *MatrixHandler) reply(ctx context.Context, roomID id.RoomID, text string) {
|
|
h.bridge.as.BotIntent().SendNotice(ctx, roomID, text)
|
|
}
|
|
|
|
func parseCommand(text string) (cmd, arg string) {
|
|
if !strings.HasPrefix(text, "bridge ") {
|
|
return "", text
|
|
}
|
|
rest := strings.TrimSpace(strings.TrimPrefix(text, "bridge "))
|
|
parts := strings.SplitN(rest, " ", 2)
|
|
cmd = parts[0]
|
|
if len(parts) > 1 {
|
|
arg = strings.TrimSpace(parts[1])
|
|
}
|
|
return
|
|
}
|