2023-08-19 09:12:26 +03:00
|
|
|
package tg
|
2023-07-09 01:28:59 +03:00
|
|
|
|
|
|
|
import (
|
2023-08-13 15:37:36 +03:00
|
|
|
"errors"
|
2023-08-12 14:35:33 +03:00
|
|
|
|
2023-08-19 12:47:33 +03:00
|
|
|
"fmt"
|
|
|
|
|
|
|
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
2023-07-09 01:28:59 +03:00
|
|
|
)
|
|
|
|
|
2023-08-19 12:47:33 +03:00
|
|
|
type Update = tgbotapi.Update
|
|
|
|
type Chat = tgbotapi.Chat
|
|
|
|
type User = tgbotapi.User
|
2023-08-13 15:37:36 +03:00
|
|
|
|
2023-07-09 01:28:59 +03:00
|
|
|
// The wrapper around Telegram API.
|
|
|
|
type Bot struct {
|
2023-08-19 12:47:33 +03:00
|
|
|
Api *tgbotapi.BotAPI
|
|
|
|
Me *User
|
2023-08-13 15:37:36 +03:00
|
|
|
// Private bot behaviour.
|
|
|
|
behaviour *Behaviour
|
|
|
|
// Group bot behaviour.
|
|
|
|
groupBehaviour *GroupBehaviour
|
|
|
|
// Bot behaviour in channels.
|
|
|
|
channelBehaviour *ChannelBehaviour
|
|
|
|
sessions SessionMap
|
|
|
|
groupSessions GroupSessionMap
|
2023-08-19 13:34:21 +03:00
|
|
|
value any
|
2023-07-09 01:28:59 +03:00
|
|
|
}
|
|
|
|
|
2023-08-13 15:37:36 +03:00
|
|
|
// Return the new bot with empty sessions and behaviour.
|
|
|
|
func NewBot(token string) (*Bot, error) {
|
2023-08-19 12:47:33 +03:00
|
|
|
bot, err := tgbotapi.NewBotAPI(token)
|
2023-08-10 15:49:25 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Bot{
|
2023-08-19 12:47:33 +03:00
|
|
|
Api: bot,
|
2023-08-10 15:49:25 +03:00
|
|
|
}, nil
|
2023-07-09 01:28:59 +03:00
|
|
|
}
|
|
|
|
|
2023-08-19 13:34:21 +03:00
|
|
|
// Set the custom global value for the bot,
|
|
|
|
// so it can be accessed from the callback
|
|
|
|
// functions.
|
|
|
|
func (bot *Bot) WithValue(v any) *Bot {
|
|
|
|
bot.value = v
|
|
|
|
return bot
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the global bot value.
|
|
|
|
func (bot *Bot) Value() any {
|
|
|
|
return bot.value
|
|
|
|
}
|
|
|
|
|
2023-08-19 12:47:33 +03:00
|
|
|
func (bot *Bot) Debug(debug bool) *Bot {
|
|
|
|
bot.Api.Debug = debug
|
|
|
|
return bot
|
|
|
|
}
|
|
|
|
|
|
|
|
func (bot *Bot) Send(
|
|
|
|
sid SessionId, v any,
|
|
|
|
) (*Message, error) {
|
|
|
|
sendable, ok := v.(Sendable)
|
|
|
|
if !ok {
|
|
|
|
cid := sid.ToApi()
|
|
|
|
str := tgbotapi.NewMessage(
|
|
|
|
cid, fmt.Sprint(v),
|
|
|
|
)
|
|
|
|
msg, err := bot.Api.Send(str)
|
|
|
|
return &msg, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return sendable.Send(sid, bot)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (bot *Bot) Render(
|
|
|
|
sid SessionId, r Renderable,
|
|
|
|
) ([]*Message, error) {
|
|
|
|
return r.Render(sid, bot)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (bot *Bot) GetSession(
|
2023-08-13 15:37:36 +03:00
|
|
|
sid SessionId,
|
2023-08-19 12:47:33 +03:00
|
|
|
) (*Session, bool) {
|
|
|
|
session, ok := bot.sessions[sid]
|
|
|
|
return session, ok
|
2023-08-13 15:37:36 +03:00
|
|
|
}
|
|
|
|
|
2023-08-19 12:47:33 +03:00
|
|
|
func (bot *Bot) GetGroupSession(
|
2023-08-13 15:37:36 +03:00
|
|
|
sid SessionId,
|
2023-08-19 12:47:33 +03:00
|
|
|
) (*GroupSession, bool) {
|
|
|
|
session, ok := bot.groupSessions[sid]
|
|
|
|
return session, ok
|
2023-08-13 15:37:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bot) WithBehaviour(beh *Behaviour) *Bot {
|
|
|
|
b.behaviour = beh
|
|
|
|
b.sessions = make(SessionMap)
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bot) WithSessions(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
|
|
|
|
}
|
|
|
|
|
2023-07-09 01:28:59 +03:00
|
|
|
// Run the bot with the Behaviour.
|
|
|
|
func (bot *Bot) Run() error {
|
2023-08-13 15:37:36 +03:00
|
|
|
if bot.behaviour == nil &&
|
|
|
|
bot.groupBehaviour == nil {
|
|
|
|
return errors.New("no behaviour defined")
|
|
|
|
}
|
2023-08-19 12:47:33 +03:00
|
|
|
uc := tgbotapi.NewUpdate(0)
|
2023-07-09 01:28:59 +03:00
|
|
|
uc.Timeout = 60
|
2023-08-19 12:47:33 +03:00
|
|
|
updates := bot.Api.GetUpdatesChan(uc)
|
2023-08-13 15:37:36 +03:00
|
|
|
handles := make(map[string]chan *Update)
|
2023-08-10 15:49:25 +03:00
|
|
|
|
2023-08-13 15:37:36 +03:00
|
|
|
if bot.behaviour != nil {
|
|
|
|
chn := make(chan *Update)
|
|
|
|
handles["private"] = chn
|
|
|
|
go bot.handlePrivate(chn)
|
|
|
|
}
|
2023-08-12 14:35:33 +03:00
|
|
|
|
2023-08-13 15:37:36 +03:00
|
|
|
if bot.groupBehaviour != nil {
|
|
|
|
chn := make(chan *Update)
|
|
|
|
handles["group"] = chn
|
|
|
|
handles["supergroup"] = chn
|
|
|
|
go bot.handleGroup(chn)
|
|
|
|
}
|
|
|
|
|
2023-08-19 12:47:33 +03:00
|
|
|
me, _ := bot.Api.GetMe()
|
2023-08-13 15:37:36 +03:00
|
|
|
bot.Me = &me
|
|
|
|
for u := range updates {
|
|
|
|
chn, ok := handles[u.FromChat().Type]
|
|
|
|
if !ok {
|
|
|
|
continue
|
2023-07-12 14:20:52 +03:00
|
|
|
}
|
2023-08-13 15:37:36 +03:00
|
|
|
|
|
|
|
chn <- &u
|
2023-07-09 01:28:59 +03:00
|
|
|
}
|
2023-08-10 15:49:25 +03:00
|
|
|
|
2023-07-09 01:28:59 +03:00
|
|
|
return nil
|
|
|
|
}
|
2023-08-12 14:35:33 +03:00
|
|
|
|
|
|
|
// The function handles updates supposed for the private
|
|
|
|
// chat with the bot.
|
2023-08-13 15:37:36 +03:00
|
|
|
func (bot *Bot) handlePrivate(updates chan *Update) {
|
|
|
|
chans := make(map[SessionId]chan *Update)
|
2023-08-12 14:35:33 +03:00
|
|
|
var sid SessionId
|
2023-08-13 15:37:36 +03:00
|
|
|
for u := range updates {
|
2023-08-16 17:25:56 +03:00
|
|
|
sid = SessionId(u.FromChat().ID)
|
|
|
|
// Create new session if the one does not exist
|
|
|
|
// for this user.
|
2023-08-12 14:35:33 +03:00
|
|
|
|
2023-08-16 17:25:56 +03:00
|
|
|
// Making the bot ignore anything except "start"
|
|
|
|
// before the session started
|
2023-08-19 13:25:47 +03:00
|
|
|
session, sessionOk := bot.sessions[sid]
|
|
|
|
chn, chnOk := chans[sid]
|
|
|
|
if sessionOk {
|
2023-09-07 15:45:38 +03:00
|
|
|
// Creating new goroutine for
|
|
|
|
// the session that exists
|
|
|
|
// but has none.
|
2023-08-19 13:25:47 +03:00
|
|
|
if !chnOk {
|
2023-08-18 13:46:10 +03:00
|
|
|
ctx := &context{
|
|
|
|
Bot: bot,
|
2023-08-16 17:25:56 +03:00
|
|
|
Session: session,
|
|
|
|
updates: make(chan *Update),
|
|
|
|
}
|
2023-08-19 13:25:47 +03:00
|
|
|
chn := make(chan *Update)
|
|
|
|
chans[sid] = chn
|
|
|
|
go ctx.handleUpdateChan(chn)
|
|
|
|
}
|
2023-09-07 15:45:38 +03:00
|
|
|
} else if u.Message != nil {
|
|
|
|
// Create session on any message
|
|
|
|
// if we have no one.
|
|
|
|
bot.sessions.Add(sid)
|
|
|
|
lsession := bot.sessions[sid]
|
|
|
|
ctx := &context{
|
|
|
|
Bot: bot,
|
|
|
|
Session: lsession,
|
|
|
|
updates: make(chan *Update),
|
2023-08-12 14:35:33 +03:00
|
|
|
}
|
2023-09-07 15:45:38 +03:00
|
|
|
chn := make(chan *Update)
|
|
|
|
chans[sid] = chn
|
|
|
|
go ctx.handleUpdateChan(chn)
|
2023-08-13 15:37:36 +03:00
|
|
|
}
|
2023-08-19 13:25:47 +03:00
|
|
|
|
2023-08-13 15:37:36 +03:00
|
|
|
chn, ok := chans[sid]
|
|
|
|
// The bot MUST get the "start" command.
|
|
|
|
// It will do nothing otherwise.
|
|
|
|
if ok {
|
|
|
|
chn <- u
|
2023-08-12 14:35:33 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-13 15:37:36 +03:00
|
|
|
func (bot *Bot) handleGroup(updates chan *Update) {
|
|
|
|
var sid SessionId
|
|
|
|
chans := make(map[SessionId]chan *Update)
|
|
|
|
for u := range updates {
|
|
|
|
sid = SessionId(u.FromChat().ID)
|
|
|
|
// If no session add new.
|
|
|
|
if _, ok := bot.groupSessions[sid]; !ok {
|
|
|
|
bot.groupSessions.Add(sid)
|
|
|
|
session := bot.groupSessions[sid]
|
2023-08-18 13:46:10 +03:00
|
|
|
ctx := &groupContext{
|
2023-08-19 12:47:33 +03:00
|
|
|
Bot: bot,
|
|
|
|
Session: session,
|
|
|
|
updates: make(chan *Update),
|
2023-08-13 15:37:36 +03:00
|
|
|
}
|
|
|
|
chn := make(chan *Update)
|
|
|
|
chans[sid] = chn
|
|
|
|
go ctx.handleUpdateChan(chn)
|
|
|
|
}
|
|
|
|
|
|
|
|
chn := chans[sid]
|
|
|
|
chn <- u
|
|
|
|
}
|
2023-08-12 14:35:33 +03:00
|
|
|
}
|