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.
171 lines
4.2 KiB
Go
171 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
|
|
"maunium.net/go/mautrix"
|
|
"maunium.net/go/mautrix/appservice"
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
type Bridge struct {
|
|
as *appservice.AppService
|
|
db *Database
|
|
ntfy *NtfyClient
|
|
cfg *Config
|
|
|
|
mu sync.Mutex
|
|
listeners map[string]context.CancelFunc
|
|
}
|
|
|
|
func NewBridge(as *appservice.AppService, db *Database, ntfy *NtfyClient, cfg *Config) *Bridge {
|
|
return &Bridge{
|
|
as: as,
|
|
db: db,
|
|
ntfy: ntfy,
|
|
cfg: cfg,
|
|
listeners: make(map[string]context.CancelFunc),
|
|
}
|
|
}
|
|
|
|
func (b *Bridge) StartExistingBridges(ctx context.Context) {
|
|
bridges, err := b.db.GetAllBridges()
|
|
if err != nil {
|
|
log.Fatalf("Failed to load bridges: %v", err)
|
|
}
|
|
|
|
botIntent := b.as.BotIntent()
|
|
for topic, roomID := range bridges {
|
|
log.Printf("[startup] Restoring listener: topic=%q room=%s", topic, roomID)
|
|
b.startListener(topic, roomID, botIntent)
|
|
}
|
|
|
|
log.Printf("[startup] Restored %d bridge(s)", len(bridges))
|
|
}
|
|
|
|
func (b *Bridge) CreateBridge(ctx context.Context, sender id.UserID, topicName string) (id.RoomID, error) {
|
|
botIntent := b.as.BotIntent()
|
|
|
|
if existing, err := b.db.GetBridgeByTopic(topicName); err != nil {
|
|
return "", fmt.Errorf("database error: %w", err)
|
|
} else if existing != "" {
|
|
return "", fmt.Errorf("topic '%s' is already bridged to %s", topicName, existing)
|
|
}
|
|
|
|
spaceRoomID, err := b.ensureSpace(ctx, sender)
|
|
if err != nil {
|
|
return "", fmt.Errorf("space setup: %w", err)
|
|
}
|
|
|
|
roomResp, err := botIntent.Client.CreateRoom(ctx, &mautrix.ReqCreateRoom{
|
|
Name: topicName,
|
|
Topic: fmt.Sprintf("ntfy topic: %s", topicName),
|
|
Preset: "private_chat",
|
|
Invite: []id.UserID{sender},
|
|
})
|
|
if err != nil {
|
|
return "", fmt.Errorf("create room: %w", err)
|
|
}
|
|
bridgeRoomID := roomResp.RoomID
|
|
|
|
if _, err := botIntent.SendStateEvent(ctx, id.RoomID(spaceRoomID), event.StateSpaceChild, string(bridgeRoomID), &event.SpaceChildEventContent{
|
|
Via: []string{b.cfg.Homeserver.Domain},
|
|
}); err != nil {
|
|
log.Printf("[bridge] failed to add room to space: %v", err)
|
|
}
|
|
|
|
userIntent := b.as.Intent(sender)
|
|
userIntent.IsCustomPuppet = true
|
|
if err := userIntent.EnsureJoined(ctx, bridgeRoomID); err != nil {
|
|
log.Printf("[bridge] double puppet join failed: %v", err)
|
|
}
|
|
|
|
if err := b.db.SetBridge(topicName, string(bridgeRoomID)); err != nil {
|
|
return "", fmt.Errorf("save bridge: %w", err)
|
|
}
|
|
|
|
b.startListener(topicName, string(bridgeRoomID), botIntent)
|
|
|
|
return bridgeRoomID, nil
|
|
}
|
|
|
|
func (b *Bridge) DeleteBridge(topicName string) error {
|
|
b.mu.Lock()
|
|
if cancel, ok := b.listeners[topicName]; ok {
|
|
cancel()
|
|
delete(b.listeners, topicName)
|
|
}
|
|
b.mu.Unlock()
|
|
|
|
return b.db.DeleteBridge(topicName)
|
|
}
|
|
|
|
func (b *Bridge) PublishToNtfy(topic, message string) error {
|
|
return b.ntfy.PublishToNtfy(topic, message)
|
|
}
|
|
|
|
func (b *Bridge) GetBridgeByRoom(roomID string) (string, error) {
|
|
return b.db.GetBridgeByRoom(roomID)
|
|
}
|
|
|
|
func (b *Bridge) GetAllBridges() (map[string]string, error) {
|
|
return b.db.GetAllBridges()
|
|
}
|
|
|
|
func (b *Bridge) HasPermission(userID string) bool {
|
|
return b.cfg.Bridge.HasPermission(userID, "admin")
|
|
}
|
|
|
|
func (b *Bridge) startListener(topic, roomID string, intent *appservice.IntentAPI) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
if _, exists := b.listeners[topic]; exists {
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
b.listeners[topic] = cancel
|
|
|
|
go b.ntfy.Listen(ctx, topic, roomID, intent)
|
|
}
|
|
|
|
func (b *Bridge) ensureSpace(ctx context.Context, sender id.UserID) (string, error) {
|
|
spaceRoomID, err := b.db.GetSpaceRoomID(string(sender))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if spaceRoomID != "" {
|
|
return spaceRoomID, nil
|
|
}
|
|
|
|
resp, err := b.as.BotIntent().Client.CreateRoom(ctx, &mautrix.ReqCreateRoom{
|
|
Name: "Notifications",
|
|
Preset: "private_chat",
|
|
CreationContent: map[string]interface{}{
|
|
"type": "m.space",
|
|
},
|
|
Invite: []id.UserID{sender},
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
spaceRoomID = string(resp.RoomID)
|
|
|
|
if err := b.db.SetSpaceRoomID(string(sender), spaceRoomID); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
userIntent := b.as.Intent(sender)
|
|
userIntent.IsCustomPuppet = true
|
|
if err := userIntent.EnsureJoined(ctx, id.RoomID(spaceRoomID)); err != nil {
|
|
log.Printf("[bridge] double puppet join space failed: %v", err)
|
|
}
|
|
|
|
return spaceRoomID, nil
|
|
}
|