|
|
|
import os
|
|
|
|
import logging
|
|
|
|
from aiogram import Bot, Dispatcher, executor, types
|
|
|
|
from aiogram.utils.exceptions import MessageToDeleteNotFound
|
|
|
|
import asyncio
|
|
|
|
from datetime import timedelta, datetime
|
|
|
|
import secrets
|
|
|
|
from sqlalchemy import create_engine
|
|
|
|
from sqlalchemy import Column, Integer, String, DateTime
|
|
|
|
from sqlalchemy.orm import declarative_base, sessionmaker
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
DELAY_TIME = 1
|
|
|
|
DELAY_TIME_Q = 3
|
|
|
|
CHAT_ID = -1001612776177
|
|
|
|
ALLOWED_USERS = [106693654, 7063133]
|
|
|
|
ADMINS = [106693654, 7063133, 4978608]
|
|
|
|
API_TOKEN = os.getenv("TELEGRAM_API_TOKEN")
|
|
|
|
bot = Bot(token=API_TOKEN)
|
|
|
|
dp = Dispatcher(bot)
|
|
|
|
|
|
|
|
engine = create_engine("sqlite:///support.db")
|
|
|
|
Base = declarative_base()
|
|
|
|
Session = sessionmaker(bind=engine)
|
|
|
|
session = Session()
|
|
|
|
|
|
|
|
|
|
|
|
class Allowed_user(Base):
|
|
|
|
__tablename__ = "allowed_users"
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
|
|
user_id = Column(Integer, nullable=False)
|
|
|
|
date_add = Column(DateTime, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
class Token(Base):
|
|
|
|
"""Класс для токенов.
|
|
|
|
|
|
|
|
Хранит сам ключ, и дату истечения.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
__tablename__ = "tokens"
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
|
|
token = Column(String, nullable=False)
|
|
|
|
expire = Column(DateTime, nullable=False)
|
|
|
|
|
|
|
|
def expired(self) -> bool:
|
|
|
|
"""Проверка срока жизни токена.
|
|
|
|
|
|
|
|
Вовзращает истину, если срок действия токена истек.
|
|
|
|
"""
|
|
|
|
if datetime.now() > self.expire:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
"""Базовое представление класса."""
|
|
|
|
return f"{self.id}: {self.token} | {self.expire}"
|
|
|
|
|
|
|
|
|
|
|
|
class Messages_to_delete(Base):
|
|
|
|
"""Сообщения, которые нужно будет удалить."""
|
|
|
|
|
|
|
|
__tablename__ = "messages_to_delete"
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
|
|
message_id = Column(Integer, nullable=False)
|
|
|
|
deletion_date = Column(DateTime, nullable=False)
|
|
|
|
|
|
|
|
def deletion_needed(self) -> bool:
|
|
|
|
"""Нужно ли удалить это сообщение.
|
|
|
|
|
|
|
|
Мы могли пропустить удаление из-за перезапуска.
|
|
|
|
"""
|
|
|
|
if datetime.now() > self.deletion_date:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def auth(func):
|
|
|
|
async def wrapper(message):
|
|
|
|
users_list: Allowed_user = (
|
|
|
|
session.query(Allowed_user).order_by(Allowed_user.id).all()
|
|
|
|
)
|
|
|
|
|
|
|
|
finded_user: Allowed_user = next(
|
|
|
|
(u for u in users_list if u.user_id == message["from"]["id"]), None
|
|
|
|
)
|
|
|
|
|
|
|
|
if not finded_user:
|
|
|
|
return # await message.reply("Access Denied", reply=False)
|
|
|
|
return await func(message)
|
|
|
|
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
|
|
async def clean_up(message_id: int, delay_min: int):
|
|
|
|
exp = datetime.now() + timedelta(minutes=delay_min)
|
|
|
|
msg: Messages_to_delete = Messages_to_delete(
|
|
|
|
message_id=message_id, deletion_date=exp
|
|
|
|
)
|
|
|
|
session.add(msg)
|
|
|
|
session.commit()
|
|
|
|
|
|
|
|
await asyncio.sleep(delay_min * 60)
|
|
|
|
try:
|
|
|
|
await bot.delete_message(chat_id=CHAT_ID, message_id=message_id)
|
|
|
|
session.delete(msg)
|
|
|
|
session.commit()
|
|
|
|
except MessageToDeleteNotFound:
|
|
|
|
print("Ошибка удаления сообщения")
|
|
|
|
|
|
|
|
|
|
|
|
@dp.message_handler(commands=["start", "help"])
|
|
|
|
@auth
|
|
|
|
async def send_welcome(message: types.Message):
|
|
|
|
|
|
|
|
await message.reply(
|
|
|
|
"Бот для взаимодействия с группой\n\n" "/q - для отправки вопроса\n",
|
|
|
|
reply=False,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@dp.message_handler(commands=["mes"])
|
|
|
|
@auth
|
|
|
|
async def send_to_chanel(message: types.Message):
|
|
|
|
|
|
|
|
deletion_time = message.date + timedelta(minutes=DELAY_TIME)
|
|
|
|
out_text = f"{message.text[5:]} \n\nБудет удалено в *{deletion_time}*"
|
|
|
|
result = await bot.send_message(
|
|
|
|
chat_id=CHAT_ID,
|
|
|
|
text=out_text,
|
|
|
|
disable_notification=True,
|
|
|
|
parse_mode="Markdown",
|
|
|
|
)
|
|
|
|
|
|
|
|
await clean_up(message_id=result.message_id, delay_min=DELAY_TIME)
|
|
|
|
|
|
|
|
|
|
|
|
@dp.message_handler(commands=["q"])
|
|
|
|
@auth
|
|
|
|
async def send_question(message: types.Message):
|
|
|
|
"""Отправить вопрос через бота.
|
|
|
|
|
|
|
|
Через бота можно отправить вопрос, который будет удален через
|
|
|
|
DELAY_TIME_Q минут.
|
|
|
|
|
|
|
|
"""
|
|
|
|
if len(message.text) == len("/q"):
|
|
|
|
await message.reply(
|
|
|
|
"Пишешь пусто - делаешь грустно (мне)"
|
|
|
|
"\nПосле /q надо написать вопрос, вот так:"
|
|
|
|
"\n\n/q как доехать до Баррикадной?",
|
|
|
|
reply=False,
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
deletion_time = message.date + timedelta(minutes=DELAY_TIME_Q)
|
|
|
|
out_text = f"*Внимание, вопрос!*\n\n{message.text[3:]}\n\nБудет удалено в *{deletion_time}*"
|
|
|
|
result = await bot.send_message(
|
|
|
|
chat_id=CHAT_ID,
|
|
|
|
text=out_text,
|
|
|
|
disable_notification=True,
|
|
|
|
parse_mode="Markdown",
|
|
|
|
)
|
|
|
|
|
|
|
|
await clean_up(message_id=result.message_id, delay_min=DELAY_TIME_Q)
|
|
|
|
|
|
|
|
|
|
|
|
@dp.message_handler(commands=["get_invite"])
|
|
|
|
async def get_invite(message: types.Message):
|
|
|
|
|
|
|
|
if message["from"]["id"] not in ADMINS:
|
|
|
|
return
|
|
|
|
|
|
|
|
token = secrets.token_urlsafe(16)
|
|
|
|
|
|
|
|
exp = datetime.now() + timedelta(minutes=5)
|
|
|
|
test = Token(token=token, expire=exp)
|
|
|
|
session.add(test)
|
|
|
|
session.commit()
|
|
|
|
|
|
|
|
await message.reply(
|
|
|
|
f"Новый токен: *{token}* \nБудет действителен 5 минут\n\nЧтобы вступить в чат напиши боту [@GorgorodBot](https://t.me/GorgorodBot)\n`/add_me {token}`",
|
|
|
|
reply=False,
|
|
|
|
parse_mode="Markdown",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@dp.message_handler(commands=["add_me"])
|
|
|
|
async def add_me(message: types.Message):
|
|
|
|
"""Добавление юзера в чат.
|
|
|
|
|
|
|
|
Когда бот получает токен, он проверяет его на валидность (есть ли
|
|
|
|
такой вообще, не истек ли) и высылает разовую ссылку на вступление
|
|
|
|
в группу.
|
|
|
|
|
|
|
|
"""
|
|
|
|
user_token = message.text[8:].strip()
|
|
|
|
tokens_list = session.query(Token).order_by(Token.id).all()
|
|
|
|
|
|
|
|
finded_token: Token = next((t for t in tokens_list if t.token == user_token), None)
|
|
|
|
|
|
|
|
if finded_token.expired():
|
|
|
|
print("Пизда токену")
|
|
|
|
|
|
|
|
# если с токеном все впорядке, то добавляем юзера в базу
|
|
|
|
if finded_token and not finded_token.expired():
|
|
|
|
new_user: Allowed_user = Allowed_user(
|
|
|
|
user_id=message["from"]["id"], date_add=datetime.now()
|
|
|
|
)
|
|
|
|
session.add(new_user)
|
|
|
|
session.commit()
|
|
|
|
|
|
|
|
# генерируем ссылку, истечет через 5 минут, может вступить 1 человек
|
|
|
|
link = await bot.create_chat_invite_link(
|
|
|
|
chat_id=CHAT_ID, expire_date=timedelta(minutes=5), member_limit=1
|
|
|
|
)
|
|
|
|
|
|
|
|
await message.reply(
|
|
|
|
"Welcome to the internet"
|
|
|
|
"\nHave a look around"
|
|
|
|
"\nAnything that brain of yours can think of can be found"
|
|
|
|
f"\n\nТвоя ссылка для вступления: {link['invite_link']}",
|
|
|
|
reply=False,
|
|
|
|
parse_mode="Markdown",
|
|
|
|
)
|
|
|
|
await asyncio.sleep(10)
|
|
|
|
with open("static/welcome_msg.txt", "r") as f:
|
|
|
|
msg = f.read()
|
|
|
|
await message.reply(msg, reply=False, parse_mode="Markdown")
|
|
|
|
|
|
|
|
|
|
|
|
@dp.message_handler(commands=["wlc"])
|
|
|
|
@auth
|
|
|
|
async def wlc(message: types.Message):
|
|
|
|
with open("welcome_msg.txt", "r") as f:
|
|
|
|
msg = f.read()
|
|
|
|
await message.reply(
|
|
|
|
msg, reply=False, parse_mode="Markdown", disable_web_page_preview=True
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# https://stackoverflow.com/questions/67637631/create-a-background-process-using-aiogram
|
|
|
|
async def messages_cleanup() -> None:
|
|
|
|
msg_list = session.query(Messages_to_delete).order_by(Messages_to_delete.id).all()
|
|
|
|
while True:
|
|
|
|
for m in msg_list:
|
|
|
|
if m.deletion_needed:
|
|
|
|
try:
|
|
|
|
await bot.delete_message(chat_id=CHAT_ID, message_id=m.message_id)
|
|
|
|
|
|
|
|
except MessageToDeleteNotFound:
|
|
|
|
print("Ошибка удаления сообщения")
|
|
|
|
await asyncio.sleep(600)
|
|
|
|
|
|
|
|
|
|
|
|
async def on_startup(dispatcher: Dispatcher) -> None:
|
|
|
|
asyncio.create_task(messages_cleanup())
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
Base.metadata.create_all(engine)
|
|
|
|
executor.start_polling(dp, skip_updates=True, on_startup=on_startup)
|