This commit is contained in:
Nefrace 2024-07-07 01:52:38 +03:00
commit 7420047491
8 changed files with 434 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
bot.db
.env
.vscode

22
go.mod Normal file
View File

@ -0,0 +1,22 @@
module nefrace.ru/kickbot.ng
go 1.22.5
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-telegram/bot v1.5.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
go.etcd.io/bbolt v1.3.10 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/sys v0.15.0 // indirect
modernc.org/libc v1.37.6 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/sqlite v1.28.0 // indirect
)

39
go.sum Normal file
View File

@ -0,0 +1,39 @@
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-telegram/bot v1.5.0 h1:q31yJ8iajFG54b17TgSs/Brl2YkWziRjf4Au5pe3xV0=
github.com/go-telegram/bot v1.5.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw=
modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=

40
src/botutils.go Normal file
View File

@ -0,0 +1,40 @@
package main
import (
"context"
"fmt"
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
)
func GetChatFullName(chat models.Chat) string {
if chat.Title != "" {
return chat.Title
}
if chat.FirstName != "" {
if chat.LastName != "" {
return fmt.Sprint(chat.FirstName, " ", chat.LastName)
}
return chat.FirstName
}
return ""
}
func GetUserFullName(user models.User) string {
if user.FirstName != "" {
if user.LastName != "" {
return fmt.Sprint(user.FirstName, " ", user.LastName)
}
return user.FirstName
}
return ""
}
func FetchMemberFromChat(ctx context.Context, b *bot.Bot, chatID int64, userID int64) (*models.ChatMember, error) {
return b.GetChatMember(ctx, &bot.GetChatMemberParams{ChatID: chatID, UserID: userID})
}
func IsAdmin(member models.ChatMember) bool {
return member.Administrator != nil || member.Owner != nil
}

162
src/db.go Normal file
View File

@ -0,0 +1,162 @@
package main
import (
"log"
"github.com/jmoiron/sqlx"
)
var schema = `
create table if not exists chats
(
id INTEGER PRIMARY KEY,
name TEXT,
username TEXT,
topic INTEGER,
active INTEGER
);
create table if not exists activations
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT
);
create table if not exists messagesToDelete
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id INTEGER,
chat_id INTEGER,
delete_date INTEGER
);
create table if not exists users
(
id INTEGER PRIMARY KEY,
name TEXT,
username TEXT
);
create table if not exists bans
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
reason TEXT,
ban_date INTEGER,
unban_date INTEGER,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
create table if not exists captchas
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
chat_id INTEGER,
message_id INTEGER,
correct_answer INTEGER,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
`
type ChatSchema struct {
Id int64 `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Username string `json:"username" db:"username"`
Topic int64 `json:"topic" db:"topic"`
Active bool `json:"active" db:"active"`
}
type MessageToDelete struct {
Id int64 `db:"id"`
MessageId int `db:"message_id"`
ChatId int64 `db:"chat_id"`
DeleteDate int64 `db:"delete_date"`
}
type User struct {
Id int64 `db:"id"`
Name string `db:"name"`
Username string `db:"username"`
}
var db *sqlx.DB
func InitDb() error {
newdb, err := sqlx.Connect("sqlite", "./bot.db?_time_format=sqlite")
if err != nil {
return err
}
db = newdb
return nil
}
func NewChat(chat ChatSchema) error {
_, err := db.NamedExec(`insert into chats (id, name, username, topic, active) values (:id, :name, :username, :topic, :active)`, chat)
return err
}
func IsChatExists(id int64) bool {
var exists bool
err := db.Get(&exists, `SELECT exists(SELECT 1 FROM chats WHERE id = $1);`, id)
if err != nil {
log.Println("Can't check existing of chat", id, err)
return false
}
return exists
}
func IsChatActive(id int64) bool {
var active bool
err := db.Get(&active, `SELECT active from chats where id = $1`, id)
if err != nil {
return false
}
return active
}
func GetChatById(id int64) (ChatSchema, error) {
c := ChatSchema{}
err := db.Get(&c, `select * from chats where id = $1`, id)
return c, err
}
func NewActivation(code string) error {
_, err := db.Exec(`insert into activations (code) values ($1)`, code)
return err
}
func UseActivation(code string) bool {
_, err := db.Exec(`delete from activations where code = $1`, code)
return err == nil
}
func ActivateChat(id int64) error {
_, err := db.Exec(`update chats set active = 1 where id = $1`, id)
return err
}
func AddMessageToDelete(msg MessageToDelete) error {
_, err := db.NamedExec(`insert into messagesToDelete (message_id, chat_id, delete_date) values (:message_id, :chat_id, :delete_date)`, msg)
return err
}
func NewUser(user User) error {
_, err := db.NamedExec(`insert into users (id, name, username) values (:id, :name, :username)`, user)
return err
}
func IsUserExists(id int64) bool {
var exists bool
err := db.Get(&exists, `SELECT exists(SELECT 1 FROM users WHERE id = $1);`, id)
if err != nil {
log.Println("Can't check existing of chat", id, err)
return false
}
return exists
}
func GetUserById(id int64) (User, error) {
c := User{}
err := db.Get(&c, `select * from users where id = $1`, id)
return c, err
}

64
src/handlers.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"context"
"log"
"strings"
"time"
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
)
func defaultHandler(ctx context.Context, b *bot.Bot, update *models.Update) {
// b.SendMessage(ctx, &bot.SendMessageParams{
// ChatID: update.Message.Chat.ID,
// Text: "hello!",
// })
log.Println(*update)
}
func registerChat(ctx context.Context, b *bot.Bot, update *models.Update) {
msg := update.Message
log.Println("registering", msg.Chat.ID)
m, err := FetchMemberFromChat(ctx, b, msg.Chat.ID, msg.From.ID)
if err == nil {
if !IsAdmin(*m) {
log.Println("register: user is not admin")
return
}
args := strings.Split(msg.Text, " ")
if len(args) == 1 {
log.Println("register: there's no code")
return
}
if !UseActivation(args[1]) {
log.Println("register: wrong code")
return
}
if err := ActivateChat(msg.Chat.ID); err != nil {
log.Println("Error activating chat: ", err)
return
}
b.DeleteMessage(ctx, &bot.DeleteMessageParams{ChatID: msg.Chat.ID, MessageID: msg.ID})
sent, err := b.SendMessage(ctx, &bot.SendMessageParams{ChatID: msg.Chat.ID, Text: "Чат зарегистрирован", MessageThreadID: msg.MessageThreadID})
if err == nil {
err := AddMessageToDelete(MessageToDelete{MessageId: sent.ID, ChatId: msg.Chat.ID, DeleteDate: time.Now().Add(1 * time.Minute).Unix()})
if err != nil {
log.Println("register: failed to add to delete", err)
}
}
} else {
log.Println("register: error", err)
}
}
func handleNewJoined(ctx context.Context, b *bot.Bot, u *models.Update) {
for _, user := range u.Message.NewChatMembers {
log.Println(user)
}
}
func banUser(ctx context.Context, b *bot.Bot, u *models.Update) {
}

40
src/main.go Normal file
View File

@ -0,0 +1,40 @@
package main
import (
"context"
"os"
"os/signal"
_ "github.com/glebarez/go-sqlite"
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := InitDb(); err != nil {
panic(err)
}
defer db.Close()
db.MustExec(schema)
opts := []bot.Option{
bot.WithMiddlewares(logChats, logUsers, checkRegistered),
bot.WithDefaultHandler(defaultHandler),
}
b, err := bot.New(os.Getenv("TG_TOKEN"), opts...)
if err != nil {
panic(err)
}
b.RegisterHandler(bot.HandlerTypeMessageText, "/register", bot.MatchTypePrefix, registerChat)
b.RegisterHandler(bot.HandlerTypeMessageText, "/ban", bot.MatchTypePrefix, banUser)
b.RegisterHandlerMatchFunc(func(update *models.Update) bool {
return update.Message != nil && len(update.Message.NewChatMembers) > 0
}, handleNewJoined)
b.Start(ctx)
}

63
src/middleware.go Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"context"
"log"
"strings"
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
)
func checkRegistered(next bot.HandlerFunc) bot.HandlerFunc {
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
if update.Message == nil {
next(ctx, b, update)
return
}
chat := update.Message.Chat
if chat.Type == "private" {
return
}
if !IsChatActive(chat.ID) && !strings.HasPrefix(update.Message.Text, "/register") {
log.Println("checkRegistered: not registered", chat.ID)
return
}
next(ctx, b, update)
}
}
func logChats(next bot.HandlerFunc) bot.HandlerFunc {
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
if update.Message == nil {
next(ctx, b, update)
return
}
chat := update.Message.Chat
chatName := GetChatFullName(chat)
if !IsChatExists(chat.ID) {
db.MustExec(`insert into chats (id, username, name, topic, active) values ($1, $2, $3, $4, $5);`, chat.ID, chat.Username, chatName, 0, false)
} else {
db.MustExec(`update chats set username = $2, name = $3 where id = $1;`, chat.ID, chat.Username, chatName)
}
next(ctx, b, update)
}
}
func logUsers(next bot.HandlerFunc) bot.HandlerFunc {
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
if update.Message == nil {
next(ctx, b, update)
return
}
user := update.Message.From
userFullName := GetUserFullName(*user)
if !IsUserExists(user.ID) {
db.MustExec(`insert into users (id, username, name) values ($1, $2, $3);`, user.ID, user.Username, userFullName, 0, false)
} else {
db.MustExec(`update users set username = $2, name = $3 where id = $1;`, user.ID, user.Username, userFullName)
}
next(ctx, b, update)
}
}