router.go raw
1 package bridge
2
3 import (
4 "context"
5
6 "next.orly.dev/pkg/lol/log"
7 )
8
9 // Router dispatches incoming DMs to the appropriate handler.
10 type Router struct {
11 subHandler *SubscriptionHandler
12 outbound *OutboundProcessor
13 sendDM func(pubkeyHex string, content string) error
14 }
15
16 // NewRouter creates a DM router.
17 func NewRouter(
18 subHandler *SubscriptionHandler,
19 outbound *OutboundProcessor,
20 sendDM func(pubkeyHex string, content string) error,
21 ) *Router {
22 return &Router{
23 subHandler: subHandler,
24 outbound: outbound,
25 sendDM: sendDM,
26 }
27 }
28
29 // RouteDM processes an incoming DM and routes it to the right handler.
30 func (r *Router) RouteDM(ctx context.Context, senderPubkeyHex, content string) {
31 // Check for commands first
32 result := ClassifyDMFull(content)
33
34 switch result.Command {
35 case DMCommandSubscribe:
36 log.D.F("subscribe command from %s (alias=%q)", senderPubkeyHex, result.Alias)
37 if r.subHandler != nil {
38 // Run in goroutine — HandleSubscribe blocks for up to 10 minutes
39 // waiting for payment, and must not block the event processing loop.
40 go r.subHandler.HandleSubscribe(ctx, senderPubkeyHex, result.Alias)
41 } else {
42 r.reply(senderPubkeyHex, "Subscriptions are not configured on this bridge.")
43 }
44 return
45
46 case DMCommandStatus:
47 log.D.F("status command from %s", senderPubkeyHex)
48 if r.subHandler != nil {
49 r.subHandler.HandleStatus(senderPubkeyHex)
50 } else {
51 r.reply(senderPubkeyHex, "Subscriptions are not configured on this bridge.")
52 }
53 return
54 }
55
56 // Check if it's an outbound email
57 if IsOutboundEmail(content) {
58 log.D.F("outbound email from %s", senderPubkeyHex)
59 if r.outbound != nil {
60 r.outbound.ProcessOutbound(senderPubkeyHex, content)
61 } else {
62 r.reply(senderPubkeyHex, "Outbound email is not configured on this bridge.")
63 }
64 return
65 }
66
67 // Not a recognized command or email — auto-reply with help
68 log.D.F("unrecognized DM from %s", senderPubkeyHex)
69 r.reply(senderPubkeyHex,
70 "Marmot Email Bridge\n\n"+
71 "Commands:\n"+
72 " subscribe — Subscribe with npub-only email\n"+
73 " subscribe <alias> — Subscribe with a custom email alias\n"+
74 " status — Check your subscription status\n\n"+
75 "To send an email, format your DM like:\n\n"+
76 "To: recipient@example.com\n"+
77 "Subject: Your subject\n\n"+
78 "Your message here.",
79 )
80 }
81
82 func (r *Router) reply(pubkeyHex, content string) {
83 if r.sendDM == nil {
84 return
85 }
86 if err := r.sendDM(pubkeyHex, content); err != nil {
87 log.E.F("failed to send reply DM to %s: %v", pubkeyHex, err)
88 }
89 }
90