~mendelmaleh/tg_mcstatusbot

9f359cae75494383491ac8fd4269ab4e1f64a596 — Mendel E 4 months ago 0b71049 master
Use akita, use go.mod, move commands to their own functions in commands.go
4 files changed, 152 insertions(+), 129 deletions(-)

M .gitignore
A commands.go
A go.mod
M main.go
M .gitignore => .gitignore +1 -0
@@ 1,3 1,4 @@
config.toml
notes.md
db/
go.sum

A commands.go => commands.go +112 -0
@@ 0,0 1,112 @@
package main

import (
	"fmt"
	"strings"

	"git.sr.ht/~mendelmaleh/akita"
	"git.sr.ht/~mendelmaleh/mcstatus"

	"github.com/bobesa/go-domain-util/domainutil"
	"github.com/go-telegram-bot-api/telegram-bot-api/v5"
)

// TODO: user facing errors

func cmdStatus(cl Client, msg *tgbotapi.Message) (tgbotapi.Chattable, error) {
	db, mc := cl.Bitcask, cl.MCServerStatus

	chatid := int64toBytes(msg.Chat.ID)
	q := msg.CommandArguments()
	if q == "" && !db.Has(chatid) {
		return nil, nil // no args and no default
	}

	if q == "" {
		val, err := db.Get(chatid)
		if err != nil {
			return nil, err
		}

		q = string(val)
	} else {
		q = strings.Fields(q)[0]

		if msg.Chat.IsPrivate() {
			if err := db.Put(chatid, []byte(q)); err != nil {
				return nil, err
			}
		}
	}

	status, err := mc.GetStatus(q)
	if err != nil {
		return nil, err
	}

	return akita.NewMessage(msg, mcStatusString(q, status)), nil
}

func cmdDefault(cl Client, msg *tgbotapi.Message) (tgbotapi.Chattable, error) {
	bot, db := cl.BotAPI, cl.Bitcask

	if msg.Chat.IsPrivate() {
		return nil, nil // private chats can't set defaults
	}

	cm, err := bot.GetChatMember(tgbotapi.GetChatMemberConfig{tgbotapi.ChatConfigWithUser{
		ChatID: msg.Chat.ID,
		UserID: msg.From.ID,
	}})
	if err != nil {
		return nil, err
	}

	if !cm.IsAdministrator() && !cm.IsCreator() {
		return nil, err
	}

	args := strings.Fields(msg.CommandArguments())
	if len(args) < 1 || len(args[0]) < 1 {
		return nil, nil // no args
	}

	q, err := args[0], nil
	if q == "clear" {
		err = db.Delete(int64toBytes(msg.Chat.ID))
	} else {
		err = db.Put(int64toBytes(msg.Chat.ID), []byte(q))
	}

	return nil, err
}

func inlineQuery(cl Client, q *tgbotapi.InlineQuery) (tgbotapi.Chattable, error) {
	if dmn := domainutil.Domain(q.Query); dmn == "" {
		return nil, nil // ignore query
	}

	status, err := cl.MCServerStatus.GetStatus(q.Query)
	if err != nil {
		return nil, err
	}

	results := make([]interface{}, 1)
	results[0] = tgbotapi.InlineQueryResultArticle{
		Type:        "article", // must be
		ID:          q.ID,
		Title:       q.Query,
		Description: fmt.Sprintf("%s is %s", q.Query, mcStatusOnlineString(status)),
		ThumbURL:    mcstatus.IconURL(q.Query),
		InputMessageContent: tgbotapi.InputTextMessageContent{
			DisableWebPagePreview: true,
			ParseMode:             "html",
			Text:                  mcStatusString(q.Query, status),
		},
	}

	return tgbotapi.InlineConfig{
		InlineQueryID: q.ID,
		Results:       results,
	}, nil
}

A go.mod => go.mod +17 -0
@@ 0,0 1,17 @@
module git.sr.ht/~mendelmaleh/tg_mcstatusbot

go 1.15

require (
	git.sr.ht/~mendelmaleh/akita v0.0.0-20200823104149-2777b6c7c7e0
	git.sr.ht/~mendelmaleh/mcstatus v0.0.0-20200823092918-cb28e3030089
	github.com/bobesa/go-domain-util v0.0.0-20190911083921-4033b5f7dd89
	github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.0.0-rc1.0.20200723221353-2f7211a7085f
	github.com/gofrs/flock v0.7.3 // indirect
	github.com/pelletier/go-toml v1.8.0
	github.com/plar/go-adaptive-radix-tree v1.0.4 // indirect
	github.com/prologic/bitcask v0.3.6
	golang.org/x/exp v0.0.0-20200821190819-94841d0725da // indirect
	golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
	golang.org/x/text v0.3.3 // indirect
)

M main.go => main.go +22 -129
@@ 8,14 8,20 @@ import (
	"log"
	"strings"

	"git.sr.ht/~mendelmaleh/akita"
	"git.sr.ht/~mendelmaleh/mcstatus"

	"github.com/bobesa/go-domain-util/domainutil"
	"github.com/go-telegram-bot-api/telegram-bot-api"
	"github.com/go-telegram-bot-api/telegram-bot-api/v5"
	"github.com/pelletier/go-toml"
	"github.com/prologic/bitcask"
)

type Client struct {
	*akita.Akita
	*bitcask.Bitcask
	*mcstatus.MCServerStatus
}

type Config struct {
	Bot struct {
		Token string


@@ 77,137 83,24 @@ func main() {
	// setup mc
	mc := mcstatus.New()

	// setup bot
	bot, err := tgbotapi.NewBotAPI(config.Bot.Token)
	// setup akita
	akita, err := akita.New(config.Bot.Token)
	if err != nil {
		log.Panic(err)
	}

	log.Printf("Authorized on account %s", bot.Self.UserName)
	// bot.Debug = true
	// akita.Debug = true
	log.Printf("Authorized on account %s", akita.Self.UserName)

	updates, err := bot.GetUpdatesChan(tgbotapi.UpdateConfig{Timeout: 60})
	if err != nil {
		log.Panic(err)
	}
	cl := Client{akita, db, mc}

	for u := range updates {
		if q := u.InlineQuery; q != nil {
			if dmn := domainutil.Domain(q.Query); dmn == "" {
				continue
			}

			status, err := mc.GetStatus(q.Query)
			if err != nil {
				log.Println(err)
				continue
			}

			r := tgbotapi.InlineQueryResultArticle{
				Type:        "article", // must be
				ID:          q.ID,
				Title:       q.Query,
				Description: fmt.Sprintf("%s is %s", q.Query, mcStatusOnlineString(status)),
				ThumbURL:    mcstatus.IconURL(q.Query),
				InputMessageContent: tgbotapi.InputTextMessageContent{
					DisableWebPagePreview: true,
					ParseMode:             "html",
					Text:                  mcStatusString(q.Query, status),
				},
			}

			results := make([]interface{}, 1)
			results[0] = r

			api, err := bot.AnswerInlineQuery(tgbotapi.InlineConfig{
				InlineQueryID: q.ID,
				Results:       results,
			})

			if err != nil {
				log.Println(api, err)
			}

			continue
		}

		if m := u.Message; m != nil {
			if m.Command() == "status" {
				chatid := int64toBytes(m.Chat.ID)
				q := m.CommandArguments()
				if q == "" && !db.Has(chatid) {
					continue // TODO
				}

				if q == "" {
					val, err := db.Get(chatid)
					if err != nil {
						log.Println(err)
						continue // TODO
					}

					q = string(val)
				} else {
					q = strings.Fields(q)[0]

					if m.Chat.IsPrivate() {
						err := db.Put(chatid, []byte(q))
						if err != nil {
							log.Println(err)
							continue // TODO
						}
					}
				}

				status, err := mc.GetStatus(q)
				if err != nil {
					log.Println(err)
					continue // TODO
				}

				msg := tgbotapi.NewMessage(m.Chat.ID, mcStatusString(q, status))
				msg.ParseMode = "html"
				msg.DisableWebPagePreview = true

				if api, err := bot.Send(msg); err != nil {
					log.Println(api, err)
				}
			} else if m.Command() == "default" {
				if m.Chat.IsPrivate() {
					continue
				}

				chatmember, err := bot.GetChatMember(tgbotapi.ChatConfigWithUser{
					ChatID: m.Chat.ID,
					UserID: m.From.ID,
				})
				if err != nil {
					log.Println(err)
					continue
				}

				if !chatmember.IsAdministrator() && !chatmember.IsCreator() {
					continue // TODO
				}

				args := strings.Fields(m.CommandArguments())
				if len(args) < 1 || len(args[0]) < 1 {
					continue // TODO
				}

				q := args[0]
				if q == "clear" {
					if err := db.Delete(int64toBytes(m.Chat.ID)); err != nil {
						log.Println(err)
					}

					continue
				}

				if err := db.Put(int64toBytes(m.Chat.ID), []byte(q)); err != nil {
					log.Println(err)
				}
			}
		}
	}
	// register commands
	akita.Register("status", "get current status of minecraft server",
		func(u tgbotapi.Update) (tgbotapi.Chattable, error) { return cmdStatus(cl, u.Message) })
	akita.Register("default", "set the default server for the group, requires admin",
		func(u tgbotapi.Update) (tgbotapi.Chattable, error) { return cmdDefault(cl, u.Message) })

	akita.InlineQuery = func(u tgbotapi.Update) (tgbotapi.Chattable, error) { return inlineQuery(cl, u.InlineQuery) }

	akita.Route()
}