269 lines
5.2 KiB
Go
269 lines
5.2 KiB
Go
package tg
|
|
|
|
import (
|
|
"errors"
|
|
"sort"
|
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
)
|
|
|
|
|
|
type Chat = tgbotapi.Chat
|
|
type User = tgbotapi.User
|
|
|
|
// The wrapper around Telegram API.
|
|
type Bot struct {
|
|
// Custom data value.
|
|
data any
|
|
api *tgbotapi.BotAPI
|
|
me User
|
|
// Private bot behaviour.
|
|
behaviour *Behaviour
|
|
// Group bot behaviour.
|
|
//groupBehaviour *GroupBehaviour
|
|
// Bot behaviour in channels.
|
|
//channelBehaviour *ChannelBehaviour
|
|
sessions SessionMap
|
|
//groupSessions GroupSessionMap
|
|
}
|
|
|
|
// Return the new bot with empty sessions and behaviour.
|
|
func NewBot(token string) (*Bot, error) {
|
|
bot, err := tgbotapi.NewBotAPI(token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Bot{
|
|
api: bot,
|
|
}, nil
|
|
}
|
|
|
|
func (bot *Bot) SetDebug(debug bool) *Bot {
|
|
bot.api.Debug = debug
|
|
return bot
|
|
}
|
|
|
|
func (bot *Bot) API() *tgbotapi.BotAPI {
|
|
return bot.api
|
|
}
|
|
|
|
func (bot *Bot) Me() User {
|
|
return bot.me
|
|
}
|
|
|
|
// Send the Renderable to the specified session client side.
|
|
// Can be used for both group and private sessions because
|
|
// SessionID represents both for chat IDs.
|
|
func (bot *Bot) Send(
|
|
sid SessionID, v Sendable,
|
|
) (*Message, error) {
|
|
config := v.SendConfig(sid, bot)
|
|
if config.Error != nil {
|
|
return nil, config.Error
|
|
}
|
|
|
|
msg, err := bot.api.Send(config.ToAPI())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v.SetMessage(&msg)
|
|
return &msg, nil
|
|
}
|
|
|
|
func (bot *Bot) Sendf(
|
|
sid SessionID, format string, v ...any,
|
|
) (*Message, error){
|
|
return bot.Send(
|
|
sid,
|
|
Messagef(format, v...),
|
|
)
|
|
}
|
|
|
|
// Send to the session specified its ID raw chattable from the tgbotapi.
|
|
func (bot *Bot) SendRaw(
|
|
sid SessionID, v tgbotapi.Chattable,
|
|
) (*Message, error) {
|
|
msg, err := bot.api.Send(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &msg, nil
|
|
}
|
|
|
|
// Get session by its ID. Can be used for any scope
|
|
// including private, group and channel.
|
|
func (bot *Bot) GotSession(
|
|
sid SessionID,
|
|
) (*Session, bool) {
|
|
session, ok := bot.sessions[sid]
|
|
return session, ok
|
|
}
|
|
|
|
func (bot *Bot) SetData(v any) *Bot {
|
|
bot.data = v
|
|
return bot
|
|
}
|
|
|
|
func (bot *Bot) Data() any {
|
|
return bot.data
|
|
}
|
|
|
|
func (b *Bot) SetBehaviour(beh *Behaviour) *Bot {
|
|
b.behaviour = beh
|
|
b.sessions = make(SessionMap)
|
|
return b
|
|
}
|
|
|
|
func (b *Bot) SetSessions(sessions SessionMap) *Bot {
|
|
b.sessions = sessions
|
|
return b
|
|
}
|
|
|
|
/*func (b *Bot) WithGroupBehaviour(beh *GroupBehaviour) *Bot {
|
|
b.groupBehaviour = beh
|
|
b.groupSessions = make(GroupSessionMap)
|
|
return b
|
|
}
|
|
|
|
func (b *Bot) WithGroupSessions(sessions GroupSessionMap) *Bot {
|
|
b.groupSessions = sessions
|
|
return b
|
|
}*/
|
|
|
|
func (bot *Bot) DeleteCommands() {
|
|
//tgbotapi.NewBotCommandScopeAllPrivateChats(),
|
|
cfg := tgbotapi.NewDeleteMyCommands()
|
|
bot.api.Request(cfg)
|
|
}
|
|
|
|
// Setting the command on the user side.
|
|
func (bot *Bot) SetCommands(
|
|
scope tgbotapi.BotCommandScope,
|
|
cmdMap CommandMap,
|
|
) error {
|
|
// First the private commands.
|
|
names := []string{}
|
|
for name := range cmdMap {
|
|
names = append(names, string(name))
|
|
}
|
|
sort.Strings(names)
|
|
|
|
cmds := []Command{}
|
|
for _, name := range names {
|
|
cmds = append(
|
|
cmds,
|
|
cmdMap[CommandName(name)],
|
|
)
|
|
}
|
|
|
|
botCmds := []tgbotapi.BotCommand{}
|
|
for _, cmd := range cmds {
|
|
botCmds = append(botCmds, cmd.ToAPI())
|
|
}
|
|
|
|
//tgbotapi.NewBotCommandScopeAllPrivateChats(),
|
|
cfg := tgbotapi.NewSetMyCommandsWithScope(
|
|
scope,
|
|
botCmds...,
|
|
)
|
|
|
|
_, err := bot.api.Request(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Run the bot with the Behaviour.
|
|
func (bot *Bot) Run() error {
|
|
if bot.behaviour == nil {
|
|
return errors.New("no behaviour defined")
|
|
}
|
|
|
|
if bot.behaviour != nil && bot.behaviour.Root == nil {
|
|
return errors.New("the root widget is not set, cannot run")
|
|
}
|
|
|
|
uc := tgbotapi.NewUpdate(0)
|
|
uc.Timeout = 10
|
|
updates := bot.api.GetUpdatesChan(uc)
|
|
handles := make(map[string] chan Update)
|
|
|
|
if bot.behaviour != nil {
|
|
chn := make(chan Update)
|
|
handles["private"] = chn
|
|
go bot.handlePrivate(chn)
|
|
}
|
|
|
|
/*if bot.groupBehaviour != nil {
|
|
commanders := make(map[CommandName] BotCommander)
|
|
for k, v := range bot.groupBehaviour.Commands {
|
|
commanders[k] = v
|
|
}
|
|
bot.SetCommands(
|
|
tgbotapi.NewBotCommandScopeAllGroupChats(),
|
|
commanders,
|
|
)
|
|
chn := make(chan *Update)
|
|
handles["group"] = chn
|
|
handles["supergroup"] = chn
|
|
go bot.handleGroup(chn)
|
|
}*/
|
|
|
|
me, _ := bot.API().GetMe()
|
|
bot.me = me
|
|
for up := range updates {
|
|
u := Update{
|
|
Update: up,
|
|
}
|
|
|
|
// Sometimes returns nil.
|
|
fromChat := u.FromChat()
|
|
if fromChat == nil {
|
|
continue
|
|
}
|
|
|
|
chn, ok := handles[fromChat.Type]
|
|
// Skipping non existent scope.
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
chn <- u
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// The function handles updates supposed for the private
|
|
// chat with the bot.
|
|
func (bot *Bot) handlePrivate(updates chan Update) {
|
|
var sid SessionID
|
|
for u := range updates {
|
|
sid = SessionID(u.FromChat().ID)
|
|
session, sessionOk := bot.sessions[sid]
|
|
if u.Message != nil && !sessionOk {
|
|
// Creating session if we have none
|
|
// but only on text messages.
|
|
session = bot.sessions.Add(bot, sid, PrivateSessionScope)
|
|
|
|
// Creating the root context
|
|
// that takes updates directly from
|
|
// the session.
|
|
rootContext := Context{
|
|
session: session,
|
|
update: u,
|
|
input: session.updates,
|
|
}
|
|
go rootContext.serve()
|
|
rootContext.input.Send(u)
|
|
|
|
continue
|
|
}
|
|
|
|
if sessionOk {
|
|
session.updates.Send(u)
|
|
}
|
|
}
|
|
}
|
|
|