hdkv
/
i2_bot
Archived
1
Fork 0

Reminders for league. Now just reminders, without attack button

master
Vladimir Hodakov 2017-12-23 01:40:36 +04:00
parent a2e70332de
commit 706dc3e944
19 changed files with 381 additions and 117 deletions

View File

@ -13,6 +13,7 @@ import (
"lab.pztrn.name/fat0troll/i2_bot/lib/orders"
"lab.pztrn.name/fat0troll/i2_bot/lib/pinner"
"lab.pztrn.name/fat0troll/i2_bot/lib/pokedexer"
"lab.pztrn.name/fat0troll/i2_bot/lib/reminder"
"lab.pztrn.name/fat0troll/i2_bot/lib/router"
"lab.pztrn.name/fat0troll/i2_bot/lib/squader"
"lab.pztrn.name/fat0troll/i2_bot/lib/statistics"
@ -44,6 +45,7 @@ func main() {
users.New(c)
statistics.New(c)
orders.New(c)
reminder.New(c)
c.Log.Info("=======================")
c.Log.Info("= i2_bot initialized. =")
@ -66,6 +68,8 @@ func main() {
}
} else if update.InlineQuery != nil {
c.Router.RouteInline(&update)
} else if update.CallbackQuery != nil {
c.Router.RouteCallback(&update)
} else if update.ChosenInlineResult != nil {
c.Log.Debug(update.ChosenInlineResult.ResultID)
} else {

View File

@ -16,6 +16,7 @@ import (
"lab.pztrn.name/fat0troll/i2_bot/lib/orders/ordersinterface"
"lab.pztrn.name/fat0troll/i2_bot/lib/pinner/pinnerinterface"
"lab.pztrn.name/fat0troll/i2_bot/lib/pokedexer/pokedexerinterface"
"lab.pztrn.name/fat0troll/i2_bot/lib/reminder/reminderinterface"
"lab.pztrn.name/fat0troll/i2_bot/lib/router/routerinterface"
"lab.pztrn.name/fat0troll/i2_bot/lib/squader/squaderinterface"
"lab.pztrn.name/fat0troll/i2_bot/lib/statistics/statisticsinterface"
@ -43,6 +44,7 @@ type Context struct {
Broadcaster broadcasterinterface.BroadcasterInterface
Welcomer welcomerinterface.WelcomerInterface
Pinner pinnerinterface.PinnerInterface
Reminder reminderinterface.ReminderInterface
Chatter chatterinterface.ChatterInterface
Squader squaderinterface.SquaderInterface
Users usersinterface.UsersInterface
@ -137,6 +139,12 @@ func (c *Context) RegisterPinnerInterface(pi pinnerinterface.PinnerInterface) {
c.Pinner.Init()
}
// RegisterReminderInterface registering reminder interface in application
func (c *Context) RegisterReminderInterface(ri reminderinterface.ReminderInterface) {
c.Reminder = ri
c.Reminder.Init()
}
// RegisterForwarderInterface registers forwarder interface in application
func (c *Context) RegisterForwarderInterface(fi forwarderinterface.ForwarderInterface) {
c.Forwarder = fi

View File

@ -10,8 +10,8 @@ import (
"path/filepath"
)
// VERSION is the urrent bot's version
const VERSION = "0.6"
// VERSION is the current bot's version
const VERSION = "0.6.5"
// DatabaseConnection handles database connection settings in config.yaml
type DatabaseConnection struct {

16
lib/dbmapping/alarms.go Normal file
View File

@ -0,0 +1,16 @@
// i2_bot Instinct PokememBro Bot
// Copyright (c) 2017 Vladimir "fat0troll" Hodakov
package dbmapping
import (
"time"
)
// Alarm is a struct, which represents `alarms` table item in databse.
type Alarm struct {
ID int `db:"id"`
PlayerID int `db:"player_id"`
TurnirNumber int `db:"turnir_number"` // From 1 to 12
CreatedAt time.Time `db:"created_at"`
}

View File

@ -0,0 +1,38 @@
// i2_bot Instinct PokememBro Bot
// Copyright (c) 2017 Vladimir "fat0troll" Hodakov
package migrations
import (
"database/sql"
)
// CreateAlarmsUp creates `alarms` table
func CreateAlarmsUp(tx *sql.Tx) error {
request := "CREATE TABLE `alarms` ("
request += "`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID настройки оповещения',"
request += "`player_id` int(11) NOT NULL COMMENT 'Игрок, которому отправляется оповещение',"
request += "`turnir_number` int(11) NOT NULL COMMENT 'Номер турнира (от 1 до 12)',"
request += "`created_at` datetime NOT NULL COMMENT 'Добавлен в базу',"
request += "PRIMARY KEY (`id`),"
request += "UNIQUE KEY `id` (`id`),"
request += "KEY `alarms_player_id` (`player_id`),"
request += "KEY `alarms_turnir_number` (`turnir_number`)"
request += ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='Оповещения на Турнир лиг'"
_, err := tx.Exec(request)
if err != nil {
return err
}
return nil
}
// CreateAlarmsDown drops `alarms` table
func CreateAlarmsDown(tx *sql.Tx) error {
_, err := tx.Exec("DROP TABLE `alarms`")
if err != nil {
return err
}
return nil
}

View File

@ -40,6 +40,7 @@ func (m *Migrations) Init() {
goose.AddNamedMigration("27_add_new_weapon.go", AddNewWeaponUp, AddNewWeaponDown)
goose.AddNamedMigration("28_fix_locations.go", FixLocationsUp, FixLocationsDown)
goose.AddNamedMigration("29_fix_leagues_names.go", FixLeaguesNamesUp, FixLeaguesNamesDown)
goose.AddNamedMigration("30_create_alarms.go", CreateAlarmsUp, CreateAlarmsUp)
}
// Migrate migrates database to current version

View File

@ -25,6 +25,4 @@ func New(ac *appcontext.Context) {
// Init is an initialization function for pinner
func (p *Pinner) Init() {
c.Log.Info("Initializing Pinner...")
c.Cron.AddFunc("0 55 4-23/2 * * *", p.PinBattleAlert)
}

View File

@ -5,28 +5,17 @@ package pinner
import (
"github.com/go-telegram-bot-api/telegram-bot-api"
"lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping"
"strconv"
"strings"
)
// PinMessageToAllChats pins message to all groups where bot exist
func (p *Pinner) PinMessageToAllChats(update *tgbotapi.Update) string {
func (p *Pinner) execMassMessagePin(update *tgbotapi.Update, groupChats []dbmapping.Chat) string {
messageToPin := update.Message.CommandArguments()
if messageToPin == "" {
return "fail"
}
groupChats, ok := c.Chatter.GetAllGroupChats()
if !ok {
return "fail"
}
for i := range groupChats {
if groupChats[i].ChatType == "supergroup" {
message := messageToPin + "\n\n"
message += "© " + update.Message.From.FirstName + " " + update.Message.From.LastName
message += " (@" + update.Message.From.UserName + ")"
message += "© " + c.Users.GetPrettyName(update.Message.From)
msg := tgbotapi.NewMessage(groupChats[i].TelegramID, message)
msg.ParseMode = "Markdown"
@ -60,7 +49,7 @@ func (p *Pinner) PinMessageToAllChats(update *tgbotapi.Update) string {
}
}
message := "*Ваше сообщение отправлено и запинено во все чаты, где сидит бот.*\n\n"
message := "*Ваше сообщение отправлено и запинено в чаты, где сидит бот.*\n\n"
message += "Текст отправленного сообщения:\n\n"
message += messageToPin
@ -72,6 +61,22 @@ func (p *Pinner) PinMessageToAllChats(update *tgbotapi.Update) string {
return "ok"
}
// PinMessageToAllChats pins message to all groups where bot exist
func (p *Pinner) PinMessageToAllChats(update *tgbotapi.Update) string {
messageToPin := update.Message.CommandArguments()
if messageToPin == "" {
return "fail"
}
groupChats, ok := c.Chatter.GetAllGroupChats()
if !ok {
return "fail"
}
return p.execMassMessagePin(update, groupChats)
}
// PinMessageToSomeChats pins message to selected groups where bot exist
func (p *Pinner) PinMessageToSomeChats(update *tgbotapi.Update) string {
commandArgs := update.Message.CommandArguments()
@ -101,84 +106,5 @@ func (p *Pinner) PinMessageToSomeChats(update *tgbotapi.Update) string {
}
c.Log.Debug("Got " + strconv.Itoa(len(groupChats)) + " group chats...")
for i := range groupChats {
if groupChats[i].ChatType == "supergroup" {
message := messageToPin + "\n\n"
message += "© " + update.Message.From.FirstName + " " + update.Message.From.LastName
message += " (@" + update.Message.From.UserName + ")"
msg := tgbotapi.NewMessage(groupChats[i].TelegramID, message)
msg.ParseMode = "Markdown"
pinnableMessage, err := c.Bot.Send(msg)
if err != nil {
c.Log.Error(err.Error())
message := "*Ваше сообщение не отправлено.*\n\n"
message += "Обычно это связано с тем, что нарушена разметка Markdown. "
message += "К примеру, если вы хотели использовать нижнее\\_подчёркивание, то печатать его надо так — \\\\_. То же самое касается всех управляющих разметкой символов в Markdown в случае, если вы их хотите использовать как текст, а не как управляющий символ Markdown."
msg := tgbotapi.NewMessage(update.Message.Chat.ID, message)
msg.ParseMode = "Markdown"
c.Bot.Send(msg)
return "fail"
}
pinChatMessageConfig := tgbotapi.PinChatMessageConfig{
ChatID: pinnableMessage.Chat.ID,
MessageID: pinnableMessage.MessageID,
DisableNotification: true,
}
_, err = c.Bot.PinChatMessage(pinChatMessageConfig)
if err != nil {
c.Log.Error(err.Error())
}
}
}
message := "*Ваше сообщение отправлено и запинено во все чаты, где сидит бот.*\n\n"
message += "Текст отправленного сообщения:\n\n"
message += messageToPin
msg := tgbotapi.NewMessage(update.Message.Chat.ID, message)
msg.ParseMode = "Markdown"
c.Bot.Send(msg)
return "ok"
}
// PinBattleAlert pins to all squads 'battle alert' at :55 of every even hour
// Even hours are in Moscow timezone
func (p *Pinner) PinBattleAlert() {
c.Log.Debug("> Cron invoked PinBattleAlert()")
message := "*Турнир Лиги покемемов состоится через 5 минут!*\nБоевая готовность, отряд!"
groupChats, _ := c.Squader.GetAllSquadChats()
for i := range groupChats {
if groupChats[i].ChatType == "supergroup" {
msg := tgbotapi.NewMessage(groupChats[i].TelegramID, message)
msg.ParseMode = "Markdown"
pinnableMessage, err := c.Bot.Send(msg)
if err != nil {
c.Log.Error(err.Error())
}
pinChatMessageConfig := tgbotapi.PinChatMessageConfig{
ChatID: pinnableMessage.Chat.ID,
MessageID: pinnableMessage.MessageID,
DisableNotification: false,
}
_, err = c.Bot.PinChatMessage(pinChatMessageConfig)
if err != nil {
c.Log.Error(err.Error())
}
}
}
return p.execMassMessagePin(update, groupChats)
}

View File

@ -7,12 +7,10 @@ import (
"github.com/go-telegram-bot-api/telegram-bot-api"
)
// PinnerInterface implements Pinner for importing via appcontex
// PinnerInterface implements Pinner for importing via appcontext
type PinnerInterface interface {
Init()
PinBattleAlert()
PinMessageToSomeChats(update *tgbotapi.Update) string
PinMessageToAllChats(update *tgbotapi.Update) string
}

30
lib/reminder/exported.go Normal file
View File

@ -0,0 +1,30 @@
// i2_bot Instinct PokememBro Bot
// Copyright (c) 2017 Vladimir "fat0troll" Hodakov
package reminder
import (
"lab.pztrn.name/fat0troll/i2_bot/lib/appcontext"
"lab.pztrn.name/fat0troll/i2_bot/lib/reminder/reminderinterface"
)
var (
c *appcontext.Context
)
// Reminder is a function-handling struct for Reminder
type Reminder struct{}
// New is a appcontext initialization function
func New(ac *appcontext.Context) {
c = ac
r := &Reminder{}
c.RegisterReminderInterface(reminderinterface.ReminderInterface(r))
}
// Init is an initialization function for reminder
func (r *Reminder) Init() {
c.Log.Info("Initializing Reminder...")
c.Cron.AddFunc("0 55 0-23/2 * * *", r.SendReminders)
}

73
lib/reminder/reminder.go Normal file
View File

@ -0,0 +1,73 @@
// i2_bot Instinct PokememBro Bot
// Copyright (c) 2017 Vladimir "fat0troll" Hodakov
package reminder
import (
"github.com/go-telegram-bot-api/telegram-bot-api"
"lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping"
"strconv"
)
func (r *Reminder) getRemindersForUser(playerRaw *dbmapping.Player) ([]dbmapping.Alarm, bool) {
alarmsList := []dbmapping.Alarm{}
err := c.Db.Select(&alarmsList, "SELECT * FROM alarms WHERE player_id=?", playerRaw.ID)
if err != nil {
c.Log.Error(err.Error())
return alarmsList, false
}
return alarmsList, true
}
func (r *Reminder) formatRemindersButtons(playerRaw *dbmapping.Player) tgbotapi.InlineKeyboardMarkup {
currentAlarms, _ := r.getRemindersForUser(playerRaw)
alarmExist := make(map[string]string)
for i := range currentAlarms {
alarmExist[strconv.Itoa(currentAlarms[i].TurnirNumber)] = "enabled"
}
keyboard := tgbotapi.InlineKeyboardMarkup{}
rows := make(map[int][]tgbotapi.InlineKeyboardButton)
rows[0] = []tgbotapi.InlineKeyboardButton{}
rows[1] = []tgbotapi.InlineKeyboardButton{}
rows[2] = []tgbotapi.InlineKeyboardButton{}
for i := 1; i <= 12; i++ {
hours := 2 * (i - 1)
if alarmExist[strconv.Itoa(i)] != "" {
hoursStr := "✅ "
hoursStr += strconv.Itoa(hours) + ":55"
btn := tgbotapi.NewInlineKeyboardButtonData(hoursStr, "disable_reminder_"+strconv.Itoa(i))
rows[(i-1)/4] = append(rows[(i-1)/4], btn)
} else {
hoursStr := "🚫 "
hoursStr += strconv.Itoa(hours) + ":55"
btn := tgbotapi.NewInlineKeyboardButtonData(hoursStr, "enable_reminder_"+strconv.Itoa(i))
rows[(i-1)/4] = append(rows[(i-1)/4], btn)
}
}
for i := range rows {
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, rows[i])
}
return keyboard
}
func (r *Reminder) formatRemindersMessageText(playerRaw *dbmapping.Player) string {
message := "*Ваши напоминания о битвах:*\n"
message += "За пять минут до битвы бот может присылать вам в личные сообщения напоминание о том, "
message += "что битва скоро состоится, и стоит встать на атаку.\n"
message += "Кнопками ниже вы можете настроить, к каким из битв вас оповещать. Время московское.\n\n"
currentAlarms, ok := r.getRemindersForUser(playerRaw)
if !ok {
message += "Не удалось получить настройки оповещений из базы. Ошибка."
} else {
message += "Установлено оповещений: " + strconv.Itoa(len(currentAlarms))
}
return message
}

View File

@ -0,0 +1,21 @@
// i2_bot Instinct PokememBro Bot
// Copyright (c) 2017 Vladimir "fat0troll" Hodakov
package reminderinterface
import (
"github.com/go-telegram-bot-api/telegram-bot-api"
"lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping"
)
// ReminderInterface implements Reminder for importing via appcontext
type ReminderInterface interface {
Init()
AlarmsList(update *tgbotapi.Update, playerRaw *dbmapping.Player) string
CreateAlarmSetting(update *tgbotapi.Update, playerRaw *dbmapping.Player) string
DestroyAlarmSetting(update *tgbotapi.Update, playerRaw *dbmapping.Player) string
SendReminders()
}

View File

@ -0,0 +1,25 @@
// i2_bot Instinct PokememBro Bot
// Copyright (c) 2017 Vladimir "fat0troll" Hodakov
package reminder
import (
"github.com/go-telegram-bot-api/telegram-bot-api"
"lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping"
)
// AlarmsList lists all alarms for user with buttons to enable/disable each of available alarms
func (r *Reminder) AlarmsList(update *tgbotapi.Update, playerRaw *dbmapping.Player) string {
msg := tgbotapi.MessageConfig{}
msg.Text = r.formatRemindersMessageText(playerRaw)
msg.ParseMode = "Markdown"
msg.ChatID = update.Message.Chat.ID
remindersMsg, _ := c.Bot.Send(msg)
keyboard := r.formatRemindersButtons(playerRaw)
buttonsUpdate := tgbotapi.NewEditMessageReplyMarkup(update.Message.Chat.ID, remindersMsg.MessageID, keyboard)
c.Bot.Send(buttonsUpdate)
return "ok"
}

32
lib/reminder/sender.go Normal file
View File

@ -0,0 +1,32 @@
// i2_bot Instinct PokememBro Bot
// Copyright (c) 2017 Vladimir "fat0troll" Hodakov
package reminder
import (
"github.com/go-telegram-bot-api/telegram-bot-api"
"lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping"
"time"
)
// SendReminders sends reminders for users about coming league tournament
func (r *Reminder) SendReminders() {
currentHour := time.Now().Hour()
nextTournamentID := (currentHour / 2) + 1
playersRaw := []dbmapping.Player{}
err := c.Db.Select(&playersRaw, "SELECT p.* FROM players p, alarms a WHERE a.turnir_number=? AND a.player_id = p.id GROUP BY p.id", nextTournamentID)
if err != nil {
c.Log.Error(err.Error())
}
for i := range playersRaw {
message := "*Турнир Лиг покемемов состоится через пять минут!*\n"
message += "Вперёд, за опытом и деньгами! Выстави атаку и жди результата!"
msg := tgbotapi.NewMessage(int64(playersRaw[i].TelegramID), message)
msg.ParseMode = "Markdown"
c.Bot.Send(msg)
}
}

60
lib/reminder/updaters.go Normal file
View File

@ -0,0 +1,60 @@
// i2_bot Instinct PokememBro Bot
// Copyright (c) 2017 Vladimir "fat0troll" Hodakov
package reminder
import (
"github.com/go-telegram-bot-api/telegram-bot-api"
"lab.pztrn.name/fat0troll/i2_bot/lib/dbmapping"
"strconv"
"strings"
"time"
)
// CreateAlarmSetting creates alarm setting for user
func (r *Reminder) CreateAlarmSetting(update *tgbotapi.Update, playerRaw *dbmapping.Player) string {
turnirNumber := strings.TrimPrefix(update.CallbackQuery.Data, "enable_reminder_")
turnirNumberInt, err := strconv.Atoi(turnirNumber)
if err != nil {
c.Log.Error(err.Error())
return "fail"
}
alarm := dbmapping.Alarm{}
alarm.PlayerID = playerRaw.ID
alarm.TurnirNumber = turnirNumberInt
alarm.CreatedAt = time.Now().UTC()
_, err = c.Db.NamedExec("INSERT INTO `alarms` VALUES(NULL, :player_id, :turnir_number, :created_at)", &alarm)
if err != nil {
c.Log.Error(err.Error())
return "fail"
}
keyboard := r.formatRemindersButtons(playerRaw)
buttonsUpdate := tgbotapi.NewEditMessageReplyMarkup(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Message.MessageID, keyboard)
c.Bot.Send(buttonsUpdate)
return "ok"
}
// DestroyAlarmSetting creates alarm setting for user
func (r *Reminder) DestroyAlarmSetting(update *tgbotapi.Update, playerRaw *dbmapping.Player) string {
turnirNumber := strings.TrimPrefix(update.CallbackQuery.Data, "disable_reminder_")
turnirNumberInt, err := strconv.Atoi(turnirNumber)
if err != nil {
c.Log.Error(err.Error())
return "fail"
}
_, err = c.Db.Exec(c.Db.Rebind("DELETE FROM `alarms` WHERE player_id=? AND turnir_number=?"), playerRaw.ID, turnirNumberInt)
if err != nil {
c.Log.Error(err.Error())
}
keyboard := r.formatRemindersButtons(playerRaw)
buttonsUpdate := tgbotapi.NewEditMessageReplyMarkup(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Message.MessageID, keyboard)
c.Bot.Send(buttonsUpdate)
return "ok"
}

View File

@ -0,0 +1,29 @@
// i2_bot Instinct PokememBro Bot
// Copyright (c) 2017 Vladimir "fat0troll" Hodakov
package router
import (
"github.com/go-telegram-bot-api/telegram-bot-api"
"regexp"
)
// RouteCallback routes inline requests to bot
func (r *Router) RouteCallback(update *tgbotapi.Update) string {
playerRaw, ok := c.Users.GetOrCreatePlayer(update.CallbackQuery.From.ID)
if !ok {
return "fail"
}
var enableAlarmCallback = regexp.MustCompile("enable_reminder_(\\d+)\\z")
var disableAlarmCallback = regexp.MustCompile("disable_reminder_(\\d+)\\z")
switch {
case enableAlarmCallback.MatchString(update.CallbackQuery.Data):
return c.Reminder.CreateAlarmSetting(update, &playerRaw)
case disableAlarmCallback.MatchString(update.CallbackQuery.Data):
return c.Reminder.DestroyAlarmSetting(update, &playerRaw)
}
return "ok"
}

View File

@ -80,6 +80,9 @@ func (r *Router) routePrivateRequest(update *tgbotapi.Update, playerRaw *dbmappi
case update.Message.Command() == "best":
c.Pokedexer.BestPokememesList(update, playerRaw)
return "ok"
case update.Message.Command() == "reminders":
return c.Reminder.AlarmsList(update, playerRaw)
case update.Message.Command() == "send_all":
if c.Users.PlayerBetterThan(playerRaw, "admin") {
c.Broadcaster.AdminBroadcastMessageCompose(update, playerRaw)

View File

@ -11,6 +11,7 @@ import (
type RouterInterface interface {
Init()
RouteCallback(update *tgbotapi.Update) string
RouteInline(update *tgbotapi.Update) string
RouteRequest(update *tgbotapi.Update) string
}

View File

@ -52,25 +52,26 @@ func (t *Talkers) HelpMessage(update *tgbotapi.Update, playerRaw *dbmapping.Play
message := "*Бот Инстинкта Enchanched.*\n\n"
message += "Текущая версия: *" + config.VERSION + "*\n\n"
message += "Список команд\n\n"
message += "+ /me посмотреть свой сохраненный профиль в боте\n"
message += "+ /best посмотреть лучших покемонов для поимки\n"
message += "+ /pokedeks получить список известных боту покемемов\n"
message += "+ /academy — Академия Инстинкта\n"
message += "+ /bastion — Бастион Инстинкта\n"
message += "\\* /me посмотреть свой сохраненный профиль в боте\n"
message += "\\* /best посмотреть лучших покемонов для поимки\n"
message += "\\* /pokedeks получить список известных боту покемемов\n"
message += "\\* /reminders — настроить оповещения на Турнир лиг\n"
message += "\\* /academy — Академия Инстинкта\n"
message += "\\* /bastion — Бастион Инстинкта\n"
if c.Users.PlayerBetterThan(playerRaw, "admin") {
message += "+ /send\\_all _текст_ — отправить сообщение всем пользователям бота\n"
message += "+ /send\\_league _текст_ — отправить сообщение всем пользователям бота, у которых профиль лиги Инстинкт\n"
message += "+ /chats — получить список групп, в которых работает бот.\n"
message += "+ /squads — получить список отрядов.\n"
message += "+ /pin _номера чатов_ _текст_ — отправить сообщение в чаты с номерами. Сообщение будет автоматичекси запинено. Пример: \"/pin 2,3,5 привет мир\". Внимание: между номерами чатов ставятся запятые без пробелов! Всё, что идёт после второго пробела в команде — сообщение\n"
message += "+ /pin\\_all _текст_ — отправить сообщение во все группы, где находится бот. Сообщение будет автоматически запинено.\n"
message += "+ /orders — просмотреть приказы на атаку\n"
message += "\\* /send\\_all _текст_ — отправить сообщение всем пользователям бота\n"
message += "\\* /send\\_league _текст_ — отправить сообщение всем пользователям бота, у которых профиль лиги Инстинкт\n"
message += "\\* /chats — получить список групп, в которых работает бот.\n"
message += "\\* /squads — получить список отрядов.\n"
message += "\\* /pin _номера чатов_ _текст_ — отправить сообщение в чаты с номерами. Сообщение будет автоматичекси запинено. Пример: \"/pin 2,3,5 привет мир\". Внимание: между номерами чатов ставятся запятые без пробелов! Всё, что идёт после второго пробела в команде — сообщение\n"
message += "\\* /pin\\_all _текст_ — отправить сообщение во все группы, где находится бот. Сообщение будет автоматически запинено.\n"
message += "\\* /orders — просмотреть приказы на атаку\n"
}
if c.Users.PlayerBetterThan(playerRaw, "academic") {
message += "+ /users — просмотреть зарегистрированных пользователей бота\n"
message += "+ /find\\_user _строка_ — найти игрока в боте по его нику или имени. Ник ищется без собачки в начале\n"
message += "\\* /users — просмотреть зарегистрированных пользователей бота\n"
message += "\\* /find\\_user _строка_ — найти игрока в боте по его нику или имени. Ник ищется без собачки в начале\n"
}
message += "+ /help выводит данное сообщение\n"
message += "\\* /help выводит данное сообщение\n"
message += "\n\n"
message += "Связаться с автором: @fat0troll\n"