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