Telegram bot

This commit is contained in:
SeraphJACK 2023-09-26 16:38:35 +08:00
parent 8ea2ad9d0d
commit 61e487f10d
Signed by: SeraphJACK
GPG Key ID: B4FFEA56F3BE0D0C
7 changed files with 214 additions and 0 deletions

24
bot/auth.go Normal file
View File

@ -0,0 +1,24 @@
package bot
import (
"fmt"
"git.s8k.top/SeraphJACK/beanbot/config"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func authorized(ctx *messageHandleContext, user *tgbotapi.User) bool {
for _, v := range config.Cfg.AuthorizedUserIDs {
if v == user.ID {
return true
}
}
msg := tgbotapi.NewMessage(ctx.chat.ID,
fmt.Sprintf("User with ID `%d` is not authorized to access this bot.", user.ID))
msg.ParseMode = tgbotapi.ModeMarkdown
go ctx.bot.Send(msg)
return false
}

108
bot/bot.go Normal file
View File

@ -0,0 +1,108 @@
package bot
import (
"fmt"
"log"
"strings"
"time"
"git.s8k.top/SeraphJACK/beanbot/config"
"git.s8k.top/SeraphJACK/beanbot/syntax"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/google/uuid"
)
type messageHandleContext struct {
bot *tgbotapi.BotAPI
chat *tgbotapi.Chat
msg *tgbotapi.Message
}
func Start() error {
bot, err := tgbotapi.NewBotAPI(config.Cfg.BotToken)
if err != nil {
return err
}
// Polling message updates
go func() {
id := 0
for {
id++
u := tgbotapi.NewUpdate(id)
u.Timeout = 60
updates := bot.GetUpdatesChan(u)
for update := range updates {
if update.Message != nil {
ctx := &messageHandleContext{bot: bot, chat: update.Message.Chat, msg: update.Message}
go handleMessage(ctx)
}
if update.CallbackQuery != nil {
go handleCallback(update.CallbackQuery)
}
}
}
}()
// Watch for transactions that need to be committed
go func() {
for {
commitAll()
time.Sleep(time.Second)
}
}()
return nil
}
func handleMessage(ctx *messageHandleContext) {
msg := ctx.msg
// We only process private messages
if msg.Chat.Type != "private" {
return
}
// User is not authorized, break
if !authorized(ctx, msg.From) {
return
}
txn, err := syntax.Parse(strings.Split(msg.Text, ""), &config.Cfg.Syntax)
if err != nil {
msg := tgbotapi.NewMessage(ctx.chat.ID, "Failed to parse txn syntax: "+err.Error())
go ctx.bot.Send(msg)
return
}
msgCfg := tgbotapi.NewMessage(ctx.chat.ID,
fmt.Sprintf("The following transaction is about to be committed:```\n%s\n```",
txn.ToBeanLanguageSyntax()),
)
msgCfg.ParseMode = tgbotapi.ModeMarkdown
txnMsg, err := ctx.bot.Send(msgCfg)
if err != nil {
log.Printf("Failed to send txn confirmation message: %v", err)
return
}
txnID := uuid.New().String()
cbMsg, err := ctx.bot.Send(tgbotapi.NewCallback(txnID, "Cancel"))
if err != nil {
log.Printf("Failed to send cancel callback message: %v", err)
return
}
aboutToCommit(txnID, &transaction{
txn: txn,
txmMsg: txnMsg,
callbackMsg: cbMsg,
commitTime: time.Now().Add(10 * time.Second),
})
}
func handleCallback(query *tgbotapi.CallbackQuery) {
cancel(query.ID)
}

66
bot/txn.go Normal file
View File

@ -0,0 +1,66 @@
package bot
import (
"fmt"
"sync"
"time"
"git.s8k.top/SeraphJACK/beanbot/repo"
"git.s8k.top/SeraphJACK/beanbot/syntax"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type transaction struct {
ctx *messageHandleContext
txn *syntax.Transaction
txmMsg tgbotapi.Message
callbackMsg tgbotapi.Message
commitTime time.Time
}
var lock sync.Mutex
var aboutToCommitTxn map[string]*transaction
func aboutToCommit(id string, txn *transaction) {
lock.Lock()
defer lock.Unlock()
aboutToCommitTxn[id] = txn
}
func cancel(id string) {
lock.Lock()
defer lock.Unlock()
if _, ok := aboutToCommitTxn[id]; ok {
delete(aboutToCommitTxn, id)
}
}
func commitAll() {
lock.Lock()
defer lock.Unlock()
for k, v := range aboutToCommitTxn {
// txn not due to commit, skip
if time.Now().Before(v.commitTime) {
continue
}
err := repo.CommitTransaction(v.txn)
if err != nil {
go v.ctx.bot.Send(tgbotapi.NewMessage(v.ctx.chat.ID, fmt.Sprintf("Failed to commit txn: %v", err)))
}
delete(aboutToCommitTxn, k)
if err == nil {
// delete transaction message
go v.ctx.bot.Send(tgbotapi.NewDeleteMessage(v.ctx.chat.ID, v.ctx.msg.MessageID))
}
// delete transaction confirmation message
go v.ctx.bot.Send(tgbotapi.NewDeleteMessage(v.ctx.chat.ID, v.txmMsg.MessageID))
// delete transaction cancel callback message
go v.ctx.bot.Send(tgbotapi.NewDeleteMessage(v.ctx.chat.ID, v.callbackMsg.MessageID))
}
}

View File

@ -5,6 +5,7 @@ import (
"log"
"os"
"git.s8k.top/SeraphJACK/beanbot/bot"
"git.s8k.top/SeraphJACK/beanbot/config"
"git.s8k.top/SeraphJACK/beanbot/repo"
"github.com/spf13/pflag"
@ -32,4 +33,9 @@ func main() {
log.Fatalf("Failed to init beancount repository: %v", err)
return
}
if err := bot.Start(); err != nil {
log.Fatalf("Failed to start telegram bot: %v", err)
return
}
}

View File

@ -9,6 +9,8 @@ import (
)
type Config struct {
BotToken string `yaml:"botToken"`
AuthorizedUserIDs []int64 `yaml:"authorizedUserIDs"`
Repo string `yaml:"repo"`
Username string `yaml:"username"`
Password string `yaml:"password"`
@ -19,6 +21,8 @@ type Config struct {
}
var Cfg = Config{
BotToken: "xxx",
AuthorizedUserIDs: []int64{},
Repo: "https://git.xxx.com/xxx/xxx.git",
Username: "someone",
Password: "password",

2
go.mod
View File

@ -4,6 +4,8 @@ go 1.21.1
require (
github.com/go-git/go-git/v5 v5.9.0
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/google/uuid v1.3.1
github.com/spf13/pflag v1.0.5
gopkg.in/yaml.v3 v3.0.1
)

4
go.sum
View File

@ -33,10 +33,14 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY=
github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=