This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/appservice"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type MatrixHandler struct {
|
||||
AS *appservice.AppService
|
||||
DB *Database
|
||||
Ntfy *NtfyClient
|
||||
Config *BridgeConfig
|
||||
HSDomain string
|
||||
GhostPrefix string
|
||||
}
|
||||
|
||||
func (h *MatrixHandler) HandleEvent(ctx context.Context, evt *event.Event) {
|
||||
if evt.Sender == h.AS.BotMXID() {
|
||||
return
|
||||
}
|
||||
if h.GhostPrefix != "" && strings.HasPrefix(string(evt.Sender), h.GhostPrefix) {
|
||||
return
|
||||
}
|
||||
|
||||
switch evt.Type {
|
||||
case event.EventMessage:
|
||||
h.handleMessage(ctx, evt)
|
||||
case event.StateMember:
|
||||
h.handleMembership(ctx, evt)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *MatrixHandler) handleMessage(ctx context.Context, evt *event.Event) {
|
||||
msg := evt.Content.AsMessage()
|
||||
if msg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
text := strings.TrimSpace(msg.Body)
|
||||
|
||||
if strings.HasPrefix(text, "bridge ") {
|
||||
h.handleCommand(ctx, evt, text)
|
||||
return
|
||||
}
|
||||
|
||||
topic, err := h.DB.GetBridgeByRoom(string(evt.RoomID))
|
||||
if err != nil {
|
||||
log.Printf("[matrix] db lookup error for room %s: %v", evt.RoomID, err)
|
||||
return
|
||||
}
|
||||
if topic == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.Ntfy.PublishToNtfy(topic, text); err != nil {
|
||||
botIntent := h.AS.BotIntent()
|
||||
botIntent.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Failed to publish to ntfy: %v", err))
|
||||
log.Printf("[matrix] publish to ntfy failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *MatrixHandler) handleCommand(ctx context.Context, evt *event.Event, text string) {
|
||||
parts := strings.SplitN(text, " ", 3)
|
||||
if len(parts) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
switch parts[1] {
|
||||
case "create":
|
||||
topicName := ""
|
||||
if len(parts) > 2 {
|
||||
topicName = strings.TrimSpace(parts[2])
|
||||
}
|
||||
h.handleCreateBridge(ctx, evt, topicName)
|
||||
default:
|
||||
botIntent := h.AS.BotIntent()
|
||||
botIntent.SendNotice(ctx, evt.RoomID, "Available commands:\n bridge create <topic_name> — Bridge an ntfy topic to a new Matrix room")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *MatrixHandler) handleCreateBridge(ctx context.Context, evt *event.Event, topicName string) {
|
||||
botIntent := h.AS.BotIntent()
|
||||
|
||||
if topicName == "" {
|
||||
botIntent.SendNotice(ctx, evt.RoomID, "Usage: bridge create <topic_name>")
|
||||
return
|
||||
}
|
||||
|
||||
if !h.Config.HasPermission(string(evt.Sender), "admin") {
|
||||
botIntent.SendNotice(ctx, evt.RoomID, "You do not have permission to create bridges. Admin access required.")
|
||||
return
|
||||
}
|
||||
|
||||
existing, err := h.DB.GetBridgeByTopic(topicName)
|
||||
if err != nil {
|
||||
log.Printf("[matrix] db error checking topic: %v", err)
|
||||
botIntent.SendNotice(ctx, evt.RoomID, "Internal database error.")
|
||||
return
|
||||
}
|
||||
if existing != "" {
|
||||
botIntent.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Topic '%s' is already bridged to room %s.", topicName, existing))
|
||||
return
|
||||
}
|
||||
|
||||
spaceRoomID, err := h.DB.GetSpaceRoomID(string(evt.Sender))
|
||||
if err != nil {
|
||||
log.Printf("[matrix] db error checking space: %v", err)
|
||||
botIntent.SendNotice(ctx, evt.RoomID, "Internal database error.")
|
||||
return
|
||||
}
|
||||
|
||||
if spaceRoomID == "" {
|
||||
resp, err := botIntent.Client.CreateRoom(ctx, &mautrix.ReqCreateRoom{
|
||||
Name: "Notifications",
|
||||
Preset: "private_chat",
|
||||
CreationContent: map[string]interface{}{
|
||||
"type": "m.space",
|
||||
},
|
||||
Invite: []id.UserID{evt.Sender},
|
||||
})
|
||||
if err != nil {
|
||||
botIntent.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Failed to create Notifications space: %v", err))
|
||||
return
|
||||
}
|
||||
spaceRoomID = string(resp.RoomID)
|
||||
|
||||
if err := h.DB.SetSpaceRoomID(string(evt.Sender), spaceRoomID); err != nil {
|
||||
log.Printf("[matrix] failed to save space: %v", err)
|
||||
botIntent.SendNotice(ctx, evt.RoomID, "Failed to save space reference to database.")
|
||||
return
|
||||
}
|
||||
|
||||
userIntent := h.AS.Intent(evt.Sender)
|
||||
userIntent.IsCustomPuppet = true
|
||||
if err := userIntent.EnsureJoined(ctx, id.RoomID(spaceRoomID)); err != nil {
|
||||
log.Printf("[matrix] double puppet join space failed: %v", err)
|
||||
}
|
||||
|
||||
botIntent.SendNotice(ctx, evt.RoomID, "Created 'Notifications' space.")
|
||||
}
|
||||
|
||||
roomResp, err := botIntent.Client.CreateRoom(ctx, &mautrix.ReqCreateRoom{
|
||||
Name: topicName,
|
||||
Topic: fmt.Sprintf("ntfy topic: %s", topicName),
|
||||
Preset: "private_chat",
|
||||
Invite: []id.UserID{evt.Sender},
|
||||
})
|
||||
if err != nil {
|
||||
botIntent.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Failed to create bridged room: %v", err))
|
||||
return
|
||||
}
|
||||
bridgeRoomID := roomResp.RoomID
|
||||
|
||||
viaServers := []string{h.HSDomain}
|
||||
if _, err := botIntent.SendStateEvent(ctx, id.RoomID(spaceRoomID), event.StateSpaceChild, string(bridgeRoomID), &event.SpaceChildEventContent{
|
||||
Via: viaServers,
|
||||
}); err != nil {
|
||||
log.Printf("[matrix] failed to add room to space: %v", err)
|
||||
}
|
||||
|
||||
userIntent := h.AS.Intent(evt.Sender)
|
||||
userIntent.IsCustomPuppet = true
|
||||
if err := userIntent.EnsureJoined(ctx, bridgeRoomID); err != nil {
|
||||
log.Printf("[matrix] double puppet join bridge room failed: %v", err)
|
||||
}
|
||||
|
||||
if err := h.DB.SetBridge(topicName, string(bridgeRoomID)); err != nil {
|
||||
log.Printf("[matrix] failed to save bridge: %v", err)
|
||||
botIntent.SendNotice(ctx, evt.RoomID, "Bridge created but failed to save to database.")
|
||||
return
|
||||
}
|
||||
|
||||
h.Ntfy.StartNtfyListener(topicName, string(bridgeRoomID), h.AS.BotIntent(), h.AS.BotMXID())
|
||||
|
||||
botIntent.SendNotice(ctx, evt.RoomID, fmt.Sprintf(
|
||||
"Bridge created!\n• Topic: %s\n• Room: %s\nMessages sent in that room will be published to ntfy, and ntfy messages will appear there.",
|
||||
topicName, bridgeRoomID,
|
||||
))
|
||||
}
|
||||
|
||||
func (h *MatrixHandler) handleMembership(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.AS.BotMXID() {
|
||||
botIntent := h.AS.BotIntent()
|
||||
if err := botIntent.EnsureJoined(ctx, evt.RoomID); err != nil {
|
||||
log.Printf("[matrix] failed to auto-join room %s: %v", evt.RoomID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user