Compare commits

..

13 Commits

3 changed files with 395 additions and 318 deletions

63
cmd/app/db.go Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"errors"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
ID int64
State string
MsgCounter uint
RoleBitmask uint
}
func (u User) IsAdmin() bool {
return u.RoleBitmask&1 == 1
}
func (u User) IsEffectiveAdmin() bool {
return u.RoleBitmask&0b10 == 0b10
}
type BotContent struct {
gorm.Model
Literal string
Content string
}
func GetDB() (*gorm.DB, error) {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
return db, err
}
db.AutoMigrate(&User{})
db.AutoMigrate(&BotContent{})
return db, err
}
func (bc BotController) GetBotContentVerbose(Literal string) (string, error) {
var c BotContent
bc.db.First(&c, "Literal", Literal)
if c == (BotContent{}) {
return "[Unitialized] Init in Admin panel! Literal: " + Literal, errors.New("No content")
}
return c.Content, nil
}
func (bc BotController) GetBotContent(Literal string) string {
content, _ := bc.GetBotContentVerbose(Literal)
return content
}
func (bc BotController) SetBotContent(Literal string, Content string) {
c := BotContent{Literal: Literal, Content: Content}
bc.db.FirstOrCreate(&c, "Literal", Literal)
bc.db.Model(&c).Update("Content", Content)
}

View File

@ -2,44 +2,20 @@ package main
import (
"fmt"
"io"
"log"
"strings"
"strconv"
"errors"
"net/http"
"os"
"io"
"strconv"
"strings"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/akulij/ticketbot/config"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
ID int64
State string
MsgCounter uint
RoleBitmask uint
}
func (u User) IsAdmin() bool {
return u.RoleBitmask & 1 == 1
}
func (u User) IsEffectiveAdmin() bool {
return u.RoleBitmask & 0b10 == 0b10
}
type BotContent struct {
gorm.Model
Literal string
Content string
}
type BotController struct {
cfg config.Config
bot *tgbotapi.BotAPI
@ -51,17 +27,19 @@ func GetBotController() BotController {
cfg := config.GetConfig()
fmt.Printf("Token value: '%v'\n", cfg.BotToken)
fmt.Printf("Admin password: '%v'\n", cfg.AdminPass)
fmt.Printf("Admin ID: '%v'\n", *cfg.AdminID)
bot, err := tgbotapi.NewBotAPI(cfg.BotToken)
if err != nil {
log.Panic(err)
}
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{})
db.AutoMigrate(&BotContent{})
db, err := GetDB()
if err != nil {
log.Panic(err)
}
bot.Debug = true
bot.Debug = true // set true only while development, should be set to false in production
log.Printf("Authorized on account %s", bot.Self.UserName)
@ -73,24 +51,6 @@ func GetBotController() BotController {
return BotController{cfg: cfg, bot: bot, db: db, updates: updates}
}
func (bc BotController) GetBotContentVerbose(Literal string) (string, error) {
var c BotContent
bc.db.First(&c, "Literal", Literal)
if c == (BotContent{}) {
return "[Unitialized] Init in Admin panel! Literal: " + Literal, errors.New("No content")
}
return c.Content, nil
}
func (bc BotController) GetBotContent(Literal string) string {
content, _ := bc.GetBotContentVerbose(Literal)
return content
}
func (bc BotController) SetBotContent(Literal string, Content string) {
c := BotContent{Literal: Literal, Content: Content}
bc.db.FirstOrCreate(&c, "Literal", Literal)
bc.db.Model(&c).Update("Content", Content)
}
func main() {
var bc = GetBotController()
@ -100,28 +60,85 @@ func main() {
}
func ProcessUpdate(bc BotController, update tgbotapi.Update) {
if update.Message != nil {
handleMessage(bc, update)
} else if update.CallbackQuery != nil {
handleCallbackQuery(bc, update)
} else if update.ChannelPost != nil {
handleChannelPost(bc, update)
}
}
func handleMessage(bc BotController, update tgbotapi.Update) {
var UserID = update.Message.From.ID
var user User
bc.db.First(&user, "id", UserID)
if user == (User{}) {
log.Printf("New user: [%d]", UserID)
user = User{ID: UserID , State: "start"}
user = User{ID: UserID, State: "start"}
bc.db.Create(&user)
}
bc.db.Model(&user).Update("MsgCounter", user.MsgCounter + 1)
bc.db.Model(&user).Update("MsgCounter", user.MsgCounter+1)
log.Printf("User[%d] messages: %d", user.ID, user.MsgCounter)
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)
possibleCommand := strings.Split(update.Message.Text, " ")[0]
args := strings.Split(update.Message.Text, " ")[1:]
log.Printf("Args: %s", args)
if possibleCommand == "/start" {
switch {
case possibleCommand == "/start":
handleStartCommand(bc, update, user)
case possibleCommand == "/id" && user.IsAdmin():
bc.bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, strconv.FormatInt(update.Message.Chat.ID, 10)))
case possibleCommand == "/secret" && len(args) > 0 && args[0] == bc.cfg.AdminPass:
handleSecretCommand(bc, update, user)
case possibleCommand == "/panel" && user.IsAdmin():
handlePanelCommand(bc, update, user)
case possibleCommand == "/usermode" && user.IsEffectiveAdmin():
handleUserModeCommand(bc, update, user)
default:
handleDefaultMessage(bc, update, user)
}
}
func handleCallbackQuery(bc BotController, update tgbotapi.Update) {
var user User
bc.db.First(&user, "id", update.CallbackQuery.From.ID)
if update.CallbackQuery.Data == "leave_ticket_button" {
handleLeaveTicketButton(bc, update, user)
} else if user.IsEffectiveAdmin() {
handleAdminCallback(bc, update, user)
}
if user.IsAdmin() && update.CallbackQuery.Data == "panel" {
handlePanelCallback(bc, update, user)
}
canswer := tgbotapi.NewCallback(update.CallbackQuery.ID, "")
bc.bot.Send(canswer)
}
func handleChannelPost(bc BotController, update tgbotapi.Update) {
post := update.ChannelPost
if post.Text == "setchannelid" {
bc.SetBotContent("channelid", strconv.FormatInt(post.SenderChat.ID, 10))
var admins []User
bc.db.Where("role_bitmask & 1 = ?", 1).Find(&admins)
for _, admin := range admins {
bc.bot.Send(tgbotapi.NewMessage(admin.ID, "ChannelID is set to "+strconv.FormatInt(post.SenderChat.ID, 10)))
delcmd := tgbotapi.NewDeleteMessage(post.SenderChat.ID, post.MessageID)
bc.bot.Send(delcmd)
}
}
}
// Helper functions for specific commands
func handleStartCommand(bc BotController, update tgbotapi.Update, user User) {
bc.db.Model(&user).Update("state", "start")
kbd := tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
@ -138,10 +155,10 @@ func ProcessUpdate(bc BotController, update tgbotapi.Update) {
),
)
}
img, err := bc.GetBotContentVerbose("preview_image")
if err != nil || img == "" {
msg := tgbotapi.NewMessage(update.Message.Chat.ID, bc.GetBotContent("start"))
// msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Hello, [user](tg://user?id=958170391)")
msg.ParseMode = "markdown"
msg.ReplyMarkup = kbd
bc.bot.Send(msg)
@ -151,19 +168,18 @@ func ProcessUpdate(bc BotController, update tgbotapi.Update) {
msg.ReplyMarkup = kbd
bc.bot.Send(msg)
}
}
} else if possibleCommand == "/id" && user.IsAdmin() {
log.Printf("THERe")
bc.bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, strconv.FormatInt(update.Message.Chat.ID, 10)))
} else if possibleCommand == "/secret" && len(args) > 0 && args[0] == bc.cfg.AdminPass {
func handleSecretCommand(bc BotController, update tgbotapi.Update, user User) {
bc.db.Model(&user).Update("state", "start")
bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask | 0b11) // set real admin ID (0b1) and effective admin toggle (0b10)
bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask|0b11) // set real admin ID (0b1) and effective admin toggle (0b10)
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "You are admin now!")
bc.bot.Send(msg)
}
} else if possibleCommand == "/panel" && user.IsAdmin() {
func handlePanelCommand(bc BotController, update tgbotapi.Update, user User) {
if !user.IsEffectiveAdmin() {
bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask | 0b10)
bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask|0b10)
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "You was in usermode, turned back to admin mode...")
bc.bot.Send(msg)
}
@ -181,49 +197,21 @@ func ProcessUpdate(bc BotController, update tgbotapi.Update) {
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Выберите пункт для изменения")
msg.ReplyMarkup = kbd
bc.bot.Send(msg)
}
} else if possibleCommand == "/usermode" && user.IsEffectiveAdmin() {
bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask & (^uint(0b10)))
func handleUserModeCommand(bc BotController, update tgbotapi.Update, user User) {
bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask&(^uint(0b10)))
log.Printf("Set role bitmask (%b) for user: %d", user.RoleBitmask, user.ID)
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Simulating user experience!")
bc.bot.Send(msg)
} else if user.IsEffectiveAdmin() && user.State != "leaveticket" {
if user.State != "start" {
if strings.HasPrefix(user.State, "imgset:") {
Literal := strings.Split(user.State, ":")[1]
if update.Message.Text == "unset" {
var l BotContent
bc.db.First(&l, "Literal", Literal)
bc.SetBotContent(Literal, "")
}
maxsize := 0
fileid := ""
for _, p := range update.Message.Photo {
if p.FileSize > maxsize {
fileid = p.FileID
maxsize = p.FileSize
}
}
bc.SetBotContent(Literal, fileid)
bc.db.Model(&user).Update("state", "start")
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Succesfully set new image!")
bc.bot.Send(msg)
} else if strings.HasPrefix(user.State, "stringset:") {
Literal := strings.Split(user.State, ":")[1]
bc.SetBotContent(Literal, update.Message.Text)
bc.db.Model(&user).Update("state", "start")
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Succesfully set new text!")
bc.bot.Send(msg)
}
}
} else {
}
func handleDefaultMessage(bc BotController, update tgbotapi.Update, user User) {
if user.State == "leaveticket" {
f := update.Message.From
ticket := fmt.Sprintf("User: %s %s\nUsername: %s\nText:\n",
f.FirstName, f.LastName,
f.UserName)
// Offset := len(ticket)
// Length := len(update.Message.Text)
ticket += update.Message.Text
chatidstr, err := bc.GetBotContentVerbose("supportchatid")
if err != nil {
@ -251,12 +239,39 @@ func ProcessUpdate(bc BotController, update tgbotapi.Update) {
bc.db.Model(&user).Update("state", "start")
msg := tgbotapi.NewMessage(update.Message.Chat.ID, bc.GetBotContent("sended_notify"))
bc.bot.Send(msg)
} else if user.IsEffectiveAdmin() {
if user.State != "start" {
if strings.HasPrefix(user.State, "imgset:") {
Literal := strings.Split(user.State, ":")[1]
if update.Message.Text == "unset" {
var l BotContent
bc.db.First(&l, "Literal", Literal)
bc.SetBotContent(Literal, "")
}
maxsize := 0
fileid := ""
for _, p := range update.Message.Photo {
if p.FileSize > maxsize {
fileid = p.FileID
maxsize = p.FileSize
}
}
} else if update.CallbackQuery != nil {
var user User
bc.db.First(&user, "id", update.CallbackQuery.From.ID)
if update.CallbackQuery.Data == "leave_ticket_button" {
bc.SetBotContent(Literal, fileid)
bc.db.Model(&user).Update("state", "start")
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Successfully set new image!")
bc.bot.Send(msg)
} else if strings.HasPrefix(user.State, "stringset:") {
Literal := strings.Split(user.State, ":")[1]
bc.SetBotContent(Literal, update.Message.Text)
bc.db.Model(&user).Update("state", "start")
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Successfully set new text!")
bc.bot.Send(msg)
}
}
}
}
func handleLeaveTicketButton(bc BotController, update tgbotapi.Update, user User) {
chatidstr, err := bc.GetBotContentVerbose("channelid")
if err != nil {
var admins []User
@ -309,21 +324,23 @@ func ProcessUpdate(bc BotController, update tgbotapi.Update) {
}
bc.bot.Send(msg)
}
} else if user.IsEffectiveAdmin() {
}
func handleAdminCallback(bc BotController, update tgbotapi.Update, user User) {
if strings.HasPrefix(update.CallbackQuery.Data, "update:") {
Label := strings.Split(update.CallbackQuery.Data, ":")[1]
if Label == "preview_image" {
bc.db.Model(&user).Update("state", "imgset:" + Label)
bc.db.Model(&user).Update("state", "imgset:"+Label)
} else {
bc.db.Model(&user).Update("state", "stringset:" + Label)
bc.db.Model(&user).Update("state", "stringset:"+Label)
}
bc.bot.Send(tgbotapi.NewMessage(user.ID, "Send me asset (text or picture (NOT as file))"))
}
}
if user.IsAdmin() {
if update.CallbackQuery.Data == "panel" {
}
func handlePanelCallback(bc BotController, update tgbotapi.Update, user User) {
if !user.IsEffectiveAdmin() {
bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask | 0b10)
bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask|0b10)
msg := tgbotapi.NewMessage(user.ID, "You was in usermode, turned back to admin mode...")
bc.bot.Send(msg)
}
@ -341,24 +358,6 @@ func ProcessUpdate(bc BotController, update tgbotapi.Update) {
msg := tgbotapi.NewMessage(user.ID, "Выберите пункт для изменения")
msg.ReplyMarkup = kbd
bc.bot.Send(msg)
}
}
canswer := tgbotapi.NewCallback(update.CallbackQuery.ID, "")
bc.bot.Send(canswer)
} else if update.ChannelPost != nil { // TODO
post := update.ChannelPost
if post.Text == "setchannelid" {
bc.SetBotContent("channelid", strconv.FormatInt(post.SenderChat.ID, 10))
var admins []User
bc.db.Where("role_bitmask & 1 = ?", 1).Find(&admins)
for _, admin := range admins {
bc.bot.Send(tgbotapi.NewMessage(admin.ID, "ChannelID is set to " + strconv.FormatInt(post.SenderChat.ID, 10)))
delcmd := tgbotapi.NewDeleteMessage(post.SenderChat.ID, post.MessageID)
bc.bot.Send(delcmd)
}
}
}
}
func DownloadFile(filepath string, url string) error {
@ -381,3 +380,17 @@ func DownloadFile(filepath string, url string) error {
_, err = io.Copy(out, resp.Body)
return err
}
func notifyAdminAboutError(bc BotController, errorMessage string) {
// Check if AdminID is set in the config
adminID := *bc.cfg.AdminID
if adminID != 0 {
msg := tgbotapi.NewMessage(
adminID,
fmt.Sprintf("Error occurred: %s", errorMessage),
)
bc.bot.Send(msg)
} else {
log.Println("AdminID is not set in the configuration.")
}
}

View File

@ -9,7 +9,8 @@ import (
type Config struct {
BotToken string `env:"BOTTOKEN, required"`
AdminPass string `env:"ADMINPASSWORD, required"`
AdminPass string `env:"ADMINPASSWORD, required"` // to activate admin privileges in bot type command: /secret `AdminPass`
AdminID *int64 `env:"ADMINID"` // optional admin ID for notifications
}
func GetConfig() Config {