mirror of
https://github.com/tiennm99/openai-status-bot.git
synced 2026-07-14 09:05:43 +00:00
147 lines
4.1 KiB
Go
147 lines
4.1 KiB
Go
package bot
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"strings"
|
|
|
|
tgbot "github.com/go-telegram/bot"
|
|
tgmodels "github.com/go-telegram/bot/models"
|
|
"github.com/tiennm99/openai-status-bot/internal/mongostore"
|
|
openai "github.com/tiennm99/openai-status-bot/internal/openai"
|
|
)
|
|
|
|
type ReplySender interface {
|
|
SendText(ctx context.Context, chatID int64, threadID *int, text string) error
|
|
}
|
|
|
|
type StatusClient interface {
|
|
FetchSummary(ctx context.Context) (openai.Summary, error)
|
|
FetchIncidents(ctx context.Context) (openai.IncidentsResponse, error)
|
|
}
|
|
|
|
type Store interface {
|
|
AddSubscriber(ctx context.Context, sub mongostore.Subscriber) error
|
|
GetSubscriber(ctx context.Context, sub mongostore.Subscriber) (mongostore.Subscriber, bool, error)
|
|
RemoveSubscriber(ctx context.Context, sub mongostore.Subscriber) error
|
|
SaveTelegramOffset(ctx context.Context, offset int64) error
|
|
UpdateSubscriberSettings(ctx context.Context, sub mongostore.Subscriber, types, components []string) (bool, error)
|
|
UpdateSubscriberTypes(ctx context.Context, sub mongostore.Subscriber, types []string) (bool, error)
|
|
}
|
|
|
|
type App struct {
|
|
sender ReplySender
|
|
statusClient StatusClient
|
|
store Store
|
|
logger *slog.Logger
|
|
username string
|
|
}
|
|
|
|
type MessageContext struct {
|
|
ChatID int64
|
|
ThreadID *int
|
|
Text string
|
|
}
|
|
|
|
func New(sender ReplySender, statusClient StatusClient, store Store, logger *slog.Logger, username string) *App {
|
|
return &App{
|
|
sender: sender,
|
|
statusClient: statusClient,
|
|
store: store,
|
|
logger: logger,
|
|
username: username,
|
|
}
|
|
}
|
|
|
|
func (b *App) RegisterHandlers(telegramBot *tgbot.Bot) {
|
|
telegramBot.RegisterHandlerMatchFunc(hasMessage, b.HandleUpdate)
|
|
}
|
|
|
|
func hasMessage(update *tgmodels.Update) bool {
|
|
return update != nil && update.Message != nil
|
|
}
|
|
|
|
func (b *App) HandleUpdate(ctx context.Context, _ *tgbot.Bot, update *tgmodels.Update) {
|
|
message, ok := messageContextFromUpdate(update)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
b.handleMessage(ctx, message)
|
|
|
|
if err := b.store.SaveTelegramOffset(ctx, update.ID+1); err != nil {
|
|
b.logger.Warn("save telegram offset", "offset", update.ID+1, "error", err)
|
|
}
|
|
}
|
|
|
|
func messageContextFromUpdate(update *tgmodels.Update) (MessageContext, bool) {
|
|
if update == nil || update.Message == nil {
|
|
return MessageContext{}, false
|
|
}
|
|
|
|
message := update.Message
|
|
var threadID *int
|
|
if message.MessageThreadID != 0 {
|
|
id := message.MessageThreadID
|
|
threadID = &id
|
|
}
|
|
|
|
return MessageContext{
|
|
ChatID: message.Chat.ID,
|
|
ThreadID: threadID,
|
|
Text: message.Text,
|
|
}, true
|
|
}
|
|
|
|
func (b *App) handleMessage(ctx context.Context, message MessageContext) {
|
|
if !strings.HasPrefix(strings.TrimSpace(message.Text), "/") {
|
|
return
|
|
}
|
|
|
|
command, fields := normalizeCommand(message.Text, b.username)
|
|
switch command {
|
|
case "":
|
|
return
|
|
case "/start":
|
|
b.subscribe(ctx, message)
|
|
case "/stop":
|
|
b.unsubscribe(ctx, message)
|
|
case "/status":
|
|
b.replyStatus(ctx, message, fields)
|
|
case "/components":
|
|
b.replyComponents(ctx, message)
|
|
case "/subscribe":
|
|
b.replySubscribe(ctx, message, fields)
|
|
case "/history":
|
|
b.replyHistory(ctx, message, parseHistoryCount(fields))
|
|
case "/uptime":
|
|
b.replyUptime(ctx, message)
|
|
case "/info":
|
|
b.replyInfo(ctx, message)
|
|
case "/help":
|
|
b.reply(ctx, message, helpText)
|
|
default:
|
|
b.reply(ctx, message, "Unknown command. Use /help.")
|
|
}
|
|
}
|
|
|
|
func (b *App) subscribe(ctx context.Context, message MessageContext) {
|
|
sub := mongostore.NewSubscriber(message.ChatID, message.ThreadID)
|
|
if err := b.store.AddSubscriber(ctx, sub); err != nil {
|
|
b.logger.Error("subscribe", "error", err)
|
|
b.reply(ctx, message, "Could not subscribe right now.")
|
|
return
|
|
}
|
|
b.reply(ctx, message, "Subscribed to OpenAI status updates. Use /subscribe to change preferences.")
|
|
}
|
|
|
|
func (b *App) unsubscribe(ctx context.Context, message MessageContext) {
|
|
sub := mongostore.NewSubscriber(message.ChatID, message.ThreadID)
|
|
if err := b.store.RemoveSubscriber(ctx, sub); err != nil {
|
|
b.logger.Error("unsubscribe", "error", err)
|
|
b.reply(ctx, message, "Could not unsubscribe right now.")
|
|
return
|
|
}
|
|
b.reply(ctx, message, "Unsubscribed from OpenAI status updates. Use /start to resubscribe.")
|
|
}
|