54 lines
2.4 KiB
Go
54 lines
2.4 KiB
Go
package channelbridge
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
|
)
|
|
|
|
// bubbleSender is the per-bubble outbound call the shared IM dispatcher uses. A
|
|
// reply exits through the adapter's SendMessage (UnifiedResponse) path, a proactive
|
|
// push through its SendProactive path — both collapse to "dispatch one chat bubble to
|
|
// the platform", which is what lets the reply and the proactive push share a single
|
|
// sanitize/split/gap pipeline instead of each re-implementing it.
|
|
type bubbleSender func(ctx context.Context, msg adapter.UnifiedMessage) error
|
|
|
|
// sendIMBubbles is the single IM outbound exit shared by sendReply (reply path) and
|
|
// SendProactive (proactive push). It turns content into the plain chat bubbles an IM
|
|
// channel should receive: role-play/action/narration markup (rendered as muted italic
|
|
// on the Web) is stripped and each <p> paragraph is delivered as its own
|
|
// bubble/message (design/17 §2), with a multi-bubble burst spacing its sends 0.5-2s
|
|
// apart so it does not flood the channel. The Web frontend is never affected — it
|
|
// renders the persisted stream directly and does not route through here.
|
|
//
|
|
// target carries the platform/channel targeting fields and is copied onto every
|
|
// bubble (only Content/ContentType/Timestamp differ per bubble); sender is the
|
|
// adapter's outbound call. A content that strips to nothing (a pure action line) is
|
|
// dropped without a send, and a per-bubble failure is wrapped with its index.
|
|
func (b *Bridge) sendIMBubbles(ctx context.Context, target adapter.UnifiedMessage, sender bubbleSender, content string) error {
|
|
// splitBubbles applies stripNarration per bubble (so <action>/<narration> and the
|
|
// star-drift role-play forms are removed for IM) and splits on <p>. It is the
|
|
// single sanitize entry point, shared with the old reply path.
|
|
bubbles := splitBubbles(content)
|
|
if len(bubbles) == 0 {
|
|
b.log.Debug("im outbound has no plain text, skipped", "platform", target.Platform, "channel", target.ChannelID)
|
|
return nil
|
|
}
|
|
for i, bubble := range bubbles {
|
|
if i > 0 {
|
|
time.Sleep(b.bubbleGapFn())
|
|
}
|
|
out := target
|
|
out.Content = bubble
|
|
out.ContentType = adapter.ContentText
|
|
out.Timestamp = time.Now().UTC()
|
|
// Each bubble is its own send so the gap lands between messages.
|
|
if err := sender(ctx, out); err != nil {
|
|
return fmt.Errorf("channelbridge: send bubble #%d of %d: %w", i+1, len(bubbles), err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|