From a7bfd5ed4ab0763766df17be8bfcd7f11708d2fe Mon Sep 17 00:00:00 2001 From: MiishaLom Date: Wed, 17 Jul 2024 23:35:45 +0300 Subject: [PATCH] Initial commit --- Dockerfile | 13 + README.md | 36 +- config.py | 27 ++ core.py | 8 + data/ban_words.xlsx | Bin 0 -> 5071 bytes docker-compose.yml | 33 ++ handlers/__init__.py | 8 + handlers/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 239 bytes handlers/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 590 bytes .../__pycache__/ban_media.cpython-311.pyc | Bin 0 -> 3302 bytes .../__pycache__/ban_words.cpython-311.pyc | Bin 0 -> 6738 bytes .../__pycache__/come_back.cpython-311.pyc | Bin 0 -> 5015 bytes handlers/__pycache__/commands.cpython-310.pyc | Bin 0 -> 596 bytes handlers/__pycache__/commands.cpython-311.pyc | Bin 0 -> 1968 bytes handlers/__pycache__/group.cpython-311.pyc | Bin 0 -> 3941 bytes handlers/__pycache__/message.cpython-311.pyc | Bin 0 -> 11383 bytes handlers/ban_media.py | 66 +++ handlers/ban_words.py | 134 +++++ handlers/come_back.py | 115 +++++ handlers/commands.py | 39 ++ handlers/group.py | 92 ++++ handlers/message.py | 252 ++++++++++ main.py | 36 ++ requirements.txt | 7 + templates/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 179 bytes .../__pycache__/ban_words.cpython-311.pyc | Bin 0 -> 2502 bytes .../__pycache__/commands.cpython-311.pyc | Bin 0 -> 2523 bytes templates/__pycache__/message.cpython-311.pyc | Bin 0 -> 8576 bytes templates/ban_words.py | 64 +++ templates/commands.py | 62 +++ templates/message.py | 226 +++++++++ templates/moderation.py | 458 ++++++++++++++++++ utils/__init__.py | 0 utils/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 119 bytes utils/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 170 bytes utils/__pycache__/db.cpython-311.pyc | Bin 0 -> 19954 bytes utils/__pycache__/defs.cpython-311.pyc | Bin 0 -> 2101 bytes utils/__pycache__/middleware.cpython-311.pyc | Bin 0 -> 177 bytes utils/db.py | 250 ++++++++++ utils/defs.py | 44 ++ utils/middleware.py | 73 +++ 42 files changed, 2041 insertions(+), 2 deletions(-) create mode 100644 Dockerfile create mode 100644 config.py create mode 100644 core.py create mode 100644 data/ban_words.xlsx create mode 100644 docker-compose.yml create mode 100644 handlers/__init__.py create mode 100644 handlers/__pycache__/__init__.cpython-310.pyc create mode 100644 handlers/__pycache__/__init__.cpython-311.pyc create mode 100644 handlers/__pycache__/ban_media.cpython-311.pyc create mode 100644 handlers/__pycache__/ban_words.cpython-311.pyc create mode 100644 handlers/__pycache__/come_back.cpython-311.pyc create mode 100644 handlers/__pycache__/commands.cpython-310.pyc create mode 100644 handlers/__pycache__/commands.cpython-311.pyc create mode 100644 handlers/__pycache__/group.cpython-311.pyc create mode 100644 handlers/__pycache__/message.cpython-311.pyc create mode 100644 handlers/ban_media.py create mode 100644 handlers/ban_words.py create mode 100644 handlers/come_back.py create mode 100644 handlers/commands.py create mode 100644 handlers/group.py create mode 100644 handlers/message.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 templates/__init__.py create mode 100644 templates/__pycache__/__init__.cpython-311.pyc create mode 100644 templates/__pycache__/ban_words.cpython-311.pyc create mode 100644 templates/__pycache__/commands.cpython-311.pyc create mode 100644 templates/__pycache__/message.cpython-311.pyc create mode 100644 templates/ban_words.py create mode 100644 templates/commands.py create mode 100644 templates/message.py create mode 100644 templates/moderation.py create mode 100644 utils/__init__.py create mode 100644 utils/__pycache__/__init__.cpython-310.pyc create mode 100644 utils/__pycache__/__init__.cpython-311.pyc create mode 100644 utils/__pycache__/db.cpython-311.pyc create mode 100644 utils/__pycache__/defs.cpython-311.pyc create mode 100644 utils/__pycache__/middleware.cpython-311.pyc create mode 100644 utils/db.py create mode 100644 utils/defs.py create mode 100644 utils/middleware.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..79369da --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.11 + +WORKDIR /moderaotrbot +COPY ./ ./ + +RUN rm -rf /etc/localtime +RUN ln -s /usr/share/zoneinfo/Europe/Moscow /etc/localtime +RUN echo "Europe/Moscow" > /etc/timezone + +RUN pip install --upgrade pip +RUN pip install --no-cache-dir -r requirements.txt + +CMD ["python","-u", "main.py"] diff --git a/README.md b/README.md index 32a8c09..dca4506 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,35 @@ -# chat_moderator_bot +## Бот транскрибатор -Удаляет сообщения из группы с бан-вордами. После, скидывает изменяемое в админ-панели сообщение \ No newline at end of file +### Конфигурация .env + +``` +BOT_TOKEN - Токен бота + +ADMINS - Админы, для которых работает /start. Записывать через запятую: user_id1,user_id2,user_id3 и т.д. + +POSTGRES_NAME - Имя postgres бд +POSTGRES_HOST - Хост postgres бд +POSTGRES_PORT - Порт postgres бд +POSTGRES_PASSWORD - Пароль от postgres бд +POSTGRES_USER - Пользовтаель postgres бд + +REDIS_NAME - Имя redis бд +REDIS_HOST - Хост redis бд +REDIS_PORT - Порт redis бд +REDIS_PASSWORD - Пароль redis бд +``` + +## Шаг 1: Получение Токена бота + +1. Перейдите в [BotFather](https://t.me/BotFather) +2. Скопируйте Токен вашего телеграмм бота + +## Шаг 2: Загрузка переменных в окружение + +1. Создайте файл `.env` в корневой папке проекта и заполните его значениями выше. + +2. Скачайте необходимые проекту библиотеки командой: + + ``` + pip install -r requirements.txt + ``` diff --git a/config.py b/config.py new file mode 100644 index 0000000..566a21e --- /dev/null +++ b/config.py @@ -0,0 +1,27 @@ +from os import getenv +from aiogram.types import BotCommand +from dotenv import load_dotenv + +load_dotenv() + +BOT_TOKEN: str = getenv('BOT_TOKEN') + +ADMINS: list = [int(admin) for admin in getenv('ADMINS').split(',')] + +POSTGRES_NAME: str = getenv('POSTGRES_NAME') +POSTGRES_HOST: str = getenv('POSTGRES_HOST') +POSTGRES_PORT: int = int(getenv('POSTGRES_PORT')) +POSTGRES_PASSWORD: str = getenv('POSTGRES_PASSWORD') +POSTGRES_USER: str = getenv('POSTGRES_USER') + +REDIS_NAME: int = int(getenv('REDIS_NAME')) +REDIS_HOST: str = getenv('REDIS_HOST') +REDIS_PORT: int =int(getenv('REDIS_PORT')) +REDIS_PASSWORD: str = getenv('REDIS_PASSWORD') + +commands = [ + BotCommand( + command='start', + description='Меню' + ) +] \ No newline at end of file diff --git a/core.py b/core.py new file mode 100644 index 0000000..4b67fa8 --- /dev/null +++ b/core.py @@ -0,0 +1,8 @@ +from aiogram import Bot + +from config import BOT_TOKEN + +bot = Bot( + token=BOT_TOKEN, + parse_mode="HTML" +) diff --git a/data/ban_words.xlsx b/data/ban_words.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5cf9c43e4998363aaf9940891ff8c9627c6d061d GIT binary patch literal 5071 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 literal 0 HcmV?d00001 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..275e466 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +services: + bot: + build: + context: . + restart: always + depends_on: + - db + env_file: + - .env + + db: + image: postgres + restart: always + environment: + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_NAME} + expose: + - '5432' + volumes: + - postgres_data:/var/lib/postgresql/data + + redis: + image: redis:latest + restart: always + command: [ "redis-server", "--requirepass", "4CEqaD0JL8gTM4XWVt8K" ] + expose: + - '6379' + volumes: + - redis_data:/data + +volumes: + postgres_data: + redis_data: diff --git a/handlers/__init__.py b/handlers/__init__.py new file mode 100644 index 0000000..93a84bc --- /dev/null +++ b/handlers/__init__.py @@ -0,0 +1,8 @@ +from handlers.ban_words import router as rban_words +from handlers.commands import router as rcommands +from handlers.come_back import router as rcome_back +from handlers.message import router as rmessage +from handlers.group import router as rgroup +from handlers.ban_media import router as rban_media + +routers = [rcommands, rban_words, rcome_back, rmessage, rgroup, rban_media] \ No newline at end of file diff --git a/handlers/__pycache__/__init__.cpython-310.pyc b/handlers/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1645c6ed99441de0c0b3a81d58a7b79695517cf7 GIT binary patch literal 239 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 GfByxafe#Y^ literal 0 HcmV?d00001 diff --git a/handlers/__pycache__/come_back.cpython-311.pyc b/handlers/__pycache__/come_back.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a55226350ba9004bbc2bf392b780a5b190a75152 GIT binary patch literal 5015 zcmdT{U1%KF6}~gGyV{-o(Ms~}N|xQO6Gzh8K~@|^F~V-42GhD&Bo#?uQCU`+Ygy9F zu5xEKuBaMB-QQ4fC;=BrK~HXl^-xNoCVzd-+pL)2WkBGzFYQy;IC{xb&$;urBRTp> z4572L=brob-8*;A`OcX?rqeQk>(dW@dHo+rLjHvn?~1z0;|Ys@L_>n?Y5J+5Z8o^p>s zL_&qTu z!j+O>r!QAkb+$UU^lF2$6=;yJOkcZPuNm}5#s+lHR%?};b*AW*S)&HsT^iN(>O2KC zK3CVYYE98$T+(fJN689RsX;56K5zHHFj~PSDzgp4sMmBmG6##=QQfE-bdFn#YbI#t zx8VQyL$I9h69H^}RakdB!$sJJZh=SqSbCk@k$w^RiMT2%;($MV-M2!&2^1p|)D|UA zH#|>+hDCowc&$z8F6{Z9zwgeq1!92jde^GB9`p3hQi+7UV>24AhY{xqJWnim9;NFd zfWf(Rbz*vYcj!=H#eWj zVqmXMC!2%n;(CBvD2e&zDy z%xgMj`i!Qjb#3OAmAM;LroF=Ii*(M=XWpo@rI~AWg|ezqXWywCGdIAMRh;1a>*(@| zo%3t{GXN2=5-gK}zfQrq{tdr1($13plXu?!?c2@5uvHi~dv@PzS0Bp9-Yr{l@qv71 zOFpxm&ROZgNS-G)xlqAnSB2qH;8HzTMq6df_nIv)3y>kG}uSWE7HAP;WIgRK)o=I}-9#HF@G zk|&Oen#|rl_s+TPOy0^At;`5w96uU{zwgIz+N6_AT?2bEe`f!0`4c#)gM;y*oh!si zKC3SJ9JslQQ^Op}IVs9|0c(n34#O~EVJAS8;_M{q-$3zA5U$I5-p~EnaapekUG_8% ze+~r_BpU+ZIUgH=N~h;|^U!n7LEl*%{0WG!(R141IZw|z*%^G~j!^H?YREbPUR;O#RGdcU!?(C01_U_8Fy^usLFgEji^V-}z+-uQ1LH8{ z+c@?b2%wfi1IPY(;1t-33CqW6a!Yxj?Jce+Wl= zc=E^%yf^b8H?ox**-q!J^r`K^)7Icemlfh+qTLLq;CDwM{~<2y&!+{kG9(7O*UKqK{nKOU?cgC)TUH^N)t%^xVVB|O1;fx_XZ?9@9X7Edae__6fU$J%s({Y}o@^I_;4j$MB;qR4)htJ~|wYt(!>9^STpbNQ5 zPXL#;B|#8c}MWNW=YIG}|5HNktSA@~GU z8!(J8ni4@{N{#QNj&Nhw`fgeiUhGlA>4d~S_b&qOvD$=QQs!}Y;4q&BCy3)ZtM^H( z@eg>UMJsYPbt}Z(@8G`Uk2$z#P#7ashgm zbRERj2uL|7;}(5fE<|(DKlM!i*0<$xr0?K9=xhC`Z^}v3v4!lz?l_T&h)}W{%_+U) zCC>{v>e`_A2wZ)R|4)EBHIRB50s9)kG%AmPKGVO-?|O;~x909f-#NL0;v)Xw+ETuC#uc=0ti*h?{cB@IU9OS_3G5HEYMfR@!e(=adVD U%2&-AR0tE2kcQNELU)Ni0}aovlK=n! literal 0 HcmV?d00001 diff --git a/handlers/__pycache__/commands.cpython-311.pyc b/handlers/__pycache__/commands.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8c29a2b4143fe91e8069d481ee8f01e614f3d4f GIT binary patch literal 1968 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 literal 0 HcmV?d00001 diff --git a/handlers/__pycache__/message.cpython-311.pyc b/handlers/__pycache__/message.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de0cbd6ebe8c75aab34e4a8257da2ae4c3b08327 GIT binary patch literal 11383 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 literal 0 HcmV?d00001 diff --git a/templates/__pycache__/ban_words.cpython-311.pyc b/templates/__pycache__/ban_words.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..812e5dcf1d5b41cfcc7050414782ed99a665b4bd GIT binary patch literal 2502 zcmcgtUuYaf7@ytSz1;2nNz)`vt!cVSgPht}@kN68P+JbE4S`aG*h z4K;8k(N-ZX2+B!mVem~Sr`apn(kb>M-3?!V94v)i3+Cv(phU~`R&YfPKBD*N8sJ^J&MF&V+)|Wj3^*F|WhYc<> zR}})Us?b=b>mw@thE?WaxH21sZZyAymPZxkBA@rH5=M#X`J~vkm23r=hWd8ziidrwn9^Cl&kF_t@!j$Qc@W0Ke(c1h++1A_)KBKF2J(yGJIxHIOCM;Yz zm<#5EQt-J7Yq=3XfP2u!_WBN81r98gnA6sbZu1=CQFnS^=*I>Od-Dz`e8R|20o1j( zEQXS2e&B_vykS|K!-i)(WQ(&b6Sj_F#K4%a^$DW}KJU9;7&qMdc*{cAHR1Ctby**k z>|>5;7m}-{ZoM!yL5@Hp6Sd2Bx`cKV)O|Ea58qD@m$DTpaq0Bj>EB|5m7b%OLoZe3 zNL<-O0NV^UBeERbMgUa~NhBvPjm?c+eL0Z&?@Rqu>aQgB|C$*5B{3KzhVCbZs5HcL zgpJiKF|gPiw*Cb8Z&cN$Itk02s%zt;xT}+IKWzU-m9c-V%D7*K>9?rz+E*(5l8LXT zH&E1-R`zR^{?JI`4(@-{v)>0F{a}wz4f~ox)@!Gf&a~PO?*4w>r*i_~z+*j#X*(B8 z(hL4!dN)OEv*?q9&`6kY$zgz2g(qs`xm?~yfy^+6MF5a@`U&!Wu5##jhrD-UD1Nxo zGh8|J%H9m5#Pc1@W^an61VEL;;6F}@u}nHl83pGZV%Q_z%(Ut9#3t-a3lB61-z!+| z$W;Bxt{nnB4RX@8M_hIp6LwJZOOan(VRuW;^CKnc`uV)+x;n276dg}*wA9v|6Z~u- zYz0Z00l(eYA@V5pTJ0i!oxA{5cDlGf15_h|AXLyvy4O=duhRCXf{xR@o@!JO`l{%0 zxSH5xt=qd)Ozf3P-PM@5Pbwv=G3jZEqJe5UB^;p#UxY0Xj&y=L09=2jis1c3;8Xnz D&{XrM literal 0 HcmV?d00001 diff --git a/templates/__pycache__/commands.cpython-311.pyc b/templates/__pycache__/commands.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94dabb3592508180c3a9faea7afa2b1aad84159f GIT binary patch literal 2523 zcmbVOZ%7YKg)dfGp8dvD)+ z^X50dH}huheyp$eBWMG^UA_2&h|nL*C^o*JY!864fH1Yk4G+|V5 zbeHPZJfj}ag;6hdEBr+sd#(thBKE$ILgEt!7UJ#Za|tz`P==JLs4f#cm^4j2vD15A zCKJiY5NEgSjE<{VAQaQ*a_m7?qP4qdZmUy=AY4ze@3VkbgNpe~op8OR$x{P+9}d*?OYbd>^j#4i@ttsDRv zcC83YFvoIfo;ifwpSeCn3$D+F8P~K6dybSwU~gGB0$7~pu&ldQ))#b*K7f*4D@W1=nx+q-u%W2w zx;1NEmrhG`5uA4&=e2?p@YkW>*XV{sSK)P6Vv|GX<(~fzW?i8hjD2mWzWib8V>7H; zU&3VftXYZPgEZ3)jWp1lvr^Vq5-_@N-H?Dt8rY`cJVF{wlZY~@PDM25j5`Evb}jq)hfE`p44s2AO#a^m){YTK(Rh9QqBPuTqz@)C9s2|Hu9IHs8S z-FC?s6L$019vP1(4b%3?Mxjtbg6)kao$KFj+_6`c4u?9a0}z9qe0DCMzkQ}J{DGkm zBdlqvu7yXYVwYs1jSziYiJ3Sowfv`JMq6(bzeHBC-nqi1r; z_8F#3%t(A9N;(0XRfw?&`TPZ+6nb_5HJ-2dm&jSi47B3}%|Tvu&ZDPG{Qs{zuH#(Iz)66o{N7PB2Ep zv%~YRT0%=kXrV$&HgNE9p#4#x-3oMM0v%N7*oKWl!y%ttFUR#UB5OV7)TClKrAAnl z*ui2)(u}J{&qU$hiBqMvn0Ml6zFz(LDqz=@QwBdHA^`^J0P$Sx*L0jzl>zb+n5@=~ u1c;oA>muj7CC2sVcQ!bp)xRv>!d^&e-d* z7f7Y1{pi>}b06ov&OLAcs-r_7@Eq$sy7wP(LjHu6`r~&BH(7>|Q$!*XlOsudvpFWu zCfPif@6dh*_+m)3cx`;tCd_T>EeKr%qf-kgvRCW8!N$qpjQuk3l8I^{lh#Zrmau2lnB<8S> zkiA|hcGQ#Xb?|xsug@v>Lb+e+lULC?FZDw`F0GOWpgy>lm*P+#k_O~Kc>rfZEN4z$ z8ic-KiP=pO!+*ss?n1Ratw3w+$*Ej+O5Q0Snk=LhX`-ZQg{juo zr_##7QZd0;zNchWP4APT*wZ>No0DV(I=IP#mT?F}U#wHP`8g<@A~H!b5=pWwk-4+b za?a@{B+0MP=Ap-6t-x-1N}{{omGD~rRB9@nms2TANTu=xsg%QVD3$t7DV?)>6doqF ze0h7vmT!_CO?`vh#05Wo{IT&pUsGj8-ILGf3i&se8UrP!8^S zssLw7YXxPZpzYD*d@+Y4+~dr-v3N)c!dw(!K^*}w)A*R3(TaR*-|>AjJDQ3!J7#t$ zk@~RGH=!ixztET9^QLi3zo@^fFX|UX{epf;zihl>yhNLp#>Fj@kA5#ZmC2PPd3wu( zlaC65e$seZe^b9`%;<~85ghauApg#I0V)@8mT{q!rt`lBEmr_-LBC)er>MpYZA%(2 zh!km2zXYfk^(*@2`gZhpaPB1$#=Hd^T+)9F72~MB1amFHDpim+L2q9L&Vblg;JpY( z5&?Yy%1baG9X+GJqc7lo#svY+r~kBpzJTa35D$039x!9Ps=sB-iW|iJnpPZt@Im?c zG+c?iF;mD>g0#N>1ioxj8xh2N z#j<$sdeL|Z7l8|WnbHX^1t^VZfIl*R^20y1a!;IwkKN2UCicK8x&wbUkD9Bwok6 zz;(ZK-5;KD0j>^uj2D{*>OZG+zks*s@Ewo^s&J1#wDE1Obq4Dfh0;2kVS$#jz<
%=?G|zHb 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 InlineKeyboardMarkup: + """ + -⬅️ Назад + :return: объект клавиатуры для параметра reply_markup + """ + builder = InlineKeyboardBuilder() + builder.add( + InlineKeyboardButton( + text='⬅️ Назад', + callback_data='come_back_ban_words' + ) + ) + builder.adjust(1) + return builder.as_markup() + + +def actions_ikb() -> InlineKeyboardMarkup: + """ + -➕ Добавить + -➖ Удалить + -⬅️ Назад + :return: объект клавиатуры для параметра reply_markup + """ + builder = InlineKeyboardBuilder() + builder.add( + InlineKeyboardButton( + text='➕ Добавить', + callback_data='ban_words_action_add' + ), + InlineKeyboardButton( + text='➖ Удалить', + callback_data='ban_words_action_remove' + ), + InlineKeyboardButton( + text='⬅️ Назад', + callback_data='come_back_menu' + ) + ) + builder.adjust(1) + return builder.as_markup() \ No newline at end of file diff --git a/templates/commands.py b/templates/commands.py new file mode 100644 index 0000000..f516707 --- /dev/null +++ b/templates/commands.py @@ -0,0 +1,62 @@ +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.utils.keyboard import InlineKeyboardBuilder + +from utils.db import Postgres + +start_text = """ +Привет, Админ! +""" + + +async def start_ikb() -> InlineKeyboardMarkup: + """ + -🚫 Стоп слова + -💬 Стоп сообщение + -Запретить / Разрешить видео без опис. + -Запретить / Разрешить фото без опис. + :return: объект клавиатуры для параметра reply_markup + """ + builder = InlineKeyboardBuilder() + + ban_media_photo = {'text': '', 'callback_data': ''} + ban_media_video = {'text': '', 'callback_data': ''} + + ban_media = await Postgres().get_data( + table_name='ban_media' + ) + + if not ban_media[0]['photo']: + ban_media_photo['text'] = 'Запретить фото без описания' + ban_media_photo['callback_data'] = 'enable_ban_media_photo' + else: + ban_media_photo['text'] = 'Разрешить фото без описания' + ban_media_photo['callback_data'] = 'disable_ban_media_photo' + + if not ban_media[0]['video']: + ban_media_video['text'] = 'Запретить видео без описания' + ban_media_video['callback_data'] = 'enable_ban_media_video' + else: + ban_media_video['text'] = 'Разрешить видео без описания' + ban_media_video['callback_data'] = 'disable_ban_media_video' + + + builder.add( + InlineKeyboardButton( + text='🚫 Стоп слова', + callback_data='ban_words' + ), + InlineKeyboardButton( + text='💬 Стоп сообщение', + callback_data='message' + ), + InlineKeyboardButton( + text=ban_media_video['text'], + callback_data=ban_media_video['callback_data'] + ), + InlineKeyboardButton( + text=ban_media_photo['text'], + callback_data=ban_media_photo['callback_data'] + ) + ) + builder.adjust(1) + return builder.as_markup() \ No newline at end of file diff --git a/templates/message.py b/templates/message.py new file mode 100644 index 0000000..ab61c5d --- /dev/null +++ b/templates/message.py @@ -0,0 +1,226 @@ +import json + +from aiogram.fsm.state import StatesGroup, State +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.types import Message +from aiogram.utils.keyboard import InlineKeyboardBuilder + +from core import bot + + +class SendState(StatesGroup): + message = State() + buttons = State() + + +actions_text = """ +Сообщение включено: {include} + +Выберите действие: +""" + +send_message_text = """ +Отправьте сообщение с прикреплением до одного фото: +""" + +send_buttons_text = """ +Отправьте кнопки в таком формате: + +Кнопка в первом ряду - http://example.com +Кнопка во втором ряду - http://example.com + +Используйте разделитель " | ", чтобы добавить до 8 кнопок в один ряд (допустимо 6 рядов): +Кнопка в ряду - http://example.com | Другая кнопка в ряду - http://example.com +""" + +incorrect_data_text = """ +Не верный формат данных +""" + +check_data_text = """ +Проверьте введённые данные +""" + +publish_message_text = """ +Обуликовано успешно +""" + + +def check_data_ikb() -> InlineKeyboardMarkup: + """ + -✅ Опубликовать + -⬅️ Назад + :return: объект клавиатуры для параметра reply_markup + """ + builder = InlineKeyboardBuilder() + builder.add( + InlineKeyboardButton( + text='✅ Опубликовать', + callback_data='publish_message' + ), + InlineKeyboardButton( + text='⬅️ Назад', + callback_data='come_back_buttons' + ) + ) + builder.adjust(1) + return builder.as_markup() + + +async def send_preview(message_data: dict, chat_id: int, username='') -> Message | None: + """ + Присылает превью сообщения + :param message_data: Данные сообщения из бд + :param chat_id: ID телеграм чата куда надо прислать превью + :param username: Username пользователя + :return: + """ + msg_text = message_data['text'] + if username: + msg_text = f'{username}\n\n' + message_data['text'] + + if message_data['media']: + preview_msg = await bot.send_photo( + chat_id=chat_id, + caption=msg_text, + photo=message_data['media'], + reply_markup=url_ikb( + row_buttons=message_data['buttons'] + ) + ) + else: + preview_msg = await bot.send_message( + chat_id=chat_id, + text=msg_text, + reply_markup=url_ikb( + row_buttons=message_data['buttons'] + ) + ) + + return preview_msg + + +def send_buttons_ikb() -> InlineKeyboardMarkup: + """ + -➡️ Пропустить + -⬅️ Назад + :return: объект клавиатуры для параметра reply_markup + """ + builder = InlineKeyboardBuilder() + builder.add( + InlineKeyboardButton( + text='➡️ Пропустить', + callback_data='pass_buttons' + ), + InlineKeyboardButton( + text='⬅️ Назад', + callback_data='come_back_message' + ) + ) + builder.adjust(1) + return builder.as_markup() + + +def send_message_ikb() -> InlineKeyboardMarkup: + """ + -⬅️ Назад + :return: объект клавиатуры для параметра reply_markup + """ + builder = InlineKeyboardBuilder() + builder.add( + InlineKeyboardButton( + text='⬅️ Назад', + callback_data='come_back_preview' + ) + ) + builder.adjust(1) + return builder.as_markup() + + +def actions_ikb(included: bool) -> InlineKeyboardMarkup: + """ + -Вкл✅ \ Выкл ❌ + -📝 Редактировать + -⬅️ Назад + :param included: bool включено ли сообщение или нет + :return: объект клавиатуры для параметра reply_markup + """ + builder = InlineKeyboardBuilder() + + if included: + included_btn = InlineKeyboardButton( + text='Выкл ❌', + callback_data='included_message_false' + ) + else: + included_btn = InlineKeyboardButton( + text='Вкл ✅', + callback_data='included_message_true' + ) + + builder.add( + included_btn, + InlineKeyboardButton( + text='📝 Редактировать', + callback_data='edit_message' + ), + InlineKeyboardButton( + text='⬅️ Назад', + callback_data='come_back_menu' + ) + ) + builder.adjust(1) + return builder.as_markup() + + +def build_url_ikb(msg_text: str) -> list: + """ + Создаёт клавиатуру с url кнопками пользователя + :param msg_text: Текст сообщения с заданными параметры клавиатуру + :return: list с кнопками клавиатуры + """ + try: + paragraphs = msg_text.split('\n') + row_buttons = [] + for paragraph in paragraphs: + row = [] + for button_text_data in paragraph.split(' | '): + row.append(button_text_data.strip().split(' - ')) + row_buttons.append(row) + + return row_buttons + except: + return [] + + +def url_ikb(row_buttons: list | str) -> InlineKeyboardMarkup | None: + """ + Создаёт клавиатуру с url кнопками пользователя + :param row_buttons: List с кнопками клавиатуры + :return: В случае ошибки False, если норм то объект клавиатуры для параметра reply_markup + """ + try: + if isinstance(row_buttons, str): + row_buttons = json.loads(row_buttons) + + builder = InlineKeyboardBuilder() + rows_len = [0, 0, 0, 0, 0, 0] + i = 0 + + for row in row_buttons: + rows_len[i] = len(row) + for button in row: + builder.add( + InlineKeyboardButton( + text=button[0], + url=button[1] + ) + ) + i += 1 + + builder.adjust(*rows_len) + if row_buttons: + return builder.as_markup() + return None + except: + return None diff --git a/templates/moderation.py b/templates/moderation.py new file mode 100644 index 0000000..6b8123a --- /dev/null +++ b/templates/moderation.py @@ -0,0 +1,458 @@ +import datetime +import logging + +from aiogram.fsm.state import StatesGroup, State +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, FSInputFile +from aiogram.utils.keyboard import InlineKeyboardBuilder + +from config import FILES_PATH, ROLE_MODERATOR_ID, ROLE_CURATOR_ID +from core import bot +from utils.bitrix import notify +from utils.db import get_data, query + + +class ModerationState(StatesGroup): + confirm = State() + confirm_confirm = State() + revision = State() + grade = State() + reject = State() + check_revision_comment = State() + check_reject_comment = State() + date = State() + + +new_idea_text = """ +{name}, пожалуйста, ознакомьтесь с новой НеоИдеей. +""" + +new_idea_data_text = """ +Автор: {author_name} +Дата: {creation_date} +Подразделение: {department} +Категория: {category} +Город: {city} +Название: «{idea_name}» +Содержание: {idea_content} +""" + +choose_action_text = """ +Пожалуйста, выберите одно из следующих действий: +""" + +confirm_action_text = """ +Вы согласовываете НеоИдею: +«{idea_name}». + +Автор: {author_name} +""" + +choose_responsible_text = """ +Пожалуйста, выберите Ответственного за категорию для передачи НеоИдеи в работу. +""" + +send_date_text = """ +В календаре укажите крайние сроки выполнения: +""" + +confirm_responsible_date_text = """ +Вы передаёте НеоИдею в работу. +Название: «{idea_name}». + +Автор: {author_name} +Ответственный за категорию: {responsible_name} +Крайние сроки выполнения: {date} +""" + +confirm_action_send_text = """ +Спасибо! Вы передали НеоИдею в работу. +""" + +revision_action_text = """ +Вы возвращаете на доработку НеоИдею: +«{idea_name}». +Автор: {author_name} + +Оставьте комментарий для того, чтобы отправить НеоИдею на доработку. +""" + +send_revision_comment_text = """ +Оставьте комментарий для того, чтобы отправить НеоИдею на доработку +""" + +send_comment_text = """ +Пожалуйста, оставьте свой комментарий: +""" + +check_comment = """ +Проверьте правильность введенных данных: + +{comment} +""" + +revision_action_send_text = """ +Вы отправили НеоИдею на доработку. +""" + +grade_action_text = """ +Вы направляете НеоИдею на оценку: +«{idea_name}». +Автор: {author_name} + +НеоИдея будет передана Куратору. +""" + +grade_action_send_text = """ +Спасибо! НеоИдея передана Куратору. +""" + +reject_action_text = """ +Вы отклоняете НеоИдею: +«{idea_name}». +Автор: {author_name} + +Пожалуйста, оставьте свой комментарий. +""" + +reject_action_send_text = """ +Спасибо! НеоИдея отклонена. +""" + +you_rejected = """ +Здравствуйте, {name}. + +Ваша НеоИдея "{idea_name}" была отклонена с комментарием: +"{comment}" +""" + +you_revision = """ +Ваша НеоИдея возвращена на доработку, пожалуйста проработайте комментарии от генерального директора и отправьте заявку заново. + +НеоИдея: "{idea_name}" +Комментарий: "{comment}" +""" + +accept_notify_author_text = """ +Поздравляем! Ваша НеоИдея передана к внедрению +""" + +accept_notify_curator_text = """ +Быстрая НеоИдея автора {author_name} передана к внедрению {responsible_name} +""" + +grade_curator_text = """ +Вам на оценку передана НеоИдея "{idea_name}". +""" + +confirm_notify_curator_text = """ +Быстрая НеоИдея автора {author_name} передана к ответственному за категорию {responsible_name} +""" + +confirm_notify_author_text = """ +Ваша НеоИдея передана ответственному за категорию +""" + + +def confirm_action_ikb() -> InlineKeyboardMarkup: + """ + -Да, продолжить ✅ + -Нет, вернуться назад ❌ + :return: объект клавиатуры для параметра reply_markup + """ + builder = InlineKeyboardBuilder() + builder.add( + InlineKeyboardButton( + text='Да, продолжить ✅', + callback_data='confirm_action_confirm' + ), + InlineKeyboardButton( + text='Нет, вернуться назад ❌', + callback_data='confirm_action_reject' + ) + ) + builder.adjust(1) + return builder.as_markup() + + +def choose_action_ikb(idea_id: str | int, msg_id: str | int) -> InlineKeyboardMarkup: + """ + -Согласовать и передать в работу + -Вернуть Автору на доработку + -Направить на оценку + -Отклонить + :param idea_id: ID идеи + :param msg_id: ID сообщения с клавиатурой + :return: объект клавиатуры для параметра reply_markup + """ + builder = InlineKeyboardBuilder() + builder.add( + InlineKeyboardButton( + text='Согласовать и передать в работу', + callback_data=f'moderation_confirm_{idea_id}_{msg_id}' + ), + InlineKeyboardButton( + text='Вернуть Автору на доработку', + callback_data=f'moderation_revision_{idea_id}_{msg_id}' + ), + InlineKeyboardButton( + text='Направить на оценку', + callback_data=f'moderation_grade_{idea_id}_{msg_id}' + ), + InlineKeyboardButton( + text='Отклонить', + callback_data=f'moderation_reject_{idea_id}_{msg_id}' + ) + ) + builder.adjust(1) + return builder.as_markup() + + +async def choose_responsible_ikb(category_id: int) -> InlineKeyboardMarkup: + """ + -Ответственный 1 + -Ответственный 2 + :param category_id: ID категории идеи + :return: объект клавиатуры для параметра reply_markup + """ + builder = InlineKeyboardBuilder() + responsible_list = await get_data( + table_name='user_to_categories', + query_filter={'category_id': category_id} + ) + for responsible in responsible_list: + responsible_data = await get_data( + table_name='users', + query_filter={'id': responsible['user_id']} + ) + builder.add( + InlineKeyboardButton( + text=responsible_data[0]['full_name'], + callback_data=f'choose_responsible_{responsible_data[0]["id"]}', + ) + ) + builder.adjust(1) + return builder.as_markup() + + +async def send_new_idea_on_moderation( + idea_id: int, user_name: str, department: str, idea_title: str, + idea_content: str, relation_id: int, city_id: int, category_id: int, creation_date: datetime.datetime): + """ + Присылает новую идею модераторам из списка + :param idea_id: Данные идеи + :param user_name: Имя пользователя + :param department: Отдел пользователя + :param idea_title: Название идеи + :param idea_content: Описание идеи + :param relation_id: id отношения к идее + :param city_id: id города, где работает пользователь + :param category_id: id категории + :param creation_date: Дата создания идеи + :return: + """ + moderators = await get_data( + table_name='user_to_roles', + query_filter={'role_id': ROLE_MODERATOR_ID} + ) + city = await get_data( + table_name='cities', + query_filter={'id': city_id} + ) + files = await get_data( + table_name='idea_to_files', + query_filter={'idea_id': idea_id} + ) + category = await get_data( + table_name='categories', + query_filter={'id': category_id} + ) + notify_text = new_idea_data_text.format( + author_name=user_name, + creation_date=creation_date, + department=department, + idea_name=idea_title, + idea_content=idea_content, + city=city[0]['name'], + category=category[0]['name'] + ) + + for moderator_id in moderators: + try: + moderator = await get_data( + table_name='users', + query_filter={'id': moderator_id['user_id']} + ) + formatted_new_idea_text = new_idea_text.format( + name=moderator[0]['full_name'], + ) + await bot.send_message( + chat_id=moderator[0]['telegram_id'], + text=formatted_new_idea_text + ) + await bot.send_message( + chat_id=moderator[0]['telegram_id'], + text=notify_text + ) + for file in files: + file_data = await get_data( + table_name='files', + query_filter={'id': file['file_id']} + ) + await bot.send_document( + chat_id=moderator[0]['telegram_id'], + document=FSInputFile(FILES_PATH + file_data[0]['file']) + ) + last_msg = await bot.send_message( + chat_id=moderator[0]['telegram_id'], + text=choose_action_text, + reply_markup=choose_action_ikb( + idea_id=idea_id, + msg_id=0 + ) + ) + await bot.edit_message_reply_markup( + message_id=last_msg.message_id, + chat_id=moderator[0]['telegram_id'], + reply_markup=choose_action_ikb( + idea_id=idea_id, + msg_id=last_msg.message_id + ) + ) + moderator_user_data = await get_data( + table_name='users', + query_filter={'id': moderator_id['user_id']} + ) + await notify( + user_id=moderator_user_data[0]['external_id'], + message='Новая быстрая НеоИдея!' + ) + + except Exception as e: + logging.error(e) + + curators = await get_data( + table_name='user_to_roles', + query_filter={'role_id': ROLE_CURATOR_ID} + ) + for curator in curators: + try: + user_data = await get_data( + table_name='users', + query_filter={'id': curator['user_id']} + ) + await notify( + user_id=user_data[0]['external_id'], + message='Новая быстрая НеоИдея!' + ) + except Exception as e: + logging.error(f'не могу отправить уведомление куратору о новой идее {e}') + + +async def accept_notify(author_name: str, responsible_id: str, author_id: int) -> None: + """ + Уведомляет куратора и автора о принятии идеи + :param responsible_id: ID кому передают идею + :param author_name: Имя автора идеи + :param author_id: ID автора + :return: + """ + responsible_data = await get_data( + table_name='users', + query_filter={'id': responsible_id} + ) + notify_text = notify_curator_text.format( + author_name=author_name, + responsible_name=responsible_data[0]['full_name'] + ) + curators = await get_data( + table_name='user_to_roles', + query_filter={'role_id': ROLE_CURATOR_ID} + ) + + for curator in curators: + try: + user_data = await get_data( + table_name='users', + query_filter={'id': curator['user_id']} + ) + await notify( + user_id=user_data[0]['external_id'], + message=notify_text + ) + await bot.send_message( + chat_id=user_data[0]['telegram_id'], + text=notify_text + ) + except Exception as e: + logging.error(f'не могу отправить уведомление куратору о принятии идеи {e}') + + try: + author_data = await get_data( + table_name='users', + query_filter={'id': author_id} + ) + await notify( + user_id=author_data[0]['external_id'], + message=notify_author_text + ) + await bot.send_message( + chat_id=author_data[0]['telegram_id'], + text=notify_author_text + ) + except Exception as e: + logging.error(f'Не смог отправить уведомление автору идеи {e}') + + +async def confirm_notify(author_name: str, responsible_id: str, author_id: int) -> None: + """ + Уведомляет куратора и автора о согласовании идеи + :param responsible_id: ID кому передают идею + :param author_name: Имя автора идеи + :param author_id: ID автора + :return: + """ + responsible_data = await get_data( + table_name='users', + query_filter={'id': responsible_id} + ) + notify_text = confirm_notify_curator_text.format( + author_name=author_name, + responsible_name=responsible_data[0]['full_name'] + ) + curators = await get_data( + table_name='user_to_roles', + query_filter={'role_id': ROLE_CURATOR_ID} + ) + + for curator in curators: + try: + user_data = await get_data( + table_name='users', + query_filter={'id': curator['user_id']} + ) + await notify( + user_id=user_data[0]['external_id'], + message=notify_text + ) + await bot.send_message( + chat_id=user_data[0]['telegram_id'], + text=notify_text + ) + except Exception as e: + logging.error(f'не могу отправить уведомление куратору о принятии идеи {e}') + + try: + author_data = await get_data( + table_name='users', + query_filter={'id': author_id} + ) + await notify( + user_id=author_data[0]['external_id'], + message=notify_author_text + ) + await bot.send_message( + chat_id=author_data[0]['telegram_id'], + text=notify_author_text + ) + except Exception as e: + logging.error(f'Не смог отправить уведомление автору идеи {e}') \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/__pycache__/__init__.cpython-310.pyc b/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f91d11f6f03f105d5b9fe76d54e3899c5f5caff3 GIT binary patch literal 119 zcmd1j<>g`kf(XyxG!Xq5L?8o3AjbiSi&=m~3PUi1CZpd-5!pP83g5+AQuP(^b literal 0 HcmV?d00001 diff --git a/utils/__pycache__/__init__.cpython-311.pyc b/utils/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1988dde6e1afdb21dc4b04b4b2adef54f6bbf933 GIT binary patch literal 170 zcmZ3^%ge<81Ww_JX(0MBh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09t%huT{CbT%U zs5mA!Hzz+gCZIAoBe5tqpeR2pHMyiXCOp3=J0>~5Dm5v;B&M_^Gp9HvK0Y%qvm`!V oub}c5hfQvNN@-52T@fqLG?0D8{6OLZGb1D82L>2X#0(Sz0K&~F#{d8T literal 0 HcmV?d00001 diff --git a/utils/__pycache__/db.cpython-311.pyc b/utils/__pycache__/db.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a038000575fae28d4ac1cbff5e53e105f2e87a01 GIT binary patch literal 19954 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 S8-(4{-BkqBs)W7^X8Z?Q@L=fx literal 0 HcmV?d00001 diff --git a/utils/__pycache__/middleware.cpython-311.pyc b/utils/__pycache__/middleware.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80bed69383825124ea41e31577c7eed1e61fe418 GIT binary patch literal 177 zcmZ3^%ge<81a-WvX_=f1439w^7+{1lKC=NC(-~42f*CX!{Z=v*F#-9XL6W~*oULL) zi&Kk=V{&tI@^fPXDw8u3i*f^s^0QKtONwK{^NX@$eDhOMixNxni=6UHVoFOgbAYNd wQ&MtL%M*)I^$IF~aoFVMr None: + """ + Создаёт таблицы если их нет + :return: + """ + await self.connect() + try: + async with self.conn.transaction(): + message_exist = await self.conn.fetchval( + exist_query, 'message' + ) + ban_media_exist = await self.conn.fetchval( + exist_query, 'ban_media' + ) + await self.conn.execute( + create_tables_query + ) + + if not message_exist: + await self.create_row( + table_name='message', + data_to_insert={ + 'text': 'Сообщение', + 'media': '', + 'buttons': '', + 'included': False + } + ) + + if not ban_media_exist: + await self.create_row( + table_name='ban_media', + data_to_insert={ + 'photo': True, + 'video': True, + } + ) + + except Exception as e: + logging.error(f'Ошибка в create_tables {e}') + finally: + await self.conn.close() + + async def get_data(self, table_name: str, columns='*', query_filter=None) -> list: + """ + Получить данные нужной таблицы по указанным фильтрам + :param columns: Название колонн с нужными данными + :param query_filter: Фильтры запроса в формате {колонка: её значение} + :param table_name: Название таблицы для запроса + :return: False в случае ошибки, словарь с данными в случае успеха + """ + if query_filter is None: + query_filter = {} + await self.connect() + try: + if isinstance(columns, str): + columns = [columns] + full_query = f"SELECT {','.join(columns)} FROM {table_name}" + + if query_filter: + query_filter = ' AND '.join( + [f"{key} = '{value}'" for key, value in query_filter.items()] + ) + full_query += f' WHERE {query_filter}' + + async with self.conn.transaction(): + result = await self.conn.fetch(full_query) + return result + + except Exception as e: + logging.error(f'Ошибка в get_data {e}') + finally: + await self.conn.close() + return [] + + async def update_data(self, new_data: dict, query_filter: dict, table_name: str) -> bool: + """ + Обновляет данные по заданным фильтрам + :param new_data: Новые данные в формате {Колонка: новое значение} + :param query_filter: Фильтры запроса в формате {колонка: её значение} + :param table_name: Название таблицы для запроса + :return: True в случае успеха, False в случае ошибки + """ + await self.connect() + try: + dollar_data = {key: f"${i + 1}" for i, key in enumerate(new_data)} + values = ', '.join(f'{key} = {value}' for key, value in dollar_data.items()) + full_query = f"UPDATE {table_name} SET {values}" + + if query_filter: + query_filter = ' AND '.join([f"{key} = '{value}'" for key, value in query_filter.items()]) + full_query += f' WHERE {query_filter}' + + async with self.conn.transaction(): + await self.conn.execute(full_query, *new_data.values()) + return True + + except Exception as e: + logging.error(f'Ошибка в update_data {e}') + return False + finally: + await self.conn.close() + + async def create_row(self, data_to_insert: dict, table_name: str) -> bool: + """ + Создаёт новую строку с данными + :param data_to_insert: Список, где ключ - название столбика, значение - значение столбика в новой строчке + :param table_name: Название таблицы, куда вставляем данные + :return: id последней вставленной строки + """ + await self.connect() + try: + dollars = [f"${i + 1}" for i in range(len(data_to_insert))] + full_query = f"INSERT INTO {table_name} ({', '.join(data_to_insert.keys())}) VALUES ({', '.join(dollars)})" + async with self.conn.transaction(): + await self.conn.execute(full_query, *data_to_insert.values()) + return True + + except Exception as e: + logging.error(f'Ошибка в create_row {e}') + return False + finally: + await self.conn.close() + + async def query(self, query_text: str): + """ + Прямой запрос к бд + :param query_text: sql запрос + :return: Результат sql запроса + """ + await self.connect() + try: + async with self.conn.transaction(): + await self.conn.execute(query_text) + + except Exception as e: + logging.error(f'Ошибка в query {e}') + finally: + await self.conn.close() + + +class Redis: + def __init__(self): + self.conn = None + + async def connect(self): + try: + self.conn = await redis.Redis( + host=REDIS_HOST, + port=REDIS_PORT, + db=REDIS_NAME, + password=REDIS_PASSWORD, + decode_responses=True, + encoding='utf-8' + ) + except Exception as e: + logging.error('redis connect', e) + + async def delete_key(self, *keys: str | int) -> str | int: + await self.connect() + try: + return await self.conn.delete(*keys) + except Exception as e: + logging.error('redis delete_key', e) + finally: + await self.conn.close() + + async def update_list(self, key: str | int, *values) -> str | int: + await self.connect() + try: + return await self.conn.rpush(key, *values) + except Exception as e: + logging.error('redis update_data', e) + finally: + await self.conn.close() + + async def get_list(self, key: str | int) -> list: + await self.connect() + try: + data = await self.conn.lrange(name=str(key), start=0, end=-1) + return data + except Exception as e: + logging.error('redis get_data', e) + return [] + finally: + await self.conn.close() + + async def update_dict(self, key: str | int, value: dict) -> str | int: + await self.connect() + try: + return await self.conn.hset(name=str(key), mapping=value) + except Exception as e: + logging.error('redis update', e) + finally: + await self.conn.close() + + async def get_dict(self, key: str | int) -> dict: + await self.connect() + try: + data = await self.conn.hgetall(name=str(key)) + return data + except Exception as e: + logging.error('redis get', e) + return [] + finally: + await self.conn.close() diff --git a/utils/defs.py b/utils/defs.py new file mode 100644 index 0000000..12c9254 --- /dev/null +++ b/utils/defs.py @@ -0,0 +1,44 @@ +import logging + +from aiogram.types import Message +import pandas + +from utils.db import Postgres + + +async def delete_msg(msg: Message) -> None: + """ + Безопасно удаляет сообщение + :param msg: Message + :return: True, если текст является ссылкой, иначе False. + """ + try: + await msg.delete() + except: + pass + + +async def create_xlsx() -> str: + """ + Составляет xlsx файл с данными бан слова + :return: Путь к готовому xlsx файлу + """ + try: + p = Postgres() + ban_words = await p.get_data( + table_name='ban_words' + ) + table_dict = {'ID': [], 'Слово': []} + + for ban_word in ban_words: + table_dict['ID'].append(ban_word['id']) + table_dict['Слово'].append(ban_word['word']) + + df = pandas.DataFrame(table_dict) + file_path = 'data/ban_words.xlsx' + df.to_excel(file_path, index=False) + + return file_path + + except Exception as e: + logging.error('Ошибка в create_xlsx', e) \ No newline at end of file diff --git a/utils/middleware.py b/utils/middleware.py new file mode 100644 index 0000000..44c8b83 --- /dev/null +++ b/utils/middleware.py @@ -0,0 +1,73 @@ +# from aiogram import BaseMiddleware +# from aiogram.types import TelegramObject, Update +# +# from typing import Callable, Dict, Any, Awaitable +# from utils.defs import delete_msg +# from utils.db import Postgres, Redis +# from templates.message import send_preview +# +# +# class DeleteMessage(BaseMiddleware): +# """ +# Мидлвари удаляющая сообщения с бан вордами +# """ +# +# async def __call__( +# self, +# handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], +# event: Update, +# data: Dict[str, Any] +# ) -> Any: +# if not event.message: +# return await handler(event, data) +# if not (event.message.chat.type == 'group' or +# event.message.chat.type == 'supergroup'): +# return await handler(event, data) +# +# ban = False +# r = Redis() +# p = Postgres() +# +# ban_words = await r.get_list( +# key='ban_words' +# ) +# if not ban_words: +# ban_words = await p.get_data( +# table_name='ban_words' +# ) +# ban_words = [word['word'] for word in ban_words] +# await r.delete_key( +# 'ban_words' +# ) +# await r.update_list( +# 'ban_words', +# *ban_words +# ) +# +# for ban_word in ban_words: +# print(ban_word) +# if ban_word in event.message.text.lower(): +# print(event.message.text.lower(), 'нашёл') +# ban = True +# +# if ban: +# await delete_msg( +# msg=event.message +# ) +# message_data = await p.get_data( +# table_name='message' +# ) +# +# if event.message.from_user.username: +# username = f'@{event.message.from_user.username}' +# else: +# username = event.message.from_user.full_name +# +# if message_data[0]['included']: +# await send_preview( +# chat_id=event.message.chat.id, +# message_data=message_data[0], +# username=username +# ) +# +# return await handler(event, data)