1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-06-26 19:37:46 +02:00
jfa-go/telegram.go
Harvey Tindall 99875b9176
almost complete telegram user verification
When signing up, the user is given a pin code which they send to a
telegram bot. This provides user verification, but more importantly
allows the bot to message the user, as the Telegram API requires the
user to interact with the bot before it can do the opposite.

The bot should recognize the correct language, but a /lang command is
also provided to change it.

The verification process is pretty much functional but ui is still
broken, and it isn't properly integrated yet.
2021-05-07 01:08:12 +01:00

202 lines
5.4 KiB
Go

package main
import (
"fmt"
"math/rand"
"strconv"
"strings"
"time"
tg "github.com/go-telegram-bot-api/telegram-bot-api"
)
type TelegramDaemon struct {
Stopped bool
ShutdownChannel chan string
bot *tg.BotAPI
username string
tokens []string
verifiedTokens []string
link string
app *appContext
}
func newTelegramDaemon(app *appContext) (*TelegramDaemon, error) {
token := app.config.Section("telegram").Key("token").String()
if token == "" {
return nil, fmt.Errorf("token was blank")
}
bot, err := tg.NewBotAPI(token)
if err != nil {
return nil, err
}
return &TelegramDaemon{
Stopped: false,
ShutdownChannel: make(chan string),
bot: bot,
username: bot.Self.UserName,
tokens: []string{},
verifiedTokens: []string{},
link: "https://t.me/" + bot.Self.UserName,
app: app,
}, nil
}
var runes = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
// NewAuthToken generates an 8-character pin in the form "A1-2B-CD".
func (t *TelegramDaemon) NewAuthToken() string {
rand.Seed(time.Now().UnixNano())
pin := make([]rune, 8)
for i := range pin {
if i == 2 || i == 5 {
pin[i] = '-'
} else {
pin[i] = runes[rand.Intn(len(runes))]
}
}
t.tokens = append(t.tokens, string(pin))
return string(pin)
}
func (t *TelegramDaemon) run() {
t.app.info.Println("Starting Telegram bot daemon")
u := tg.NewUpdate(0)
u.Timeout = 60
updates, err := t.bot.GetUpdatesChan(u)
if err != nil {
t.app.err.Printf("Failed to start Telegram daemon: %v", err)
return
}
for {
var upd tg.Update
select {
case upd = <-updates:
if upd.Message == nil {
continue
}
sects := strings.Split(upd.Message.Text, " ")
if len(sects) == 0 {
continue
}
lang := t.app.storage.lang.chosenTelegramLang
user, ok := t.app.storage.telegram[upd.Message.Chat.ID]
if !ok {
user := TelegramUser{
Username: upd.Message.Chat.UserName,
ChatID: upd.Message.Chat.ID,
Lang: "",
}
t.app.storage.telegram[upd.Message.Chat.ID] = user
err := t.app.storage.storeTelegramUsers()
if err != nil {
t.app.err.Printf("Failed to store Telegram users: %v", err)
}
}
if user.Lang != "" {
lang = user.Lang
} else {
for code := range t.app.storage.lang.Telegram {
if code[:2] == upd.Message.From.LanguageCode {
lang = code
break
}
}
}
switch msg := sects[0]; msg {
case "/start":
content := t.app.storage.lang.Telegram[lang].Strings.get("startMessage") + "\n"
content += t.app.storage.lang.Telegram[lang].Strings.get("languageMessage")
err := t.Reply(&upd, content)
if err != nil {
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
}
continue
case "/lang":
if len(sects) == 1 {
list := "/lang <lang>\n"
for code := range t.app.storage.lang.Telegram {
list += fmt.Sprintf("%s: %s\n", code, t.app.storage.lang.Telegram[code].Meta.Name)
}
err := t.Reply(&upd, list)
if err != nil {
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
}
continue
}
if _, ok := t.app.storage.lang.Telegram[sects[1]]; ok {
user.Lang = sects[1]
t.app.storage.telegram[upd.Message.Chat.ID] = user
err := t.app.storage.storeTelegramUsers()
if err != nil {
t.app.err.Printf("Failed to store Telegram users: %v", err)
}
}
continue
default:
tokenIndex := -1
for i, token := range t.tokens {
if upd.Message.Text == token {
tokenIndex = i
break
}
}
if tokenIndex == -1 {
err := t.QuoteReply(&upd, t.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"))
if err != nil {
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
}
continue
}
err := t.QuoteReply(&upd, t.app.storage.lang.Telegram[lang].Strings.get("success"))
if err != nil {
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
}
t.verifiedTokens = append(t.verifiedTokens, upd.Message.Text)
t.tokens[len(t.tokens)-1], t.tokens[tokenIndex] = t.tokens[tokenIndex], t.tokens[len(t.tokens)-1]
t.tokens = t.tokens[:len(t.tokens)-1]
}
case <-t.ShutdownChannel:
t.ShutdownChannel <- "Down"
return
}
}
}
func (t *TelegramDaemon) Reply(upd *tg.Update, content string) error {
msg := tg.NewMessage((*upd).Message.Chat.ID, content)
_, err := t.bot.Send(msg)
return err
}
func (t *TelegramDaemon) QuoteReply(upd *tg.Update, content string) error {
msg := tg.NewMessage((*upd).Message.Chat.ID, content)
msg.ReplyToMessageID = (*upd).Message.MessageID
_, err := t.bot.Send(msg)
return err
}
// Send adds compatibility with EmailClient, fromName/fromAddr are discarded, message.Text is used, addresses are Chat IDs as strings.
func (t *TelegramDaemon) Send(fromName, fromAddr string, message *Message, address ...string) error {
for _, addr := range address {
ChatID, err := strconv.ParseInt(addr, 10, 64)
if err != nil {
return err
}
msg := tg.NewMessage(ChatID, message.Text)
_, err = t.bot.Send(msg)
if err != nil {
return err
}
}
return nil
}
func (t *TelegramDaemon) Shutdown() {
t.Stopped = true
t.ShutdownChannel <- "Down"
<-t.ShutdownChannel
close(t.ShutdownChannel)
}