Как написать триггер бот

Время на прочтение
3 мин

Количество просмотров 10K

Привет! Меня зовут Боровков Евгений и я предприниматель. В этой статье я расскажу про свой pet-проект. Или как я решил проблему вечного поиска информации прямо в уютном Телеграммчике.

Скажу сразу: я не программист. Я скорее менеджер, который работает в команде с программистами. Поэтому прошу меня простить, если технической глубины в статье не будет хватать.

Бизнесовая часть и проблематика

Идея не нова. Скорее всего вы или уже пользовались такими ботами, или пробовали написать такого своего. Но в поиске оптимального решения своей проблемы, мы пришли именно к такой конфигурации.

Суть бота очень проста и видна на видео ниже:

Если смотреть не хочется, то вкратце:

Еду, значит, я в метротаксиавтобусе на работу. И тут новому подрядчикуколлегеклиенту срочно потребовалось скинуть наши реквизиты. Для счетадоговораактачего угодно.

Где мне их найти быстро? В закладках? В автозамене? В загрузках в телефоне? В “Избранном”? Думаю вы уловили, или сталкивались с таким сами 🙂

У нас даже в какой то момент появилось специальное удобное хранилище таких файлов и информации. Но достаточно быстро оно все равно забилось и проблему не решило. Пример с реквизитами тут взят исключительно как пример. Ведь всем вокруг постоянно нужны кроме реквизитов еще и логотип в “линиях”, ссылки на какие-то сайты, или, в конце концов, смешные гифки.

В какой-то момент я пробовал подключить стороннего похожего бота. Но он то не работал, то как-то криво отвечал, то вообще слал рекламную рассылку. Пробовали других ботов: тоже самое. Некоторых неудобно настраивать, где-то нет списка всех триггеров, где-то нет русского языка.

В итоге решили писать своего. Чат-ботов мы делаем уже больше 4 лет, видение уже +- сформировалось, на серверах место для еще одного бота найдется. Поехали!

Бизнесовая логика работы бота

Второе видео как продолжение первого:

Бот работает на столько просто, что разберется даже ребенок:

  • Добавляем бота @trigger_new_bot в чат (можно не делать админом).

  • Пишем какое-то сообщение или кидаем гифкуфайлвидео — что угодно.

  • Делаем реплай нужного сообщения и пишем команду /set_trigger ключевое слово (или триггер) например «/set_trigger реквизиты».

  • Бот пришлет сообщение «триггер установлен».

  • Все, пишем то ключевое слово которое только что писали (ровно так же прям), и он пришлет то сообщение, куда был реплай.

Больше бот не делает НИ ЧЕ ГО. И тем самым соблюдает две основные заповеди ботостроения: имеет простой функционал и молчит когда его не спрашивают.

Количество чатов не ограничено, количество триггеров внутри чата не ограничено.

Как это работает под капотом

Основной ЯП, который мы используем, это Java. По нему у нас накоплена самая большая компетенция. Поэтому бот построен на Spring boot стеке:

<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-spring-boot-starter</artifactId>
<version>5.7.1</version>
</dependency>

База: MongoDB. Бот не хранит сами сообщения и не скачивает файлы. Ему достаточно хранить только ссылки на них.

Теперь по пунктам

  • Пишем сообщение: гифкуфайлвидео — что угодно

  • Делаем реплай нужного сообщения и пишем команду /set_trigger ключевое слово (или триггер) например «/set_trigger реквизиты».

После этого бот:

  1. Достает имя триггера из сообщения

String command = userText.replace("/set_trigger", "").trim();

2. Достает ссылку на само сообщение, которое должно быть сохранено

if (reply.hasText()) {
Message reply = update.getMessage().getReplyToMessage();
String triggerMessage = reply.getText();
}
  1. Собирает триггер и сохраняет его в БД

Trigger  trigger = Trigger.builder()
.chatId(chatId)
.commandName(command)
.triggerMessage(triggerMessage)
.triggerType(triggerType)
.build();

triggerRepository.save(trigger);

4. Отправляет ответ, что все сделано

Bot.send(new SendMessage(
chatId.toString(),
EmojiParser.parseToUnicode(":white_check_mark:") + createdInfo + "n + triggerMessage));

Другие технические тонкости

Собирается проект с помощью Maven. Просто так вышло, не спрашивайте почему. Другие проекты мы собираем, как правило, с помощью Gradle.

Собранный jar запущен на vps как unix-сервис. Для комфортной работы бота хватает 512 мб памяти. Не смотря даже на количество пользователей и количество заведенных триггеров, этого пока вполне достаточно.

Кому нужны исходники, можете написать, мы готовы ими поделиться. В общий доступ выкладывать не хочется. Хотя и секрета там особого нет. И да, сидим мы на гитлабе в приватных репозиториях, а не на гитхабе.

Бот не требует никакого обслуживания и внимания. Единственное, что недавний переход телеги на 64-битный идентификатор заставил перелопатить некоторых наших ботов. В том числе и этого.

В отличие от других ботов, тут мы не использовали свою State машину. Но в других проектах, где используется более 5 стейтев бота рекомендуем уже использовать либо спринговскую State машину либо самописную. Иначе с развитием проекта, код быстро превращается в набор спагетти методов.

Что еще

На данный момент, мы думаем как улучшить бота, не уничтожив его “шарм минималистичности”. Если есть идеи, пишите в комментарии.

Introduction

Triggerbot for Counter-Strike Global Offensive. Here I am going to show you the basics of making a mini-cheat for csgo. Just a simple triggerbot. Install Visual Studio or some other good editor.

WARNING!

Do not use it to cheat in csgo! It will get you instantly banned! This tutorial is for educational purposes*

1. Setup

To make a triggerbot first you have to install and use ProcMem.

First, create your main.cpp file and add ProcMem.cpp, ProcMem.h from the downloaded .rar file. You do this in Visual Studio by right clicking source files -> Add -> Existing item and choosing ProcMem.cpp. Then you do exactly the same but in Header Files with ProcMem.h.
Next we need to initialize ProcMem in our main file (main.cpp) so we can use functions in it. Easy, huh?
That’s how it looks like in code:

#include "ProcMem.h" // Reads memory
ProcMem Mem; // That's the shortcut

But to actually read our proccess’s memory (csgo) we have to choose a process

Mem.Process("csgo.exe"); // Chooses our process

After we did it we can read memory from it but reading more advanced things like PlayerBase we need to get the client.dll

DWORD ClientDLL = Mem.Module("client.dll"); // Creates a module we are reading memory from

Now the most boring part, which is offsets of our crosshair and other things like health blah blah…
Has to be updated when csgo updates. I was making this triggerbot with the CS Warzone version, so change the offsets. Just google it

// HAS to be updated when counter strike is updated.
const DWORD playerBase = 0xA68A14;
const DWORD entityBase = 0x4A0B0C4;
const DWORD crosshairOffset = 0x23F8;
// Doesn't require updating
const DWORD teamOffset = 0xF0;
const DWORD healthOffset = 0xFC;
const DWORD EntLoopDist = 0x10;

2. Making a Triggerbot

So now we to have get information about yourself. You are the LocalPlayer. To get information about LocalPlayer we have to read client.dll. client.dll has most information about us and our enemies/teammates.

// As i said, LocalPlayer (You)
DWORD LocalPlayer = Mem.Read<DWORD>(ClientDLL + PlayerBase);
// That is our teammates
int LocalTeam = Mem.Read<int>(LocalPlayer + teamOffset);
// That's our crosshair ID, we will use it for reading what's "in our crosshair"
int CrossHairID = Mem.Read<int>(LocalPlayer + CrosshairOffset);

After that we have to create a Triggerbot function. I will make it shorter and just name it Bot. We have to read memory which is required to make the triggerbot work

void Bot()
{
    DWORD EnemyInCH = Mem.Read<DWORD>(ClientDLL + EntityBase + ((CrossHairID - 1) * EntLoopDist)); // CH is just crosshair btw
    int EnemyHealth = Mem.Read<int>(EnemyInCH + healthOffset); // Enemy in our crosshair
    int EnemyTeam = Mem.Read<int>(EnemyInCH + teamOffset); // Enemy in crosshair's team, we need it to distinguish are we aiming at the enemy or the teammate does. Logic.
} 

Now for the ifs. To don’t make it shoot at our «friends» here is the first one

if (LocalTeam != EnemyTeam)
{
    // left click or just shoot lol
}

But notice that our triggerbot is gonna shoot death bodies/enemies with 0 health. Here is how we can do it. We are just gonna check is EnemyHealth higher than 0 if is then shoot. We will get to the shooting later.

if (EnemyHealth > 0)
{
    // left click or just shoot lol
}

For the gods of c++ we will make the code «prettier»

if (LocalTeam != EnemyTeam && EnemyHealth > 0)
{
    // shoot
}

That’s almost everything. BUT WAIT! It does not shoot! Because it’s only memory reading triggerbot. Now we are gonna make it click/shoot instead of forcing it through writing memory. We will be using mouse_event. Check mouse_event on MSDN and just try to get it. I get it after watching a tutorial. But anyways, there is the full code. NOTE If you do not understand something go back and try again.

void Bot()
{
    DWORD EnemyInCH = Mem.Read<DWORD>(ClientDLL + EntityBase + ((CrossHairID - 1) * EntLoopDist));
    int EnemyHealth = Mem.Read<int>(EnemyInCH + healthOffset);
    int EnemyTeam = Mem.Read<int>(EnemyInCH + teamOffset);
    if (LocalTeam != EnemyTeam && EnemyHealth > 0)
    {
    // Add a little delay yourself by Sleep()
    mouse_event(MOUSEEVENTF_LEFTDOWN, NULL, NULL, NULL, NULL);
    // You can use Sleep() here too, that line is made for autos like ak47. Not useful with pistol
    mouse_event(MOUSEEVENTF_LEFTUP, NULL, NULL, NULL, NULL);
    // Now you can make a cooldown beetween shots using Sleep() again.
    }

Now our final part, just add the triggerbot to your main in a loop

int main()
{
    while(true)
    {
        Bot();
        // Add a Sleep() here if you want some optimization
    }
}

Сегодня, с моим учеником, выполним заказ взятый на одной из фриланс биржи. Реальный боевой проект. Триггер бот — это конструктор триггеров, который запоминает отмеченные вами посты и копирует их вам в чат когда вы отправляется в чат определенное триггер-слово. Бот написан с использованием aiogram и базы данных postgresql. Как оказалось очень удобная вещь, альтернатива заметкам. Взял себе на вооружение с небольшими доработками. Смотрим, ставим лайки, подписываемся на канал, делимся с друзьями.

handlers/basic.py

from aiogram import Bot
from aiogram.filters import CommandObject
from aiogram.types import Message
from core.utils.dbconnect import Request


async def add_trigger(message: Message, command: CommandObject, request: Request):
    name_trigger = command.args.replace(' ', '_')
    value_trigger = message.reply_to_message.message_id
    await request.add_trigger(name_trigger, value_trigger)
    await message.answer(f'Триггер {name_trigger} добавлен')
    print(message.chat.id)


async def get_triggers(message: Message, request: Request):
    msg = await request.get_triggers()
    await message.answer(msg, parse_mode="MARKDOWN")


async def get_values(message: Message, bot: Bot, request: Request):
    values = await request.get_values(message.text.replace('#', ''))
    list_values = values.split('rn')

    for value in list_values:
        await bot.copy_message(message.chat.id, message.chat.id, int(value))

middlewares/dbmiddleware/py

from typing import Callable, Awaitable, Dict, Any
import asyncpg
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject
from core.utils.dbconnect import Request


class DbSession(BaseMiddleware):
    def __init__(self, connector: asyncpg.pool.Pool):
        super().__init__()
        self.connector = connector

    async def __call__(
            self,
            handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
            event: TelegramObject,
            data: Dict[str, Any],
    ) -> Any:
        async with self.connector.acquire() as connect:
            data['request'] = Request(connect)
            return await handler(event, data)

urils/dbconnect.py

import asyncpg
from typing import List
from asyncpg import Record

class Request:
    def __init__(self, connector: asyncpg.pool.Pool):
        self.connector = connector

    async def add_trigger(self, name_trigger, value_trigger):
        query = f"INSERT INTO trigger_table (name_trigger, value_trigger) VALUES ('{name_trigger}', '{value_trigger}') " 
                f"ON CONFLICT (name_trigger) " 
                f"DO UPDATE SET value_trigger=trigger_table.value_trigger || 'rn' || excluded.value_trigger"
        await self.connector.execute(query)


    async def get_triggers(self):
        query = f"SELECT name_trigger FROM trigger_table ORDER BY name_trigger"
        result_list: List[Record] = await self.connector.fetch(query)
        return 'rn'.join([f"`#{result.get('name_trigger')}`" for result in result_list])

    async def get_values(self, name_trigger):
        query = f"SELECT value_trigger FROM trigger_table WHERE name_trigger='{name_trigger}'"
        return await self.connector.fetchval(query)

main.py

import contextlib

from aiogram import Bot, Dispatcher, F
import asyncio
import logging

from aiogram.filters import Command

from core.settings import settings

from core.utils.commands import set_commands
from core.middlewares.dbmiddleware import DbSession
import asyncpg

from core.handlers import basic
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())


async def start_bot(bot: Bot):
    await set_commands(bot)
    await bot.send_message(settings.bots.admin_id, text='Бот запущен!')


async def stop_bot(bot: Bot):
    await bot.send_message(settings.bots.admin_id, text='Бот остановлен!')


async def create_pool():
    return await asyncpg.create_pool(user='postgres', password='qwerty', database='triggerbot',
                                     host='127.0.0.1', port=5432, command_timeout=60)


async def start():
    logging.basicConfig(level=logging.INFO,
                        format="%(asctime)s - [%(levelname)s] -  %(name)s - "
                               "(%(filename)s).%(funcName)s(%(lineno)d) - %(message)s"
                        )

    bot = Bot(token=settings.bots.bot_token, parse_mode='HTML')
    pool_connect = await create_pool()
    dp = Dispatcher()
    dp.update.middleware.register(DbSession(pool_connect))
    dp.startup.register(start_bot)
    dp.shutdown.register(stop_bot)

    dp.message.register(basic.add_trigger, Command(commands='add_trigger', magic=F.args), F.reply_to_message,
                        F.chat.id == -603194626)
    dp.message.register(basic.get_triggers, Command(commands='get_triggers'), F.chat.id == -603194626)
    dp.message.register(basic.get_values, F.chat.id == -603194626, F.text.startswith('#'))
    try:
        await dp.start_polling(bot)
    finally:
        await bot.session.close()


if __name__ == "__main__":
    with contextlib.suppress(KeyboardInterrupt, SystemExit):
        asyncio.run(start())

КУРСЫ ПО РАЗРАБОТКЕ НА PYTHON

Богатый Python разработчик

Как начать стабильно зарабатывать не выходя из дома, даже если сейчас вы ничего не понимаете в программировании и не умеете…

  • Previous
  • Next

Похожие записи

Отправляем сообщение если юзер

В этом видео мы научимся отправлять юзерботом сообщение пользователю, только когда тот будет онлайн с помощью pyrogram и python. Тем…

  • Previous
  • Next

Привет! Меня зовут Боровков Евгений и я предприниматель. В этой статье я расскажу про свой pet-проект. Или как я решил проблему вечного поиска информации прямо в уютном Телеграммчике.

Скажу сразу: я не программист. Я скорее менеджер, который работает в команде с программистами. Поэтому прошу меня простить, если технической глубины в статье не будет хватать.

Бизнесовая часть и проблематика

Идея не нова. Скорее всего вы или уже пользовались такими ботами, или пробовали написать такого своего. Но в поиске оптимального решения своей проблемы, мы пришли именно к такой конфигурации.

Суть бота очень проста и видна на видео ниже:

Если смотреть не хочется, то вкратце:

Еду, значит, я в метротаксиавтобусе на работу. И тут новому подрядчикуколлегеклиенту срочно потребовалось скинуть наши реквизиты. Для счетадоговораактачего угодно.

Где мне их найти быстро? В закладках? В автозамене? В загрузках в телефоне? В “Избранном”? Думаю вы уловили, или сталкивались с таким сами

Привет! Сегодня я расскажу про то, как мы устали искать информацию — и сделали так, чтобы она всегда была под рукой. Всегда, когда есть телефон с установленным Telegram в руках. Поехали!

Видео демонстрация работы Trigger bot

Вся проблематика, которую мы хотели закрыть ботом, — мы закрыли. И она видна на гифке выше. Если смотреть не хочется, то вкратце:

Едете, значит, вы в метроавтобусетакси — на работу. И тут новому клиентуподрядчикуколлеге срочно нужны наши реквизиты. Для чего угодно (думаю, вы меня понимаете). Где мне их найти быстро? В загруженных файлах на телефоне? Искать по рабочим чатам или в «Избранном»? Лезть с вопросами к юристам? Все эти (и не только эти) пути исхожены уже до такой степени, что в какой-то момент стало настолько не смешно, что захотелось свежего решения.

Все то же самое, что применимо к реквизитам, можно применить и к другим вещам. Например, шаблон договора, ссылка на какой-то сайт, куда надо заходить раз в год, логотип «в линиях», смешная гифка и так далее.

К моменту создания бота, мы занимались их разработкой уже как пару лет. И поэтому логичным решением показалось именно создание очередного бота. (Бот родился не вчера. Ему уже больше года. А нашему направлению по ботам уже четыре.)

Честно признаться — выход из ситуации не уникальный. Америку мы не открыли. Такая функциональность встречалась в разном виде в разных ботах, и, может быть, вы его даже видели. А может быть видели и не обращали внимания. Мы постарались «консолидировать опыт» других разработчиков, чтобы решить свои проблемы. И сейчас пишем об этом вам.

Итак, что делает бот?

Все так просто, что многие не могут даже поверить.

Второе видео демонстрация работы Trigger bot

  1. добавляем бота @trigger_new_bot в чат (можно не делать админом);
  2. пишем какое-то сообщение или кидаем гифкуфайлвидео — что угодно;
  3. делаем реплай нужного сообщения и пишем команду /set_trigger *ключевое слово* (или триггер) например «/set_trigger реквизиты»;
  4. бот пришлет сообщение «триггер установлен»;
  5. все, пишем то *ключевое слово* которое только что писали (ровно так же прям), и он пришлет то сообщение, куда был реплай.

У нас есть чаты, где триггеров созданы сотни, и куча информации всегда под рукой. Достаточно зайти в чат, вбить нужное сочетание слов, и бот триггернется нужным сообщением. Просто и удобно.

Еще есть команда /del_trigger для удаления триггера, и /help. Это вроде очевидно.

Ну и он специально сделан чтобы отличать заглавные и строчные буквы. Триггеры «Реквизиты» и «реквизиты» — это разные триггеры, что тоже можно использовать интересным образом. Хотя, казалось бы, одна буква разница.

Это может и запутать, конечно. Но в этом случае достаточно завести на оба варианта одни и те же реквизиты :)

Как оказалось, такую функциональность можно применить в бесчисленное количество сценариев. В одном из чатов настроена цепочка так, что с ним можно петь (он продолжает слова песни). В другом он просто напоминает правила чата, когда добавляются новые люди (не тупо дублирует каждый раз, а по запросу админа, очевидно).

Самая прелесть в том, что больше он не делает НИ ЧЕ ГО. В личке молчит, в каналах молчит. Он просто триггерится когда надо — и все. Только в групповых чатах. Мы встречали, что другие разработчики добавляли такую функциональность «в довесок» к «основной» функциональности своего бота. Или зашивали какие-то триггеры свои «смешные». А мы просто отрезали все лишнее, и остались довольны.

Боту, по сути, без разницы, что вы ему хотите засунуть в реплай. Он не скачивает это видео или документ себе. Он сохраняет лишь ссылку на сообщение (спасибо Дурову) . Потом обращается по этой ссылке и воспроизводит.

Есть несколько мыслей, как сделать бота поинтереснее. Но есть опасение, что он утратит свой «шарм минималистичности». Например, чтобы сразу можно было создавать несколько триггеров одним сообщением. Или возможность установки триггеров сразу во все чаты, где есть и бот и пользователь, который устанавливает триггер. Что думаете об этом?

Понравилась статья? Поделить с друзьями:
  • Как написать трещина правильно
  • Как написать трехметровый
  • Как написать трехлебову
  • Как написать трехкомнатная квартира
  • Как написать трех цифрой