mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-10-07 08:20:51 +08:00
152 lines
3.3 KiB
Go
152 lines
3.3 KiB
Go
package kick
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/facebookincubator/go-belt/tool/logger"
|
|
"github.com/xaionaro-go/kickcom/pkg/kickcom"
|
|
"github.com/xaionaro-go/streamctl/pkg/observability"
|
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
|
)
|
|
|
|
type ChatClient interface {
|
|
GetChatMessagesV2(
|
|
ctx context.Context,
|
|
channelID uint64,
|
|
cursor uint64,
|
|
) (*kickcom.ChatMessagesV2Reply, error)
|
|
}
|
|
|
|
type ChatHandler struct {
|
|
currentCursor uint64
|
|
channelID uint64
|
|
lastMessageID string
|
|
client Client
|
|
cancelFunc context.CancelFunc
|
|
messagesOutChan chan streamcontrol.ChatMessage
|
|
}
|
|
|
|
func (k *Kick) newChatHandler(
|
|
ctx context.Context,
|
|
channelID uint64,
|
|
) (*ChatHandler, error) {
|
|
return NewChatHandler(ctx, k.Client, channelID)
|
|
}
|
|
|
|
func NewChatHandler(
|
|
ctx context.Context,
|
|
chatClient Client,
|
|
channelID uint64,
|
|
) (*ChatHandler, error) {
|
|
|
|
ctx, cancelFn := context.WithCancel(ctx)
|
|
h := &ChatHandler{
|
|
currentCursor: 0,
|
|
client: chatClient,
|
|
channelID: channelID,
|
|
cancelFunc: cancelFn,
|
|
messagesOutChan: make(chan streamcontrol.ChatMessage, 100),
|
|
}
|
|
|
|
observability.Go(ctx, func() {
|
|
defer func() {
|
|
close(h.messagesOutChan)
|
|
}()
|
|
|
|
h.sendMessage(kickcom.ChatMessageV2{
|
|
ID: "",
|
|
ChatID: 0,
|
|
UserID: 0,
|
|
Content: "test\nmultiline message",
|
|
Type: "",
|
|
Metadata: nil,
|
|
CreatedAt: time.Time{},
|
|
Sender: kickcom.ChatMessageSenderV2{
|
|
ID: 0,
|
|
Slug: "",
|
|
Username: "test-user",
|
|
Identity: kickcom.Identity{},
|
|
},
|
|
})
|
|
err := h.iterate(ctx)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "unable to perform an iteration: %w", err)
|
|
return
|
|
}
|
|
|
|
t := time.NewTicker(time.Second)
|
|
defer t.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-t.C:
|
|
}
|
|
err := h.iterate(ctx)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "unable to perform an iteration: %w", err)
|
|
return
|
|
}
|
|
}
|
|
})
|
|
|
|
return h, nil
|
|
}
|
|
|
|
func (h *ChatHandler) iterate(ctx context.Context) error {
|
|
startTS := time.Now()
|
|
reply, err := h.client.GetChatMessagesV2(ctx, h.channelID, 0)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get the chat messages of channel with ID %d: %w", h.channelID, err)
|
|
}
|
|
rtt := time.Since(startTS)
|
|
logger.Tracef(ctx, "round trip time == %v", rtt)
|
|
|
|
// TODO: use the cursor instead of message ID to avoid duplicates
|
|
cursor, err := strconv.ParseUint(reply.Data.Cursor, 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to parse the cursor value '%s': %w", reply.Data.Cursor, err)
|
|
}
|
|
h.currentCursor = cursor
|
|
|
|
// assuming reply.Data is already sorted by CreatedAt DESC
|
|
|
|
slices.Reverse(reply.Data.Messages)
|
|
|
|
// skipping everything we already notified about
|
|
var firstNewIdx int
|
|
for idx, msg := range reply.Data.Messages {
|
|
if msg.ID == h.lastMessageID {
|
|
firstNewIdx = idx + 1
|
|
break
|
|
}
|
|
}
|
|
|
|
for _, msg := range reply.Data.Messages[firstNewIdx:] {
|
|
h.sendMessage(msg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *ChatHandler) sendMessage(
|
|
msg kickcom.ChatMessageV2,
|
|
) {
|
|
h.lastMessageID = msg.ID
|
|
select {
|
|
case h.messagesOutChan <- streamcontrol.ChatMessage{
|
|
CreatedAt: msg.CreatedAt,
|
|
UserID: streamcontrol.ChatUserID(fmt.Sprintf("%d", msg.UserID)),
|
|
MessageID: streamcontrol.ChatMessageID(msg.ID),
|
|
Message: msg.Content,
|
|
}:
|
|
default:
|
|
}
|
|
}
|
|
func (h *ChatHandler) MessagesChan() <-chan streamcontrol.ChatMessage {
|
|
return h.messagesOutChan
|
|
}
|