Refactoring: step 1

- пакет app переименован в src
- файл БД теперь должен лежать в data
- удалена папка коммандс, все команды вынесены в модуль коммандс или админ_коммандс
- нет импортов в ините
- убраны все импорты *
- теперь в .env можно указать use_reminder="FALSE", тогда не будет стартовать ремайндер
- можно указать урл для Правил
- в конфиге теперь только конфиг -то, что из енва получаем
- константы теперь лежат отдельно
- добавлены явные return там, где не было
- бот и вебхук разнесены отдельно
- при старте проверяем, что Правила доступны
- добавлено еще логирование
This commit is contained in:
kotolex
2023-12-22 19:16:57 +05:00
parent 1cd96220a5
commit 9103669cac
19 changed files with 268 additions and 258 deletions
+1
View File
@@ -0,0 +1 @@
+162
View File
@@ -0,0 +1,162 @@
import logging
from datetime import datetime as dt
from telebot import types, logger
from .constants import ADMIN_ID, LOGGING_LEVEL_DEBUG, LOGGING_LEVEL_INFO
from .filters import is_white, is_admin
from .helpers import me, my_ip
from .models import Chat, Quote, session
from .reminder import remind, print_get_jobs, create_report_text
from .report import reset_report_stats
from .robot import bot
@bot.message_handler(commands=["me"])
async def send_me(message):
"""Send info about user and chat id [Service]."""
await bot.send_message(message.chat.id, me(message))
@bot.message_handler(func=is_admin, commands=["ip"])
async def get_ip(message):
await bot.send_message(message.chat.id, my_ip())
@bot.message_handler(commands=["remind"])
async def remind_manually(message):
"""Remind holidays manually."""
args = message.text.split()
if len(args) > 1:
try:
today = dt.strptime(args[1], "%m-%d-%Y")
except ValueError as ve:
await bot.send_message(message.chat.id, f"Не удалось разобрать дату!\n{ve}")
else:
await remind(message.chat.id, today)
else:
await bot.send_message(
message.chat.id,
f"<b>Формат даты: MM-DD-YYYY</b>\n\n"
f"Примеры:\n"
f"/remind 09-12-2024\n"
f"/remind 09-13-2022",
)
@bot.message_handler(func=is_admin, commands=["jobs"])
async def list_jobs(_message):
"""List all the jobs in schedule."""
await bot.send_message(ADMIN_ID, print_get_jobs())
@bot.message_handler(func=is_admin, commands=["stats"])
async def send_stats(message: types.Message):
if len(message.text.split()) == 1:
rep = create_report_text(message.chat.id)
if rep:
await bot.send_message(message.chat.id, rep)
else:
await bot.send_message(message.chat.id, create_report_text(message.text.split()[-1]))
@bot.message_handler(func=is_admin, commands=["reset_stats"], chat_types=["supergroup", "group"])
async def send_stats(message: types.Message):
logging.warning("reset_stats")
reset_report_stats(message.chat.id)
await bot.send_message(message.chat.id, reset_report_stats(message.chat.id))
@bot.message_handler(func=is_white, commands=["enable_stan"], chat_types=["supergroup", "group"])
async def enable_stan(message: types.Message):
"""Add group to database."""
logging.info(f"[{message.chat.title}] [{message.from_user.id}] {message.from_user.username}: {message.text}")
if not session.query(Chat.id).filter(Chat.chat_id == message.chat.id).first():
session.add(Chat(chat_id=message.chat.id, title=message.chat.title, antispam=1, report=0, reminder=1))
session.commit()
await bot.send_message(
message.chat.id,
f"""Группа "{message.chat.title} добавлена в БД.
/get_group_info - узнать текущие настройки""",
)
else:
await bot.send_message(message.chat.id, "Отказ. Группа уже включена")
@bot.message_handler(func=is_admin, commands=["disable_stan"], chat_types=["supergroup", "group"])
async def disable_stan(message: types.Message):
logging.info(f"[{message.chat.title}] [{message.from_user.id}] {message.from_user.username}: {message.text}")
if session.query(Chat.id).filter(Chat.chat_id == message.chat.id).first():
chat = session.query(Chat).filter_by(chat_id=message.chat.id).first()
session.delete(chat)
session.commit()
await bot.send_message(message.chat.id, f"Группа {message.chat.title} и все связанные с ней цитаты удалёны!")
else:
await bot.send_message(message.chat.id, "Отказ. Этой группы нет в БД.")
@bot.message_handler(
func=is_white,
commands=["set_antispam_report_reminder", "set_rules"],
chat_types=["supergroup", "group"],
)
async def set_antispam_report_reminder(message: types.Message):
args = message.text.split()
if len(args) == 4:
try:
antispam = int(args[1])
rep = int(args[2])
rem = int(args[3])
except ValueError:
logging.info(f"[ERROR] Неверные аргументы {message.text}")
else:
session.query(Chat).filter_by(chat_id=message.chat.id).update(
{"antispam": antispam, "report": rep, "reminder": rem}
)
session.commit()
await bot.send_message(message.chat.id, f"""Настройки обновлены. Проверить: /get_group_info""")
@bot.message_handler(func=is_white, commands=["get_quotes"], chat_types=["supergroup", "group"])
async def get_quotes(message: types.Message):
logging.info(f"[{message.chat.id}] {message.from_user.first_name} {message.text}.")
quotes = session.query(Quote.text).filter(Quote.chat_id == message.chat.id).all()
if quotes:
length = len(session.query(Quote).filter(Quote.chat_id == message.chat.id).all())
text = f"Количество цитат: {length}\n\Последние добавленные\n\n"
text += "\n".join(f"· {quote[0]}" for quote in quotes[-10:])
await bot.send_message(message.chat.id, f"{text}")
else:
await bot.send_message(message.chat.id, f"Цитаты отсутствуют. Подробнее: /get_group_info")
@bot.message_handler(func=is_white, commands=["get_group_info"], chat_types=["supergroup", "group"])
async def get_group_info(message: types.Message):
group = session.query(Chat).filter(Chat.chat_id == message.chat.id).first()
if group:
await bot.send_message(
message.chat.id,
f"""Группа: {group.title}
ID группы: {group.chat_id}
Количество цитат: {len(session.query(Quote).filter(Quote.chat_id == message.chat.id).all())}
Последние добавленные: /get_quotes
Текущие настройки:
Антиспам: {group.antispam}
Ежедневные отчёты: {group.report}
Праздники: {group.reminder}""",
)
else:
await bot.send_message(message.chat.id, f"Группа не включена. Включить: /enable_stan")
@bot.message_handler(func=is_white, commands=["set_logging_level"], chat_types=["supergroup", "group"])
async def set_logging_level(message: types.Message):
args = message.text.split()
if args[-1] == '10':
logger.setLevel(LOGGING_LEVEL_DEBUG)
logging.info("[LEVEL] Установлен уровень DEBUG")
else:
logger.setLevel(LOGGING_LEVEL_INFO)
logging.info("[LEVEL] Установлен уровень INFO")
+368
View File
@@ -0,0 +1,368 @@
import logging
import logging.handlers
import shelve
from telebot import types
from .constants import DATA, LOG_COMM, FAQ, LIB, RULES, RUS, RUS_ENG_TABLE, ENG_RUS_TABLE, PYTHONCHATRU, ZEN
from .filters import in_spam_list, in_caption_spam_list, in_delete_list, is_admin
from .helpers import represent_as_get, detect_args, update_stats
from .admin_commands import bot
from .rules import fetch_rule
from .stan import act, speak
async def send_or_reply(message: types.Message, answer, **kwargs):
if message.reply_to_message:
await bot.reply_to(message.reply_to_message, answer, **kwargs)
else:
await bot.send_message(message.chat.id, answer, **kwargs)
""" [ ANTISPAM ] """
@bot.edited_message_handler(func=in_spam_list, chat_types=["supergroup", "group"])
@bot.message_handler(func=in_spam_list, chat_types=["supergroup", "group"])
async def moderate_messages(message: types.Message):
"""Ban user and delete their message."""
await bot.delete_message(message.chat.id, message.id)
await bot.ban_chat_member(message.chat.id, message.from_user.id)
with shelve.open(f"{DATA}{message.chat.id}") as shelve_db:
shelve_db["Banned"] += 1
@bot.message_handler(func=in_caption_spam_list, content_types=["video"], chat_types=["supergroup", "group"])
async def catch_videos(message: types.Message):
"""Catch offensive videos"""
await bot.delete_message(message.chat.id, message.id)
await bot.ban_chat_member(message.chat.id, message.from_user.id)
with shelve.open(f"{DATA}{message.chat.id}") as shelve_db:
shelve_db["Banned"] += 1
@bot.edited_message_handler(func=in_delete_list, chat_types=["supergroup", "group"])
@bot.message_handler(func=in_delete_list, chat_types=["supergroup", "group"])
async def delete_message(message: types.Message):
"""Delete unwanted message."""
await bot.delete_message(message.chat.id, message.id)
with shelve.open(f"{DATA}{message.chat.id}") as shelve_db:
shelve_db["Deleted"] += 1
""" [ COMMANDS ] """
@bot.message_handler(commands=["start", "links", "ссылки"])
async def start(message: types.Message):
"""What to begin with."""
logging.info(LOG_COMM % (message.chat.title, message.from_user.id, message.from_user.first_name, message.text))
markup = types.InlineKeyboardMarkup([[RULES], [FAQ], [LIB]], 1)
await send_or_reply(message, "Начни с прочтения", reply_markup=markup)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["rules", "rule", "r", "правила", "правило", "п"])
async def send_rules(message: types.Message):
markup = types.InlineKeyboardMarkup([[RULES]], 1)
args = message.text.split()
logging.info(LOG_COMM % (message.chat.title, message.from_user.id, message.from_user.first_name, message.text))
if len(args) > 1 and args[-1].isdigit() and 0 < int(args[-1]):
await send_or_reply(
message,
f"<b>Правило {args[-1]}</b>\n<i>{fetch_rule(args[-1])}</i>",
reply_markup=markup,
)
else:
await send_or_reply(message, "Читай...", reply_markup=markup)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["faq", "чзв"])
async def send_faq(message: types.Message):
logging.info(
LOG_COMM
% (
message.chat.title,
message.from_user.id,
message.from_user.first_name,
message.text,
)
)
markup = types.InlineKeyboardMarkup([[FAQ]], 1)
await send_or_reply(message, "Читай...", reply_markup=markup)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["lib", "library", "books", "книги", "библиотека"])
async def send_lib(message: types.Message):
logging.info(
LOG_COMM
% (
message.chat.title,
message.from_user.id,
message.from_user.first_name,
message.text,
)
)
markup = types.InlineKeyboardMarkup([[LIB]], 1)
await send_or_reply(message, "Читай...", reply_markup=markup)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["lutz", "лутц"])
async def send_lutz(message: types.Message):
logging.info(
LOG_COMM
% (
message.chat.title,
message.from_user.id,
message.from_user.first_name,
message.text,
)
)
await bot.send_document(
message.chat.id,
document="BQACAgQAAxkBAAPBYsWJG9Ml0fPrnbU9UyzTQiQSuHkAAjkDAAIstCxSkuRbXAlcqeQpBA",
caption="вот, не позорься",
)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["bdmtss", "бдмтсс"])
async def send_bdmtss_audio(message: types.Message):
logging.info(
LOG_COMM
% (
message.chat.title,
message.from_user.id,
message.from_user.first_name,
message.text,
)
)
await bot.send_voice(
message.chat.id,
"AwACAgIAAxkBAAIJrWOg2WUvLwrf7ahyJxQHB8_nqllwAAL5JQAC2_IJSbhfQIO5YnVmLAQ",
)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["tr", "тр"])
async def translate_layout(message: types.Message):
logging.info(
LOG_COMM
% (
message.chat.title,
message.from_user.id,
message.from_user.first_name,
message.text,
)
)
if message.reply_to_message and message.reply_to_message.text:
if message.reply_to_message.text[0] in RUS:
await bot.send_message(message.chat.id, message.reply_to_message.text.translate(RUS_ENG_TABLE))
else:
await bot.send_message(message.chat.id, message.reply_to_message.text.translate(ENG_RUS_TABLE))
@bot.message_handler(commands=["quote", "цитата"])
async def stan_speak(message: types.Message):
logging.info(
LOG_COMM
% (
message.chat.title,
message.from_user.id,
message.from_user.first_name,
message.text,
)
)
await bot.send_message(message.chat.id, speak(0, message.chat.id), parse_mode='Markdown')
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["tsya", "тся", "ться"])
async def send_tsya(message: types.Message):
logging.info(
LOG_COMM
% (
message.chat.title,
message.from_user.id,
message.from_user.first_name,
message.text,
)
)
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("🧑🏼‍🎓 Читать правило", url="https://tsya.ru/"), row_width=1)
await send_or_reply(message, "<i>-тся</i> и <i>-ться</i> в глаголах", reply_markup=markup)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["nometa", "номета"])
async def send_nometa(message: types.Message):
logging.info(
LOG_COMM
% (
message.chat.title,
message.from_user.id,
message.from_user.first_name,
message.text,
)
)
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("❓ nometa.xyz", url="https://nometa.xyz/ru.html"), row_width=1)
await send_or_reply(
message,
"""Не задавай мета-вопросов вроде:
<i> «Можно задать вопрос?»
«Кто-нибудь пользовался .. ?»
«Привет, мне нужна помощь по .. !»</i>
Просто спроси сразу! И чем лучше объяснишь проблему, тем вероятнее получишь помощь.""",
reply_markup=markup,
)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["neprivet", "непривет"])
async def send_neprivet(message: types.Message):
logging.info(
LOG_COMM
% (
message.chat.title,
message.from_user.id,
message.from_user.first_name,
message.text,
)
)
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("👋 Непривет", url="https://neprivet.com/"), row_width=1)
await send_or_reply(message, "Пожалуйста, не пишите просто «Привет» в чате.", reply_markup=markup)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["nojob", "ноджоб"])
async def send_nojob(message):
answer = """Мы здесь не для того, чтобы за тебя решать задачи.
Здесь помогают по конкретным вопросам в <u>ТВОЁМ</u> коде, поэтому тебе нужно показать код, который ты написал сам и \
объяснить где и почему застрял... всё просто. 🤷🏼️"""
await send_or_reply(message, answer)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["nobot", "нобот"])
async def nobot(message: types.Message):
answer = """<b>Внимание</b>:
Телеграм бот <i>не должен</i> быть твоим первым проектом на Python. Пожалуйста, изучи <code>основы Python</code>, \
<code>работу с модулями</code>, <code>основы веб-технологий</code>, <code>асинхронное программирование</code> и \
<code>отладку</code> до начала работы с Телеграм ботами. Существует много ресурсов для этого в интернете."""
await send_or_reply(message, answer)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["nogui", "ногуи"])
async def nogui(message: types.Message):
answer = """<b>Внимание</b>:
GUI приложение <i>не должно</i> быть твоим первым проектом на Python. Пожалуйста, изучи <code>основы Python</code>, \
<code>работу с модулями</code>, <code>циклы событий</code> и <code>отладку</code> до начала работы с какими-либо \
GUI-фреймворками. Существует много ресурсов для этого в интернете."""
await send_or_reply(message, answer)
await bot.delete_message(message.chat.id, message.id)
@bot.message_handler(commands=["g", "г"])
async def google_it(message: types.Message):
"""Google it!"""
logging.info(
LOG_COMM
% (
message.chat.title,
message.from_user.id,
message.from_user.first_name,
message.text,
)
)
query = f"<i>{detect_args(message)}</i>"
get_query = f"https://www.google.com/search?q={represent_as_get(message)}"
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("🔍 Google Поиск", url=get_query), row_width=1)
await send_or_reply(message, f"<i>Ищем «{query}»...</i>", reply_markup=markup)
""" [ ADMIN PANEL ] """
@bot.message_handler(func=is_admin, commands=["ddel"])
async def delete_user(message: types.Message):
if message.reply_to_message:
await bot.delete_message(message.chat.id, message.id)
await bot.delete_message(message.chat.id, message.reply_to_message.id)
logging.info(
"!DEL!M "
+ LOG_COMM
% (
message.chat.title,
message.reply_to_message.from_user.id,
message.reply_to_message.from_user.first_name,
message.reply_to_message.text,
)
)
@bot.message_handler(func=is_admin, commands=["bban"])
async def ban_user(message: types.Message):
if message.reply_to_message:
await bot.delete_message(message.chat.id, message.id)
await bot.delete_message(message.chat.id, message.reply_to_message.id)
await bot.ban_chat_member(message.chat.id, message.reply_to_message.from_user.id)
logging.info(
f"!BAN!M "
+ LOG_COMM
% (
message.chat.title,
message.reply_to_message.from_user.id,
message.reply_to_message.from_user.first_name,
message.reply_to_message.text,
)
)
@bot.message_handler(func=is_admin, commands=["unban_id"])
async def unban_user(message: types.Message):
if message.text.split()[-1].isdigit():
user_id = int(message.text.split()[-1])
await bot.unban_chat_member(PYTHONCHATRU, user_id)
logging.info(f"!UNBAN (M)! {user_id}")
""" [ INLINE ] """
@bot.inline_handler(lambda query: True)
async def default_query(inline_query):
"""Inline the Zen of Python."""
zen = []
for id_p, phrase in enumerate(ZEN):
q = inline_query.query.casefold()
if phrase.casefold().startswith(q) or " " + q in phrase.casefold():
zen.append(
types.InlineQueryResultArticle(
f"{id_p}",
f"The Zen of Python #{id_p + 1}",
types.InputTextMessageContent(f"{phrase}"),
description=phrase,
)
)
await bot.answer_inline_query(inline_query.id, zen, cache_time=1200)
""" [ COUNTER ] """
@bot.message_handler(content_types=["text", "sticker", "photo", "animation", "video", "audio", "document"],
chat_types=["supergroup", "group"])
async def handle_msg(message: types.Message):
"""Count messages, Stan."""
update_stats(message)
await act(message)
+26
View File
@@ -0,0 +1,26 @@
import logging
import os
from urllib.error import URLError
from urllib.request import urlopen
from dotenv import load_dotenv
from telebot import logger
load_dotenv()
TOKEN = os.environ.get("LUTZPYBOT")
if not TOKEN:
raise LookupError("LUTZPYBOT (token) has not been found in .env")
WHITEIDS = {int(i) for i in os.environ.get("whiteids").split(",")}
ROLLBACK = {int(i) for i in os.environ.get("rollback").split(",")}
USE_REMINDER = os.environ.get("use_reminder", "TRUE") == "TRUE"
RULES_URL = os.environ.get("rules_url", "https://telegra.ph/pythonchatru-07-07")
try:
assert urlopen(RULES_URL).status == 200
except (URLError, AssertionError):
raise LookupError(f"LUTZPYBOT rules url({RULES_URL}) is not reachable")
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
logger.setLevel(logging.DEBUG)
+80
View File
@@ -0,0 +1,80 @@
import re
from telebot import types
LOG_TEXT = "[%s] %s: %s"
LOG_COMM = "[%s] [%s] %s: %s"
LOGGING_LEVEL_DEBUG = 10
LOGGING_LEVEL_INFO = 20
DATA = "data/chat"
ADMIN_ID = 280887861 # Rykov7
PYTHONCHATRU = -1001338616632 # pythonchatru
URL_RX = re.compile(r"\w+\.\w+/(\+)?\w+")
ALLOWED_WORDS = [
"paste",
"nekobin",
"github",
"google",
"nometa",
"python",
"django",
"flask",
"fastapi",
"wiki",
"stackoverflow",
"rykov7",
"habr",
"medium",
"youtu",
"rutube",
"stepik",
"digitalocean",
"gra.ph",
"#rtfm",
"support",
"jetbrains",
]
RUS = """ёйцукенгшщзхъфывапролджэячсмитьбю.Ё!"№;%:?ЙЦУКЕНГШЩЗХЪ/ФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,"""
ENG = """`qwertyuiop[]asdfghjkl;'zxcvbnm,./~!@#$%^&QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?"""
RUS_ENG_TABLE = str.maketrans(RUS, ENG)
ENG_RUS_TABLE = str.maketrans(ENG, RUS)
RULES = types.InlineKeyboardButton("🟡 Правила чата", url="https://telegra.ph/pythonchatru-07-07")
FAQ = types.InlineKeyboardButton("🔵 Частые вопросы", url="https://telegra.ph/faq-10-07-4")
LIB = types.InlineKeyboardButton("📚 Книги", url="https://telegra.ph/what-to-read-10-06")
SPAM = {"me.sv/", "tg.sv/", "goo.by/", "go.sv/", "intim.video/", "uclck.ru/", 'раб0т',
'0плат', 'Жду в ЛС', 'новый способ заработка', 'криптовалют', 'цифровых валют',
'Binance', 'ByBit', 'Помогу заработать', 'подробнее в лс', 'арбитраж', 'напиши в личку',
'DEX бирж', 'пишите в ЛС', 'Писать в ЛИЧКУ!', 'любую девушку', 'Пишите в личку',
'прибыль у вaс', 'обучaю быстро', 'в л.с.', 'вопросам в ЛС', 'Предлагаю новый вид заработка',
'пишите мне в ЛС', 'Писать в ЛС', 'межбиржев', 'КУПЮРЫ', '💎', 'kрипт', 'биpж', 'apбитpaж',
'в неделю выходит до', 'предложить заработок', 'голые фот', '🔞', 'интимные фот', '➡️',
'заработка в крипте', 'Веду набор', 'Идёт набор', 'мне в ЛС', 'Доход от', 'Trading',
'P2P', 'Идет набор', 'в лс', 'нaбop', 'комaндy', 'kpuпт', 'бupж', '💰', 'пишитe', '💸',
'$ в неделю', 'обучаю с нуля', 'доходы для всех', 'по трейдингу', 'фальшивые рубли', '‼️',
'зароботку', 'Набuраю', 'цuфр', 'Обучу вас', 'прuб', 'остались курсы', 'Crypt', 'kр', 'рu'}
NON_GRATA = {"дудар", "хауди", "dudar"}
BAN_WORDS = {"GREEN"}
ZEN = [
"Beautiful is better than ugly.",
"Explicit is better than implicit.",
"Simple is better than complex.",
"Complex is better than complicated.",
"Flat is better than nested.",
"Sparse is better than dense.",
"Readability counts.",
"Special cases aren't special enough to break the rules. Although practicality beats purity.",
"Errors should never pass silently. Unless explicitly silenced.",
"In the face of ambiguity, refuse the temptation to guess.",
"There should be one — and preferably only one — obvious way to do it.",
"Now is better than never. Although never is often better than *right* now.",
"If the implementation is hard to explain, it's a bad idea.",
"If the implementation is easy to explain, it may be a good idea.",
"Namespaces are one honking great idea — let's do more of those!",
]
+69
View File
@@ -0,0 +1,69 @@
import logging.handlers
from telebot import types
from .config import WHITEIDS
from .constants import BAN_WORDS, LOG_COMM, SPAM, ALLOWED_WORDS, URL_RX, NON_GRATA, ADMIN_ID
from .models import Chat
from .models import session
def in_spam_list(message: types.Message) -> bool:
"""Check for mentioning unwanted persons in text."""
antispam_is_enabled = session.query(Chat.antispam).filter_by(chat_id=message.chat.id).first()[0]
from_user_id, title, from_user_name = message.from_user.id, message.chat.title, message.from_user.first_name
if from_user_id not in WHITEIDS and antispam_is_enabled:
for text in SPAM:
if text.casefold() in message.text.casefold():
logging.info("!BAN!" + LOG_COMM % (title, from_user_id, from_user_name, message.text))
return True
return False
def in_caption_spam_list(message: types.Message) -> bool:
"""Check for mentioning unwanted words in caption."""
for phrase in BAN_WORDS:
if message.caption and phrase in message.caption:
logging.info("!BAN!" + LOG_COMM % (
message.chat.title, message.from_user.id, message.from_user.first_name, message.video.file_name))
return True
return False
def in_not_allowed(word_list, msg):
for word in word_list:
if word in msg.casefold():
return False
return True
def in_delete_list(message: types.Message) -> bool:
"""Check for URLs in message and delete."""
antispam_is_enabled = session.query(Chat.antispam).filter_by(chat_id=message.chat.id).first()[0]
from_user_id, title, from_user_name = message.from_user.id, message.chat.title, message.from_user.first_name
if from_user_id not in WHITEIDS and antispam_is_enabled:
if URL_RX.search(message.text) and in_not_allowed(ALLOWED_WORDS, message.text):
logging.info(f"[DELETE] [{title}] [{from_user_id}] {from_user_name}: {message.text}")
return True
if message.entities:
for entity in message.entities:
if entity.url and in_not_allowed(ALLOWED_WORDS, entity.url):
logging.info(f"[DELETE] [{title}] [{from_user_id}] {from_user_name}: [entity] {message.text}")
return True
return False
def is_nongrata(type_message: types.Message) -> bool:
"""Check for non grata persons."""
for phrase in NON_GRATA:
if phrase in type_message.text.casefold():
return True
return False
def is_admin(message: types.Message) -> bool:
return message.from_user.id == ADMIN_ID
def is_white(message: types.Message) -> bool:
return message.from_user.id in WHITEIDS
+62
View File
@@ -0,0 +1,62 @@
""" Send User's info. """
import logging
import shelve
from pathlib import Path
from urllib import parse
from urllib.request import urlopen
from telebot import types
from .constants import DATA
from .report import reset_report_stats
def my_ip():
ip_service = "https://icanhazip.com"
ip = urlopen(ip_service)
return ip.read().decode("utf-8")
def me(message: types.Message):
msg = f"<b>Telegram ID:</b> <code>{message.from_user.id}</code>\n\
├ <b>Is bot:</b> {message.from_user.is_bot}\n\
├ <b>First name:</b> {message.from_user.first_name}\n"
if message.from_user.last_name:
msg += f" ├ <b>Last name:</b> {message.from_user.last_name}\n"
if message.from_user.username:
msg += f" ├ <b>Username:</b> {message.from_user.username}\n"
msg += f" ├ <b>Language code:</b> {message.from_user.language_code}\n"
if message.from_user.is_premium is True:
msg += f" ├ <b>Is Premium</b>: ⭐️\n"
else:
msg += f" ├ <b>Is Premium</b>: No\n"
msg += f" ├ <b>Chat ID:</b> {message.chat.id}"
return msg
def represent_as_get(message: types.Message):
return parse.quote_plus(detect_args(message))
def detect_args(message: types.Message):
if message.reply_to_message and message.reply_to_message.text:
if len(message.text.split()) == 1:
return message.reply_to_message.text
else:
return " ".join(message.text.split()[1:])
else:
if len(message.text.split()) > 1:
return " ".join(message.text.split()[1:])
def update_stats(message: types.Message):
if not Path(f"{DATA}{message.chat.id}").exists():
reset_report_stats(message.chat.id)
with shelve.open(f"{DATA}{message.chat.id}", writeback=True) as shelve_db:
if "Messages" not in shelve_db:
reset_report_stats(message.chat.id)
if message.from_user.id not in shelve_db["Messages"]:
shelve_db["Messages"][message.from_user.id] = {"User": message.from_user, "Count": 1}
logging.info(f"[{message.chat.title[:10]}] [{message.from_user.id}] {message.from_user.first_name}")
else:
shelve_db["Messages"][message.from_user.id]["Count"] += 1
+51
View File
@@ -0,0 +1,51 @@
from pathlib import Path
from sqlalchemy import Column, String, Integer, Boolean, ForeignKey
from sqlalchemy import create_engine
from sqlalchemy.orm import relationship, declarative_base, sessionmaker
Base = declarative_base()
root = Path(__file__).parent.parent
data_folder = root / "data"
db_path = data_folder / "db.sqlite"
engine = create_engine(f'sqlite:///{db_path}')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
class Permission:
USE_COMMANDS = 1 # Пользоваться обычными командами.
SEND_LINKS = 2 # Может отправлять ссылки без удаления.
MODIFY_QUOTES = 4 # Может добавлять и удалять цитаты.
MODERATE = 8
ADMIN = 16 # Может банить юзеров и удалять сообщения.
class Quote(Base):
__tablename__ = "quotes"
id = Column(Integer, primary_key=True)
chat_id = Column(Integer, ForeignKey('chats.chat_id'))
text = Column(String)
def __repr__(self):
return f"Quote({self.text=}, {self.chat_id=})"
class Chat(Base):
__tablename__ = "chats"
id = Column(Integer, primary_key=True)
chat_id = Column(Integer, unique=True)
title = Column(String)
# Settings
quotes = relationship('Quote', backref='chat', cascade='all, delete')
antispam = Column(Boolean)
report = Column(Boolean)
reminder = Column(Boolean)
hidden = Column(Boolean)
def __repr__(self):
return f'Chat({self.chat_id=}, {self.title=})'
+70
View File
@@ -0,0 +1,70 @@
""" Schedule holidays. """
import csv
import logging
import time
from datetime import datetime as dt
from threading import Thread
from schedule import repeat, every, get_jobs, run_pending
from telebot import TeleBot
from .config import TOKEN, USE_REMINDER
from .constants import PYTHONCHATRU
from .report import create_report_text, reset_report_stats
if USE_REMINDER:
logging.debug("Starts reminder!!!")
# We need synchronous instance of bot here as we use synchronous library 'schedule' in a separate thread.
bot = TeleBot(TOKEN, "HTML", disable_web_page_preview=True, allow_sending_without_reply=True)
else:
bot = None
def scheduler():
"""Main loop of schedule library."""
while True:
run_pending()
time.sleep(60)
@repeat(every().day.at("07:00"), PYTHONCHATRU, None)
def remind(chat_to_repeat, today):
"""Remind holiday."""
if not today:
today = dt.today()
with open("holidays.csv", newline="", encoding="utf-8") as holidays_file:
holidays = tuple(csv.reader(holidays_file))[1:]
for entry in holidays:
date, holiday, description = entry
if today.year % 4 == 0 and holiday == "День программиста":
date = "09-12-1000" # instead of 09-13-1000 in non-leap-year
date = dt.strptime(date, "%m-%d-%Y")
if today.month == date.month and today.day == date.day:
notification = f"🎉 Сегодня <b>{holiday.upper()}</b>!\n\n{description}"
if date.year != 1000:
age = today.year - date.year
notification += f"\n\n🥳 <i>{age}-ая годовщина</i>"
bot.send_message(chat_to_repeat, notification)
@repeat(every().day.at("06:00"), PYTHONCHATRU)
def stat_report(chat_to_repeat):
"""Everyday group statistic info."""
rep = create_report_text(PYTHONCHATRU)
if rep:
bot.send_message(chat_to_repeat, rep)
reset_report_stats(PYTHONCHATRU)
def print_get_jobs():
"""Get jobs from schedule and prepare a formatted info text."""
all_jobs = get_jobs()
text = f"<b>{len(all_jobs)} jobs</b>\
\n..:: {dt.now().strftime('%d-%m-%Y %H:%M:%S')} ::..\n"
for i in all_jobs:
text += f"\n · {i!r}"
return text
if USE_REMINDER:
Thread(target=scheduler).start()
+65
View File
@@ -0,0 +1,65 @@
import html
import os
import random
import shelve
from .config import ROLLBACK
from .constants import DATA
def create_report_text(chat_id):
report = ""
top_users = ""
flooders = []
spammer_icons = ('🐷', '🐒', '🐔')
current_spammer_face = random.choice(spammer_icons)
if os.path.exists(f"{DATA}{chat_id}.db"):
with shelve.open(f"{DATA}{chat_id}", writeback=True) as s:
for user_id in ROLLBACK:
if user_id in s["Messages"] and s["Messages"][user_id]["Count"] > 10:
s["Messages"][user_id]["Count"] //= 3
for n in range(min(3, len(s["Messages"]))):
top_user = s["Messages"][
sorted(
s["Messages"],
key=lambda a: s["Messages"][a]["Count"],
reverse=True,
)[n]
]
if top_user["Count"] >= 5:
flooders.append(top_user)
for i, flooder in enumerate(flooders):
user = flooder["User"]
name = (
f"{user.first_name} {user.last_name}"
if user.last_name
else user.first_name
)
top_users += f'\n{i + 1}. {html.escape(name)} [{flooder["Count"]}]'
if len(flooders) >= 3 or s["Banned"] or s["Deleted"]:
report = f"<b>H</b>ello, <b>W</b>orld!\n"
if len(flooders) >= 3:
report += f"{top_users}\n"
if s["Banned"]:
report += f"""
<b> Криптоидов поймано:</b> {s['Banned']}
└ [ {s['Banned'] * current_spammer_face} ]
"""
return report
else:
return f"Невозможно получить статистику.\n{DATA}{chat_id}.db не существует, возможно запрос в приватном чате. "
def reset_report_stats(chat_id):
with shelve.open(f"{DATA}{chat_id}") as shelve_db:
shelve_db["Messages"] = {}
shelve_db["Banned"] = 0
shelve_db["Deleted"] = 0
return f"""Chat ID: {chat_id}
{shelve_db['Messages']=}
{shelve_db['Banned']=}
{shelve_db['Deleted']=}"""
+14
View File
@@ -0,0 +1,14 @@
import logging
from telebot.async_telebot import AsyncTeleBot
from .config import TOKEN
logging.warning("[START] Stan")
bot = AsyncTeleBot(
TOKEN,
"HTML",
disable_web_page_preview=True,
allow_sending_without_reply=True,
colorful_logs=True,
)
+16
View File
@@ -0,0 +1,16 @@
from urllib.request import urlopen
from bs4 import BeautifulSoup
from .config import RULES_URL
html = BeautifulSoup(urlopen(RULES_URL), "lxml")
def fetch_rule(li):
li = int(li)
lis = html.find_all("li")
if li <= len(lis):
return lis[li - 1].text
else:
return f"Пока не придумали."
+68
View File
@@ -0,0 +1,68 @@
""" Stan's commands and reactions. """
import asyncio
import html
import random
from sqlalchemy import delete
from telebot import types
from .filters import is_white, is_nongrata
from .models import Quote
from .models import session
from .robot import bot
TYPING_TIMEOUT = 0.13 / 4 # Reading time is quarter of the same text writing time
def speak(chance_of, group_id):
number = random.randint(0, chance_of)
if number == 0:
return random.choice([i[0] for i in session.query(Quote.text).filter(Quote.chat_id == group_id).all()])
async def send_quote(after_sec, message, quote):
"""Pretend Reading, pretend Typing, send."""
if message.text:
await asyncio.sleep(len(message.text) * TYPING_TIMEOUT)
await bot.send_chat_action(message.chat.id, action="typing")
await asyncio.sleep(after_sec) # Typing time
await bot.send_message(message.chat.id, html.escape(quote))
async def act(message: types.Message):
quote = speak(50, message.chat.id)
if quote:
await send_quote(len(quote) * 0.13, message, quote)
@bot.message_handler(func=is_white, commands=["add"])
async def add_stan_quote(message: types.Message):
if message.reply_to_message and message.reply_to_message.text:
quote = message.reply_to_message.text
if quote not in [i[0] for i in session.query(Quote.text).filter(Quote.chat_id == message.chat.id).all()]:
session.add(Quote(chat_id=message.chat.id, text=quote.replace("\n", " ")))
session.commit()
await bot.send_message(message.chat.id, "➕\n └ " + quote.replace("\n", " "), parse_mode='Markdown')
await bot.delete_message(message.chat.id, message.id)
else:
await bot.send_message(message.chat.id, f"⛔️ Не добавил, есть токое\n └ {quote}", parse_mode='Markdown')
@bot.message_handler(func=is_white, commands=["remove"])
async def remove_stan_quote(message: types.Message):
if message.reply_to_message and message.reply_to_message.text:
quote = message.reply_to_message.text
already_exist = session.query(Quote.text).filter_by(text=quote, chat_id=message.chat.id).first()
if already_exist:
session.execute(delete(Quote).filter_by(text=quote, chat_id=message.chat.id))
session.commit()
await bot.send_message(message.chat.id, f"➖ \n └ {quote}", parse_mode='Markdown')
await bot.delete_message(message.chat.id, message.id)
else:
await bot.send_message(message.chat.id, f"⛔️ Нет такого\n └ {quote}", parse_mode='Markdown')
@bot.message_handler(func=is_nongrata)
async def tease_nongrata(message: types.Message):
"""Reply to non grata mentions."""
await bot.reply_to(message, "у нас тут таких не любят")
+20
View File
@@ -0,0 +1,20 @@
import logging
import telebot
from fastapi import FastAPI
from .commands import bot
from .config import TOKEN
logging.warning("[START] Webhook server")
app = FastAPI(docs=None, redoc_url=None)
@app.post(f"/bot{TOKEN}/")
async def webhook(update: dict):
"""Parse POST requests from Telegram."""
if update:
update = telebot.types.Update.de_json(update)
await bot.process_new_updates([update])
else:
return None