Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
BOT_CONTAINER_NAME=bot_container_name
BOT_IMAGE_NAME=botimage_name
BOT_NAME=mybotname
BOT_TOKEN=123456:Your-TokEn_ExaMple
ADMINS=123456,654321
USE_REDIS=False

DB_NAME=Granted.db

GRANT_NUMBER=500
66 changes: 66 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Translations
*.mo
*.pot


# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Environments
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Pyre type checker
.pyre/
.idea/*
.env

# Database
*.db
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM python:3.9-buster
ENV BOT_NAME=$BOT_NAME

WORKDIR /usr/src/app/"${BOT_NAME:-tg_bot}"

COPY requirements.txt /usr/src/app/"${BOT_NAME:-tg_bot}"
RUN pip install -r /usr/src/app/"${BOT_NAME:-tg_bot}"/requirements.txt
COPY . /usr/src/app/"${BOT_NAME:-tg_bot}"
86 changes: 85 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,85 @@
# tg-bot-users 🤖
# Anniversary Users Bot 🤖

Бот для автоматического отслеживания и поздравления юбилейных пользователей. При
вступлении в группу юбилейного участника бот присылает в группу
администраторов уведомление с именем, ником, id пользователя, юбилейным номером и датой и временем вступления,
а также кнопкой "Поздравить" и "Отменить" для автоматического поздравления юбилейного участника или отмены, в случае
вступления в группу бота/модератора. В файле .env в переменной GRANT_NUMBER (пример в .env.dist), необходимо установить
число, кратно которому будут определятся пользователи (при 500 - 500, 1000 и т.д.), это глобальная настройка для всех
групп, если необходимо для какой-то группы выставить индивидуальные значения, то необходимо через приватный канал бота,
для этой группы настроить соответствующую таблицу. Для доступа к конфигурированию бота через приватный канал, в файле
.env (пример в .env.dist) необходимо прописать в переменной ADMINS, прописать ID пользователей Телеграмм, которые будут
иметь соответствующие права.

## Особенности

* Бот, в автоматическом режиме проверяет вступившего пользователя на "юбилейные" места в группе, и посылает оповещения в
группу администраторов.
* Все новые вступившие пользователи проверяются по базе данных, что бы исключить повторное поздравление.
* Управление ботов осуществляется в группе администраторов.
* Возможность "тонкой" настройки юбилейных мест. К примеру, каждый: 50, 100, 200 ... и так далее.
* Сохранение данных о двух последующих пользователях.
* Возможность настройки групп администраторов.
* База данных на всех поздравленных пользователей
* Автоматический перезапуск бота в случае возникновения ошибки (Docker)

## Доступные команды

### В группе модераторов

* /проверка - команда проверяет, есть ли в очереди на поздравление пользователи из модерируемых групп.
* /восстановить - команда предназначена для восстановления кнопок для поздравления, на случай если уведомление с
кнопками поздравлениями удалили, или нажали кнопку отмены.
* /списокЮбилейный - выводит историю о юбилейных пользователях

### В приватном чате бота(админка)

Настройка бота производится в приватном чате, команды доступные в меню:

*/start - Позволяет проверить запушен ли бот и есть ли у пользователя права на конфигурирование
*/configure - Вызывает главное меню

#### Главное меню:

В главном меню есть доступ для перехода к конфигурированию 2 таблиц:

1. Настройка таблицы групп:

* Показать таблицу - выводит таблицу с данными из БД
* Добавить строку - позволяет добавить новую в БД, на одну группу модераторов может быть добавлено несколько групп
пользователей, при этом, если ввести id группы модераторов, запись с которой уже существует, то бот просто добавит
к старой записи новые группы пользователей, при этом бот уберет дубликаты id пользовательских групп, если их введут
повторно.
* Удалить строку - удаляет строку из БД по ID записи в БД
* Главное меню - возвращает в главное меню

2. Настройка таблицы с юбилейными номерами:

* Показать таблицу - выводит таблицу с данными из БД
* Добавить строку - позволяет добавить новую в БД, на одну группу пользователей может быть добавлено несколько номеров,
при этом, если ввести уже id группы пользователей, запись с которой уже существует, то бот просто добавит
к старой записи, новые номера, при этом бот уберет дубликаты номеров а также отсортиует их по возрастанию.
* Удалить строку - удаляет строку из БД по ID записи в БД
* Главное меню - возвращает в главное меню

## Requirements

* aiogram 2.21
* aioredis 2.0
* environs 9.0
* aiosqlite 0.17
* python-dotenv 0.20

## Подготовка и запуск

1) Создайте своего бота с помощью бота @BotFather и сохраните свой токен.
2) Необходимо в @BotFather отключить privacy mode.
3) Добавьте бота во все чаты, в которых необходимо отслеживать пользователей(боту необходимо выдать права
администратора)
4) Создать файл .env в директории программы и заполнить его согласно .env.dist
5) Вы можете запустить бота локально, установив все зависимости командой:
pip install -r requirements.txt и запустив bot.py
6) Вы так же можете запустить бота из Docker контейнера, для этого перейдите в терминале, в папку с ботом и выполните
команду: docker-compose up
7) После чего можете переходить к настройкам админки, для того чтобы бот начал работать в группах в которые он был
добавлен, необходимо внести соответствие ID этих групп в таблицу, через приватный канал бота.
130 changes: 130 additions & 0 deletions bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import asyncio
import logging

from aiogram import Bot, Dispatcher
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.contrib.fsm_storage.redis import RedisStorage2
from aiogram.contrib.middlewares.logging import LoggingMiddleware
from aiogram.types import AllowedUpdates

from tgbot.handlers.admin.add_groups_callback import register_add_groups
from tgbot.handlers.admin.add_numbers_callback import register_add_numbers
from tgbot.handlers.admin.cancel import register_cancel_menu
from tgbot.handlers.admin.configure_groups_callback import register_configure_groups
from tgbot.handlers.admin.configure_numbers_callback import register_configure_numbers
from tgbot.handlers.admin.delete_from_groups import register_delete_from_groups
from tgbot.handlers.admin.delete_from_numbers import register_delete_numbers
from tgbot.handlers.admin.delete_groups_callback import register_delete_groups_cb
from tgbot.handlers.admin.delete_numbers_callback import register_delete_numbers_cb
from tgbot.handlers.admin.get_mod_group import register_get_mod_group
from tgbot.handlers.admin.get_numbers import register_get_grant_numbers
from tgbot.handlers.admin.get_numbers_group import register_numbers_group
from tgbot.handlers.admin.get_users_groups import register_get_users_group
from tgbot.handlers.admin.main_menu import register_main_menu
from tgbot.handlers.admin.back_to_main_menu import register_back_to_main
from tgbot.handlers.admin.show_groups_callback import register_show_groups
from tgbot.handlers.admin.show_numbers_callback import register_show_numbers
from tgbot.handlers.admin.user import register_user
from tgbot.handlers.admin.admin import register_admin

from tgbot.handlers.groups.check import register_check_queue
from tgbot.handlers.groups.get_granted import register_get_granted
from tgbot.handlers.groups.restore import register_restore
from tgbot.handlers.groups.grant_cancel_callback import register_cancel_grant
from tgbot.handlers.groups.grant_callback import register_grant
from tgbot.handlers.groups.catch_update import register_catch
from tgbot.handlers.groups.show_granted_callback import register_show_granted_cb

from tgbot.misc.set_commands import set_default_commands

from tgbot.filters.moder_group import IsModerGroup
from tgbot.filters.granted import IsNotGranted
from tgbot.filters.count import IsGrantCount
from tgbot.filters.user_group import IsUserGroup
from tgbot.Utils.DBWorker import create_tables
from tgbot.config import load_config
from tgbot.filters.admin import AdminFilter
from tgbot.filters.group_join import IsGroupJoin

logger = logging.getLogger(__name__)


def register_all_middlewares(dp):
dp.setup_middleware(LoggingMiddleware())


def register_all_filters(dp):
dp.filters_factory.bind(AdminFilter)
dp.filters_factory.bind(IsGroupJoin)
dp.filters_factory.bind(IsUserGroup)
dp.filters_factory.bind(IsModerGroup)
dp.filters_factory.bind(IsGrantCount)
dp.filters_factory.bind(IsNotGranted)


def register_all_handlers(dp):
register_admin(dp)
register_user(dp)
register_catch(dp)
register_grant(dp)
register_cancel_grant(dp)
register_check_queue(dp)
register_restore(dp)
register_get_granted(dp)
register_configure_groups(dp)
register_show_groups(dp)
register_add_groups(dp)
register_get_mod_group(dp)
register_get_users_group(dp)
register_delete_groups_cb(dp)
register_delete_from_groups(dp)
register_main_menu(dp)
register_back_to_main(dp)
register_cancel_menu(dp)
register_configure_numbers(dp)
register_show_numbers(dp)
register_add_numbers(dp)
register_numbers_group(dp)
register_get_grant_numbers(dp)
register_delete_numbers_cb(dp)
register_delete_numbers(dp)
register_show_granted_cb(dp)


async def main():
logging.basicConfig(
level=logging.INFO,
format=u'%(filename)s:%(lineno)d #%(levelname)-8s [%(asctime)s] - %(name)s - %(message)s',
)
logger.info("Starting bot")
config = load_config(".env")

storage = RedisStorage2() if config.tg_bot.use_redis else MemoryStorage()
bot = Bot(token=config.tg_bot.token, parse_mode='HTML')
dp = Dispatcher(bot, storage=storage)

bot['config'] = config
register_all_middlewares(dp)
register_all_filters(dp)
register_all_handlers(dp)
await set_default_commands(dp)
await create_tables()

# start
try:
await bot.delete_webhook(drop_pending_updates=True)
await dp.start_polling(
allowed_updates=AllowedUpdates.MESSAGE + AllowedUpdates.CHAT_MEMBER + AllowedUpdates.CALLBACK_QUERY)
finally:
await dp.storage.close()
await dp.storage.wait_closed()
await bot.session.close()


if __name__ == '__main__':
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(main())
except (KeyboardInterrupt, SystemExit):
logger.error("Bot stopped!")
23 changes: 23 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: '3.3'

services:
bot:
image: "${BOT_IMAGE_NAME:-tg_bot-image}"
container_name: "${BOT_CONTAINER_NAME:-tg_bot-container}"
stop_signal: SIGINT
build:
context: .
working_dir: "/usr/src/app/${BOT_NAME:-tg_bot}"
volumes:
- .:/usr/src/app/${BOT_NAME:-tg_bot}
command: python3 -m bot
restart: always
env_file:
- ".env"
networks:
- tg_bot


networks:
tg_bot:
driver: bridge
5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
aiogram~=2.21
aioredis~=2.0
environs~=9.0
aiosqlite~=0.17
python-dotenv~=0.20
14 changes: 14 additions & 0 deletions systemd/tgbot.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[Unit]
Description=Сongratulate Bot
After=network.target

[Service]
User=tgbot
Group=tgbot
Type=simple
WorkingDirectory=/opt/tgbot
ExecStart=/opt/tgbot/venv/bin/python bot.py
Restart=always

[Install]
WantedBy=multi-user.target
Loading