From c73e91b9dd74592f47f67e523c595c844912e6b2 Mon Sep 17 00:00:00 2001 From: Lamroy95 Date: Mon, 23 Oct 2023 10:41:11 +0400 Subject: [PATCH 1/4] Move models --- app/filters/karma_change.py | 2 +- app/filters/tg_permissions.py | 2 +- app/handlers/base.py | 2 +- app/handlers/change_karma.py | 2 +- app/handlers/karma.py | 2 +- app/handlers/keyboards.py | 8 +++++-- app/handlers/moderator.py | 2 +- app/handlers/settings.py | 2 +- app/handlers/superuser.py | 2 +- app/infrastructure/__init__.py | 0 app/infrastructure/database/__init__.py | 0 .../database/models/__init__.py | 9 ++++++++ .../database/models}/chat.py | 2 +- .../database/models}/chat_settings.py | 2 +- .../database/models}/karma_actions.py | 14 +++++++----- .../database/models}/moderator_actions.py | 22 ++++++++++--------- .../database/models}/report.py | 13 ++++++----- .../database/models}/user.py | 4 ++-- .../database/models}/user_karma.py | 2 +- app/middlewares/db_middleware.py | 2 +- app/models/config/auto_restriction.py | 2 +- app/models/db/__init__.py | 9 -------- app/models/db/db.py | 19 +++++++++++++++- app/services/adaptive_trottle.py | 2 +- app/services/change_karma.py | 2 +- app/services/find_target_user.py | 2 +- app/services/karma.py | 2 +- app/services/moderation.py | 2 +- app/services/report.py | 3 +-- app/services/settings.py | 2 +- app/services/user_info.py | 2 +- app/utils/types.py | 2 +- 32 files changed, 84 insertions(+), 59 deletions(-) create mode 100644 app/infrastructure/__init__.py create mode 100644 app/infrastructure/database/__init__.py create mode 100644 app/infrastructure/database/models/__init__.py rename app/{models/db => infrastructure/database/models}/chat.py (98%) rename app/{models/db => infrastructure/database/models}/chat_settings.py (94%) rename app/{models/db => infrastructure/database/models}/karma_actions.py (82%) rename app/{models/db => infrastructure/database/models}/moderator_actions.py (81%) rename app/{models/db => infrastructure/database/models}/report.py (71%) rename app/{models/db => infrastructure/database/models}/user.py (98%) rename app/{models/db => infrastructure/database/models}/user_karma.py (98%) diff --git a/app/filters/karma_change.py b/app/filters/karma_change.py index 5823fa24..4c0b6af0 100644 --- a/app/filters/karma_change.py +++ b/app/filters/karma_change.py @@ -12,7 +12,7 @@ MINUS_EMOJI, MINUS_TRIGGERS, ) -from app.models.db import ChatSettings +from app.infrastructure.database.models import ChatSettings PUNCTUATIONS = ",.!)" INF = float('inf') diff --git a/app/filters/tg_permissions.py b/app/filters/tg_permissions.py index a9c29454..c0d8ea2d 100644 --- a/app/filters/tg_permissions.py +++ b/app/filters/tg_permissions.py @@ -7,7 +7,7 @@ from aiogram.enums import ChatMemberStatus from aiogram.filters import BaseFilter -from app.models.db import Chat, User +from app.infrastructure.database.models import Chat from app.services.find_target_user import get_target_user logger = logging.getLogger(__name__) diff --git a/app/handlers/base.py b/app/handlers/base.py index b5cbb330..19f4f30b 100644 --- a/app/handlers/base.py +++ b/app/handlers/base.py @@ -3,7 +3,7 @@ from aiogram.fsm.context import FSMContext from aiogram.utils.markdown import hpre, hbold -from app.models.db import Chat +from app.infrastructure.database.models import Chat from app.utils.log import Logger diff --git a/app/handlers/change_karma.py b/app/handlers/change_karma.py index 7f026364..01fbbdd0 100644 --- a/app/handlers/change_karma.py +++ b/app/handlers/change_karma.py @@ -4,7 +4,7 @@ from aiogram.types import ContentType from aiogram.utils.text_decorations import html_decoration as hd -from app.models.db import Chat, User +from app.infrastructure.database.models import Chat, User from app.services.adaptive_trottle import AdaptiveThrottle from app.services.change_karma import change_karma, cancel_karma_change from app.services.remove_message import remove_kb diff --git a/app/handlers/karma.py b/app/handlers/karma.py index 33a300a2..9ac1d2e4 100644 --- a/app/handlers/karma.py +++ b/app/handlers/karma.py @@ -5,7 +5,7 @@ from aiogram.utils.text_decorations import html_decoration as hd from app.models.config import Config -from app.models.db import ( +from app.infrastructure.database.models import ( Chat, User ) diff --git a/app/handlers/keyboards.py b/app/handlers/keyboards.py index d5f3e6de..122d377c 100644 --- a/app/handlers/keyboards.py +++ b/app/handlers/keyboards.py @@ -1,8 +1,12 @@ from aiogram.filters.callback_data import CallbackData from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from app.models.db import User, KarmaEvent, ModeratorEvent -from app.models.db.report import Report +from app.infrastructure.database.models import ( + User, + KarmaEvent, + ModeratorEvent, + Report +) class KarmaCancelCb(CallbackData, prefix="karma_cancel"): diff --git a/app/handlers/moderator.py b/app/handlers/moderator.py index 0f68d482..989a63f3 100644 --- a/app/handlers/moderator.py +++ b/app/handlers/moderator.py @@ -17,7 +17,7 @@ ) from app.handlers import keyboards as kb from app.models.config import Config -from app.models.db import Chat, User, ChatSettings, ReportStatus +from app.infrastructure.database.models import Chat, User, ReportStatus, ChatSettings from app.services.moderation import ( ban_user, delete_moderator_event, diff --git a/app/handlers/settings.py b/app/handlers/settings.py index ac9f6b30..55720ad4 100644 --- a/app/handlers/settings.py +++ b/app/handlers/settings.py @@ -2,7 +2,7 @@ from aiogram.filters import Command from app.filters import HasPermissions -from app.models.db import Chat +from app.infrastructure.database.models import Chat from app.services.settings import enable_karmic_restriction, disable_karmic_restriction, get_settings_card, \ enable_karma_counting, disable_karma_counting diff --git a/app/handlers/superuser.py b/app/handlers/superuser.py index 51bdeb46..993cf1a8 100644 --- a/app/handlers/superuser.py +++ b/app/handlers/superuser.py @@ -1,6 +1,6 @@ from typing import Iterable -from aiogram import Bot, Router, enums +from aiogram import Bot, Router from aiogram.filters import Command from aiogram.types import Message, BufferedInputFile from functools import partial diff --git a/app/infrastructure/__init__.py b/app/infrastructure/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/infrastructure/database/__init__.py b/app/infrastructure/database/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/infrastructure/database/models/__init__.py b/app/infrastructure/database/models/__init__.py new file mode 100644 index 00000000..12fe45be --- /dev/null +++ b/app/infrastructure/database/models/__init__.py @@ -0,0 +1,9 @@ +from app.infrastructure.database.models.chat import Chat, ChatType +from app.infrastructure.database.models.chat_settings import ChatSettings +from app.infrastructure.database.models.karma_actions import KarmaEvent +from app.infrastructure.database.models.moderator_actions import ModeratorEvent +from app.infrastructure.database.models.report import Report, ReportStatus +from app.infrastructure.database.models.user import User +from app.infrastructure.database.models.user_karma import UserKarma + +__all__ = [Chat, ChatType, User, UserKarma, KarmaEvent, ModeratorEvent, ChatSettings, Report, ReportStatus] diff --git a/app/models/db/chat.py b/app/infrastructure/database/models/chat.py similarity index 98% rename from app/models/db/chat.py rename to app/infrastructure/database/models/chat.py index ce4d9f58..0f164270 100644 --- a/app/models/db/chat.py +++ b/app/infrastructure/database/models/chat.py @@ -8,7 +8,7 @@ from tortoise.transactions import in_transaction from app.utils.exceptions import NotHaveNeighbours -from .db import karma_filters +from app.models.db.db import karma_filters SQL_PREV_NEXT = """ diff --git a/app/models/db/chat_settings.py b/app/infrastructure/database/models/chat_settings.py similarity index 94% rename from app/models/db/chat_settings.py rename to app/infrastructure/database/models/chat_settings.py index ced930a2..3b200571 100644 --- a/app/models/db/chat_settings.py +++ b/app/infrastructure/database/models/chat_settings.py @@ -1,7 +1,7 @@ from tortoise import fields from tortoise.models import Model -from .chat import Chat +from app.infrastructure.database.models import Chat class ChatSettings(Model): diff --git a/app/models/db/karma_actions.py b/app/infrastructure/database/models/karma_actions.py similarity index 82% rename from app/models/db/karma_actions.py rename to app/infrastructure/database/models/karma_actions.py index 6b39b0fc..22f4b133 100644 --- a/app/models/db/karma_actions.py +++ b/app/infrastructure/database/models/karma_actions.py @@ -1,18 +1,20 @@ +import typing + from aiogram.utils.text_decorations import html_decoration as hd from tortoise import fields from tortoise.models import Model -from .chat import Chat -from .user import User +if typing.TYPE_CHECKING: + from app.infrastructure.database.models import Chat, User class KarmaEvent(Model): id_ = fields.IntField(pk=True, source_field="id") - user_from: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( + user_from: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( 'models.User', related_name='i_change_karma_events') - user_to: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( + user_to: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( 'models.User', related_name='my_karma_events') - chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( + chat: fields.ForeignKeyRelation["Chat"] = fields.ForeignKeyField( 'models.Chat', related_name='karma_events') date = fields.DatetimeField(auto_now=True, null=False) how_change = fields.FloatField( @@ -34,7 +36,7 @@ def __repr__(self): ) @classmethod - async def get_last_by_user(cls, user: User, chat: Chat, limit: int = 10): + async def get_last_by_user(cls, user: "User", chat: "Chat", limit: int = 10): return await cls.filter( user_to=user, chat=chat diff --git a/app/models/db/moderator_actions.py b/app/infrastructure/database/models/moderator_actions.py similarity index 81% rename from app/models/db/moderator_actions.py rename to app/infrastructure/database/models/moderator_actions.py index 0489e7ed..8b867108 100644 --- a/app/models/db/moderator_actions.py +++ b/app/infrastructure/database/models/moderator_actions.py @@ -1,3 +1,4 @@ +import typing from datetime import timedelta from aiogram.utils.text_decorations import html_decoration as hd @@ -5,18 +6,19 @@ from tortoise.models import Model from app.utils.timedelta_functions import format_timedelta -from .chat import Chat -from .user import User -from ..common import TypeRestriction +from app.models.common import TypeRestriction + +if typing.TYPE_CHECKING: + from app.infrastructure.database.models import Chat, User class ModeratorEvent(Model): id_ = fields.IntField(pk=True, source_field="id") - moderator: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( + moderator: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( 'models.User', related_name='my_moderator_events') - user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( + user: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( 'models.User', related_name='my_restriction_events') - chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( + chat: fields.ForeignKeyRelation["Chat"] = fields.ForeignKeyField( 'models.Chat', related_name='moderator_events') date = fields.DatetimeField(auto_now=True, null=False) type_restriction = fields.CharField(max_length=20) @@ -36,9 +38,9 @@ def __repr__(self): @classmethod async def save_new_action( cls, - moderator: User, - user: User, - chat: Chat, + moderator: "User", + user: "User", + chat: "Chat", type_restriction: str, duration: timedelta = None, comment: str = "", @@ -56,7 +58,7 @@ async def save_new_action( return moderator_event @classmethod - async def get_last_by_user(cls, user: User, chat: Chat, limit: int = 10): + async def get_last_by_user(cls, user: "User", chat: "Chat", limit: int = 10): return await cls.filter( user=user, chat=chat diff --git a/app/models/db/report.py b/app/infrastructure/database/models/report.py similarity index 71% rename from app/models/db/report.py rename to app/infrastructure/database/models/report.py index 9df96fc6..a8cfea65 100644 --- a/app/models/db/report.py +++ b/app/infrastructure/database/models/report.py @@ -1,10 +1,11 @@ +import typing from enum import Enum from tortoise import fields from tortoise.models import Model -from .chat import Chat -from .user import User +if typing.TYPE_CHECKING: + from app.infrastructure.database.models import Chat, User TG_MESSAGE_MAX_LEN = 4096 @@ -18,15 +19,15 @@ class ReportStatus(Enum): class Report(Model): id = fields.IntField(pk=True) - reporter: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( + reporter: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( 'models.User', related_name='made_reports' ) - reported_user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( + reported_user: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( 'models.User', related_name='got_reports' ) - chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( + chat: fields.ForeignKeyRelation["Chat"] = fields.ForeignKeyField( 'models.Chat', related_name='reports' ) @@ -34,7 +35,7 @@ class Report(Model): resolution_time = fields.DatetimeField(null=True) reported_message_id = fields.BigIntField(generated=False, null=False) reported_message_content = fields.CharField(null=False, max_length=TG_MESSAGE_MAX_LEN) - resolved_by: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( + resolved_by: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( 'models.User', related_name='resolved_reports', null=True diff --git a/app/models/db/user.py b/app/infrastructure/database/models/user.py similarity index 98% rename from app/models/db/user.py rename to app/infrastructure/database/models/user.py index f6fa6281..3fc1014b 100644 --- a/app/models/db/user.py +++ b/app/infrastructure/database/models/user.py @@ -8,8 +8,8 @@ from app.models.common import TypeRestriction from app.utils.exceptions import UserWithoutUserIdError -from .chat import Chat -from .. import dto +from app.infrastructure.database.models import Chat +from app.models import dto class User(Model): diff --git a/app/models/db/user_karma.py b/app/infrastructure/database/models/user_karma.py similarity index 98% rename from app/models/db/user_karma.py rename to app/infrastructure/database/models/user_karma.py index 26b7461e..e41f1e06 100644 --- a/app/models/db/user_karma.py +++ b/app/infrastructure/database/models/user_karma.py @@ -4,7 +4,7 @@ from tortoise.models import Model from app.filters.karma_change import INF -from app.models.db import User, Chat +from app.infrastructure.database.models import User, Chat from app.models.db.db import karma_filters from app.utils.exceptions import SubZeroKarma from app.utils.log import Logger diff --git a/app/middlewares/db_middleware.py b/app/middlewares/db_middleware.py index 8cfe1b10..b7e1e91c 100644 --- a/app/middlewares/db_middleware.py +++ b/app/middlewares/db_middleware.py @@ -6,7 +6,7 @@ from aiogram.dispatcher.event.bases import CancelHandler from aiogram.types import TelegramObject -from app.models.db import Chat, User +from app.infrastructure.database.models import Chat, User from app.services.settings import get_chat_settings from app.utils.lock_factory import LockFactory from app.utils.log import Logger diff --git a/app/models/config/auto_restriction.py b/app/models/config/auto_restriction.py index b73576ea..e56c68f7 100644 --- a/app/models/config/auto_restriction.py +++ b/app/models/config/auto_restriction.py @@ -4,7 +4,7 @@ from app.config.restriction_plan import RestrictionPlan, RestrictionPlanElem if typing.TYPE_CHECKING: - from app.models.db import User + from app.infrastructure.database.models import User @dataclass diff --git a/app/models/db/__init__.py b/app/models/db/__init__.py index 9d42665d..e69de29b 100644 --- a/app/models/db/__init__.py +++ b/app/models/db/__init__.py @@ -1,9 +0,0 @@ -from .chat import Chat, ChatType -from .chat_settings import ChatSettings -from .karma_actions import KarmaEvent -from .moderator_actions import ModeratorEvent -from .report import Report, ReportStatus -from .user import User -from .user_karma import UserKarma - -__all__ = [Chat, ChatType, User, UserKarma, KarmaEvent, ModeratorEvent, ChatSettings, Report, ReportStatus] diff --git a/app/models/db/db.py b/app/models/db/db.py index 3a101c0f..c0df4fed 100644 --- a/app/models/db/db.py +++ b/app/models/db/db.py @@ -5,7 +5,7 @@ logger = Logger(__name__) -__models__ = ['app.models.db'] +__models__ = ['app.infrastructure.database.models'] karma_filters = ("-karma", "uc_id") @@ -29,3 +29,20 @@ async def generate_schemas_db(db_config: DBConfig): def generate_schemas(db_config: DBConfig): run_async(generate_schemas_db(db_config)) + + + +""" +CREATE TABLE "reports" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + "created_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "resolution_time" TIMESTAMP, + "reported_message_id" BIGINT NOT NULL, + "reported_message_content" VARCHAR(4096) NOT NULL, + "status" VARCHAR(9) NOT NULL /* approved: Approved\ndeclined: Declined\npending: Pending\ncancelled: Cancelled */, + "chat_id" BIGINT NOT NULL REFERENCES "chats" ("chat_id") ON DELETE CASCADE, + "reported_user_id" INT NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE, + "reporter_id" INT NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE, + "resolved_by_id" INT REFERENCES "users" ("id") ON DELETE CASCADE +) +""" diff --git a/app/services/adaptive_trottle.py b/app/services/adaptive_trottle.py index 330ff8e8..f713454b 100644 --- a/app/services/adaptive_trottle.py +++ b/app/services/adaptive_trottle.py @@ -5,7 +5,7 @@ from aiogram import types -from app.models.db import User, Chat +from app.infrastructure.database.models import User, Chat from app.utils.exceptions import Throttled from app.utils.log import Logger diff --git a/app/services/change_karma.py b/app/services/change_karma.py index 5ac1ae81..177c0e77 100644 --- a/app/services/change_karma.py +++ b/app/services/change_karma.py @@ -4,7 +4,7 @@ from app.config.main import load_config from app.models.common import TypeRestriction -from app.models.db import ( +from app.infrastructure.database.models import ( User, Chat, UserKarma, diff --git a/app/services/find_target_user.py b/app/services/find_target_user.py index 8767e911..369f8f22 100644 --- a/app/services/find_target_user.py +++ b/app/services/find_target_user.py @@ -6,7 +6,7 @@ from app.models import dto from app.models.config import TgClientConfig -from app.models.db import User +from app.infrastructure.database.models import User from app.services.user_getter import UserGetter from app.utils.exceptions import UserWithoutUserIdError from app.utils.log import Logger diff --git a/app/services/karma.py b/app/services/karma.py index 3e9dae60..65ffe980 100644 --- a/app/services/karma.py +++ b/app/services/karma.py @@ -2,7 +2,7 @@ from aiogram.utils.markdown import hbold -from app.models.db import Chat, User, UserKarma +from app.infrastructure.database.models import Chat, User, UserKarma from app.utils.exceptions import NotHaveNeighbours diff --git a/app/services/moderation.py b/app/services/moderation.py index ca28edf7..8a0ed817 100644 --- a/app/services/moderation.py +++ b/app/services/moderation.py @@ -8,7 +8,7 @@ from app.config import moderation from app.config.main import load_config from app.models.common import TypeRestriction -from app.models.db import ModeratorEvent, User, Chat +from app.infrastructure.database.models import ModeratorEvent, User, Chat from app.utils.exceptions import CantRestrict from app.utils.log import Logger from app.utils.timedelta_functions import parse_timedelta_from_text, format_timedelta diff --git a/app/services/report.py b/app/services/report.py index 14bb92ad..63596efa 100644 --- a/app/services/report.py +++ b/app/services/report.py @@ -4,8 +4,7 @@ from aiogram import Bot from tortoise.backends.base.client import BaseDBAsyncClient -from app.models.db import User, Chat -from app.models.db.report import Report, ReportStatus +from app.infrastructure.database.models import User, Chat, Report, ReportStatus from app.services.change_karma import change_karma from app.utils.types import ResultChangeKarma diff --git a/app/services/settings.py b/app/services/settings.py index 103109cb..fbe7442c 100644 --- a/app/services/settings.py +++ b/app/services/settings.py @@ -1,4 +1,4 @@ -from app.models.db import Chat, ChatSettings +from app.infrastructure.database.models import Chat, ChatSettings async def get_chat_settings(chat: Chat) -> ChatSettings: diff --git a/app/services/user_info.py b/app/services/user_info.py index 10715377..ef1f3970 100644 --- a/app/services/user_info.py +++ b/app/services/user_info.py @@ -1,4 +1,4 @@ -from app.models.db import User, Chat, ModeratorEvent, KarmaEvent +from app.infrastructure.database.models import User, Chat, ModeratorEvent, KarmaEvent async def get_user_info(user: User, chat: Chat, date_format: str): diff --git a/app/utils/types.py b/app/utils/types.py index 81cdb992..e17f8321 100644 --- a/app/utils/types.py +++ b/app/utils/types.py @@ -1,6 +1,6 @@ from typing import NamedTuple -from app.models.db import UserKarma, KarmaEvent, ModeratorEvent +from app.infrastructure.database.models import UserKarma, KarmaEvent, ModeratorEvent class ResultChangeKarma(NamedTuple): From 0aa2471d7630d5c66008d19ba223e4f46393c307 Mon Sep 17 00:00:00 2001 From: Lamroy95 Date: Thu, 26 Oct 2023 17:41:36 +0400 Subject: [PATCH 2/4] Fix imports --- .../database/models/chat_settings.py | 2 +- .../database/models/karma_actions.py | 14 ++++++------- .../database/models/moderator_actions.py | 20 +++++++++---------- app/infrastructure/database/models/report.py | 13 ++++++------ app/infrastructure/database/models/user.py | 20 +++++++++---------- .../database/models/user_karma.py | 3 ++- app/models/db/db.py | 17 ---------------- 7 files changed, 34 insertions(+), 55 deletions(-) diff --git a/app/infrastructure/database/models/chat_settings.py b/app/infrastructure/database/models/chat_settings.py index 3b200571..a21aa930 100644 --- a/app/infrastructure/database/models/chat_settings.py +++ b/app/infrastructure/database/models/chat_settings.py @@ -1,7 +1,7 @@ from tortoise import fields from tortoise.models import Model -from app.infrastructure.database.models import Chat +from app.infrastructure.database.models.chat import Chat class ChatSettings(Model): diff --git a/app/infrastructure/database/models/karma_actions.py b/app/infrastructure/database/models/karma_actions.py index 22f4b133..e9aabf5d 100644 --- a/app/infrastructure/database/models/karma_actions.py +++ b/app/infrastructure/database/models/karma_actions.py @@ -1,20 +1,18 @@ -import typing - from aiogram.utils.text_decorations import html_decoration as hd from tortoise import fields from tortoise.models import Model -if typing.TYPE_CHECKING: - from app.infrastructure.database.models import Chat, User +from app.infrastructure.database.models.chat import Chat +from app.infrastructure.database.models.user import User class KarmaEvent(Model): id_ = fields.IntField(pk=True, source_field="id") - user_from: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( + user_from: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( 'models.User', related_name='i_change_karma_events') - user_to: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( + user_to: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( 'models.User', related_name='my_karma_events') - chat: fields.ForeignKeyRelation["Chat"] = fields.ForeignKeyField( + chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( 'models.Chat', related_name='karma_events') date = fields.DatetimeField(auto_now=True, null=False) how_change = fields.FloatField( @@ -36,7 +34,7 @@ def __repr__(self): ) @classmethod - async def get_last_by_user(cls, user: "User", chat: "Chat", limit: int = 10): + async def get_last_by_user(cls, user: User, chat: Chat, limit: int = 10): return await cls.filter( user_to=user, chat=chat diff --git a/app/infrastructure/database/models/moderator_actions.py b/app/infrastructure/database/models/moderator_actions.py index 8b867108..a8067dd9 100644 --- a/app/infrastructure/database/models/moderator_actions.py +++ b/app/infrastructure/database/models/moderator_actions.py @@ -1,4 +1,3 @@ -import typing from datetime import timedelta from aiogram.utils.text_decorations import html_decoration as hd @@ -7,18 +6,17 @@ from app.utils.timedelta_functions import format_timedelta from app.models.common import TypeRestriction - -if typing.TYPE_CHECKING: - from app.infrastructure.database.models import Chat, User +from app.infrastructure.database.models.chat import Chat +from app.infrastructure.database.models.user import User class ModeratorEvent(Model): id_ = fields.IntField(pk=True, source_field="id") - moderator: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( + moderator: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( 'models.User', related_name='my_moderator_events') - user: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( + user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( 'models.User', related_name='my_restriction_events') - chat: fields.ForeignKeyRelation["Chat"] = fields.ForeignKeyField( + chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( 'models.Chat', related_name='moderator_events') date = fields.DatetimeField(auto_now=True, null=False) type_restriction = fields.CharField(max_length=20) @@ -38,9 +36,9 @@ def __repr__(self): @classmethod async def save_new_action( cls, - moderator: "User", - user: "User", - chat: "Chat", + moderator: User, + user: User, + chat: Chat, type_restriction: str, duration: timedelta = None, comment: str = "", @@ -58,7 +56,7 @@ async def save_new_action( return moderator_event @classmethod - async def get_last_by_user(cls, user: "User", chat: "Chat", limit: int = 10): + async def get_last_by_user(cls, user: User, chat: Chat, limit: int = 10): return await cls.filter( user=user, chat=chat diff --git a/app/infrastructure/database/models/report.py b/app/infrastructure/database/models/report.py index a8cfea65..4866e924 100644 --- a/app/infrastructure/database/models/report.py +++ b/app/infrastructure/database/models/report.py @@ -1,11 +1,10 @@ -import typing from enum import Enum from tortoise import fields from tortoise.models import Model -if typing.TYPE_CHECKING: - from app.infrastructure.database.models import Chat, User +from app.infrastructure.database.models.chat import Chat +from app.infrastructure.database.models.user import User TG_MESSAGE_MAX_LEN = 4096 @@ -19,15 +18,15 @@ class ReportStatus(Enum): class Report(Model): id = fields.IntField(pk=True) - reporter: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( + reporter: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( 'models.User', related_name='made_reports' ) - reported_user: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( + reported_user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( 'models.User', related_name='got_reports' ) - chat: fields.ForeignKeyRelation["Chat"] = fields.ForeignKeyField( + chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( 'models.Chat', related_name='reports' ) @@ -35,7 +34,7 @@ class Report(Model): resolution_time = fields.DatetimeField(null=True) reported_message_id = fields.BigIntField(generated=False, null=False) reported_message_content = fields.CharField(null=False, max_length=TG_MESSAGE_MAX_LEN) - resolved_by: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField( + resolved_by: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( 'models.User', related_name='resolved_reports', null=True diff --git a/app/infrastructure/database/models/user.py b/app/infrastructure/database/models/user.py index 3fc1014b..dca39cd4 100644 --- a/app/infrastructure/database/models/user.py +++ b/app/infrastructure/database/models/user.py @@ -1,4 +1,5 @@ from datetime import datetime +from typing import TYPE_CHECKING from aiogram import types from aiogram.utils.text_decorations import html_decoration as hd @@ -8,9 +9,13 @@ from app.models.common import TypeRestriction from app.utils.exceptions import UserWithoutUserIdError -from app.infrastructure.database.models import Chat +from app.infrastructure.database.models.chat import Chat from app.models import dto +if TYPE_CHECKING: + from app.infrastructure.database.models.user_karma import UserKarma + from app.infrastructure.database.models.moderator_actions import ModeratorEvent + class User(Model): id = fields.IntField(pk=True) @@ -19,10 +24,8 @@ class User(Model): last_name = fields.CharField(max_length=255, null=True) username = fields.CharField(max_length=32, null=True) is_bot: bool = fields.BooleanField(null=True) - # noinspection PyUnresolvedReferences - karma: fields.ReverseRelation['UserKarma'] # noqa: F821 - # noinspection PyUnresolvedReferences - my_restriction_events: fields.ReverseRelation['ModeratorEvent'] # noqa: F821 + karma: fields.ReverseRelation["UserKarma"] + my_restriction_events: fields.ReverseRelation["ModeratorEvent"] class Meta: table = "users" @@ -100,14 +103,12 @@ def fullname(self): return ' '.join((self.first_name, self.last_name)) return self.first_name or self.username or str(self.tg_id) or str(self.id) - # noinspection PyUnresolvedReferences - async def get_uk(self, chat: Chat) -> "UserKarma": # noqa: F821 + async def get_uk(self, chat: Chat) -> "UserKarma": return await self.karma.filter(chat=chat).first() async def get_karma(self, chat: Chat): user_karma = await self.get_uk(chat) if user_karma: - # noinspection PyUnresolvedReferences return user_karma.karma_round return None @@ -117,8 +118,7 @@ async def set_karma(self, chat: Chat, karma: int): await user_karma.save() async def get_number_in_top_karma(self, chat: Chat) -> int: - # noinspection PyUnresolvedReferences - uk: "UserKarma" = await self.get_uk(chat) # noqa: F821 + uk = await self.get_uk(chat) return await uk.number_in_top() async def has_now_ro_db(self, chat: Chat): diff --git a/app/infrastructure/database/models/user_karma.py b/app/infrastructure/database/models/user_karma.py index e41f1e06..99e6ecb2 100644 --- a/app/infrastructure/database/models/user_karma.py +++ b/app/infrastructure/database/models/user_karma.py @@ -4,7 +4,8 @@ from tortoise.models import Model from app.filters.karma_change import INF -from app.infrastructure.database.models import User, Chat +from app.infrastructure.database.models.chat import Chat +from app.infrastructure.database.models.user import User from app.models.db.db import karma_filters from app.utils.exceptions import SubZeroKarma from app.utils.log import Logger diff --git a/app/models/db/db.py b/app/models/db/db.py index c0df4fed..54ea2e61 100644 --- a/app/models/db/db.py +++ b/app/models/db/db.py @@ -29,20 +29,3 @@ async def generate_schemas_db(db_config: DBConfig): def generate_schemas(db_config: DBConfig): run_async(generate_schemas_db(db_config)) - - - -""" -CREATE TABLE "reports" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "created_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - "resolution_time" TIMESTAMP, - "reported_message_id" BIGINT NOT NULL, - "reported_message_content" VARCHAR(4096) NOT NULL, - "status" VARCHAR(9) NOT NULL /* approved: Approved\ndeclined: Declined\npending: Pending\ncancelled: Cancelled */, - "chat_id" BIGINT NOT NULL REFERENCES "chats" ("chat_id") ON DELETE CASCADE, - "reported_user_id" INT NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE, - "reporter_id" INT NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE, - "resolved_by_id" INT REFERENCES "users" ("id") ON DELETE CASCADE -) -""" From c575ad5e144685a4ee439ec0c3353861bbd38c09 Mon Sep 17 00:00:00 2001 From: Lamroy95 Date: Fri, 27 Oct 2023 10:45:01 +0400 Subject: [PATCH 3/4] Formatting --- app/filters/karma_change.py | 34 ++-- app/filters/tg_permissions.py | 39 ++-- app/handlers/base.py | 59 ++++--- app/handlers/change_karma.py | 85 +++++---- app/handlers/karma.py | 47 +++-- app/handlers/keyboards.py | 140 ++++++++------- app/handlers/moderator.py | 166 +++++++++++------- app/handlers/settings.py | 28 +-- app/handlers/superuser.py | 14 +- .../database/models/__init__.py | 12 +- app/infrastructure/database/models/chat.py | 56 +++--- .../database/models/chat_settings.py | 14 +- .../database/models/karma_actions.py | 24 ++- .../database/models/moderator_actions.py | 57 +++--- app/infrastructure/database/models/report.py | 19 +- app/infrastructure/database/models/user.py | 25 +-- .../database/models/user_karma.py | 65 ++++--- app/middlewares/db_middleware.py | 13 +- app/models/config/auto_restriction.py | 14 +- app/models/db/db.py | 8 +- app/services/adaptive_trottle.py | 80 +++++---- app/services/change_karma.py | 66 ++++--- app/services/find_target_user.py | 42 +++-- app/services/karma.py | 19 +- app/services/moderation.py | 95 ++++++---- app/services/report.py | 30 ++-- app/services/user_info.py | 7 +- app/utils/types.py | 2 +- 28 files changed, 754 insertions(+), 506 deletions(-) diff --git a/app/filters/karma_change.py b/app/filters/karma_change.py index 4c0b6af0..ac8a4591 100644 --- a/app/filters/karma_change.py +++ b/app/filters/karma_change.py @@ -5,17 +5,17 @@ from aiogram.filters import BaseFilter from app.config.karmic_triggers import ( - PLUS, - PLUS_TRIGGERS, - PLUS_EMOJI, MINUS, MINUS_EMOJI, MINUS_TRIGGERS, + PLUS, + PLUS_EMOJI, + PLUS_TRIGGERS, ) from app.infrastructure.database.models import ChatSettings PUNCTUATIONS = ",.!)" -INF = float('inf') +INF = float("inf") @dataclass @@ -25,7 +25,9 @@ class KarmaFilter(BaseFilter): """ async def __call__( - self, message: types.Message, chat_settings: ChatSettings, + self, + message: types.Message, + chat_settings: ChatSettings, ) -> dict[str, dict[str, float]]: if chat_settings is None or not chat_settings.karma_counting: return {} @@ -39,7 +41,7 @@ async def check(message: types.Message) -> dict[str, dict[str, float]]: karma_change, comment = get_karma_trigger(possible_trigger_text) if karma_change is None: return {} - rez = {'karma': {'karma_change': karma_change, 'comment': comment}} + rez = {"karma": {"karma_change": karma_change, "comment": comment}} return rez @@ -78,20 +80,22 @@ def has_plus_karma(possible_trigger: str) -> typing.Optional[float]: if len(possible_trigger) == 0: # blank line has no triggers return None - if all([ - len(possible_trigger) > 1, - possible_trigger[1:] == possible_trigger[:-1], - possible_trigger[0:len(PLUS)] == PLUS - ]): + if all( + [ + len(possible_trigger) > 1, + possible_trigger[1:] == possible_trigger[:-1], + possible_trigger[0 : len(PLUS)] == PLUS, + ] + ): # contains only ++..+ return INF if possible_trigger in PLUS_TRIGGERS: return INF if possible_trigger[0] in PLUS_EMOJI: return INF - if possible_trigger[0:len(PLUS)] == PLUS: + if possible_trigger[0 : len(PLUS)] == PLUS: try: - return +int(possible_trigger[len(PLUS):]) + return +int(possible_trigger[len(PLUS) :]) except ValueError: pass return None @@ -107,9 +111,9 @@ def has_minus_karma(possible_trigger: str) -> typing.Optional[float]: # will never be true. Maybe we can remove it from condition if not has_spaces(possible_trigger) and possible_trigger[0] in MINUS_EMOJI: return -INF - if possible_trigger[0:len(MINUS)] == MINUS: + if possible_trigger[0 : len(MINUS)] == MINUS: try: - return -int(possible_trigger[len(MINUS):]) + return -int(possible_trigger[len(MINUS) :]) except ValueError: pass return None diff --git a/app/filters/tg_permissions.py b/app/filters/tg_permissions.py index c0d8ea2d..4d464221 100644 --- a/app/filters/tg_permissions.py +++ b/app/filters/tg_permissions.py @@ -18,6 +18,7 @@ class HasPermissions(BaseFilter): """ Validate the user has specified permissions in chat """ + can_post_messages: bool = False can_edit_messages: bool = False can_delete_messages: bool = False @@ -44,13 +45,19 @@ def __post_init__(self): arg: True for arg in self.ARGUMENTS.values() if getattr(self, arg) } - def _get_cached_value(self, user: types.User, chat: Chat) -> types.ChatMember | None: + def _get_cached_value( + self, user: types.User, chat: Chat + ) -> types.ChatMember | None: return None # TODO - def _set_cached_value(self, user: types.User, chat: Chat, _member: types.ChatMember): + def _set_cached_value( + self, user: types.User, chat: Chat, _member: types.ChatMember + ): return None # TODO - async def _get_chat_member(self, update: types.TelegramObject, user: types.User, chat: Chat, bot: Bot): + async def _get_chat_member( + self, update: types.TelegramObject, user: types.User, chat: Chat, bot: Bot + ): chat_member = self._get_cached_value(user, chat) if chat_member is None: @@ -59,17 +66,20 @@ async def _get_chat_member(self, update: types.TelegramObject, user: types.User, if target_user_id is None: return False try: - chat_member = next(filter(lambda member: member.user.id == target_user_id, admins)) + chat_member = next( + filter(lambda member: member.user.id == target_user_id, admins) + ) except StopIteration: return False self._set_cached_value(user, chat, chat_member) return chat_member async def __call__( - self, update: types.TelegramObject, - event_from_user: types.User, - chat: Chat, - bot: Bot + self, + update: types.TelegramObject, + event_from_user: types.User, + chat: Chat, + bot: Bot, ) -> bool | dict[str, Any]: chat_member = await self._get_chat_member(update, event_from_user, chat, bot) if not chat_member: @@ -82,7 +92,9 @@ async def __call__( return {self.PAYLOAD_ARGUMENT_NAME: chat_member} - def get_target_id(self, update: types.TelegramObject, user: types.User, bot: Bot) -> int | None: + def get_target_id( + self, update: types.TelegramObject, user: types.User, bot: Bot + ) -> int | None: return user.id @@ -91,10 +103,13 @@ class TargetHasPermissions(HasPermissions): """ Validate the target user has specified permissions in chat """ + can_be_same: bool = False can_be_bot: bool = False - def get_target_id(self, message: types.Message, user: types.User, bot: Bot) -> int | None: + def get_target_id( + self, message: types.Message, user: types.User, bot: Bot + ) -> int | None: target_user = get_target_user(message, self.can_be_same, self.can_be_bot) if target_user is None: return None @@ -118,5 +133,7 @@ class BotHasPermissions(HasPermissions): } PAYLOAD_ARGUMENT_NAME = "bot_member" - def get_target_id(self, message: types.Message, user: types.User, bot: Bot) -> int | None: + def get_target_id( + self, message: types.Message, user: types.User, bot: Bot + ) -> int | None: return bot.id diff --git a/app/handlers/base.py b/app/handlers/base.py index 19f4f30b..1819879d 100644 --- a/app/handlers/base.py +++ b/app/handlers/base.py @@ -1,17 +1,16 @@ -from aiogram import types, F, Router +from aiogram import F, Router, types from aiogram.filters import Command from aiogram.fsm.context import FSMContext -from aiogram.utils.markdown import hpre, hbold +from aiogram.utils.markdown import hbold, hpre from app.infrastructure.database.models import Chat from app.utils.log import Logger - logger = Logger(__name__) router = Router(name=__name__) -@router.message(Command("start", prefix='!/')) +@router.message(Command("start", prefix="!/")) async def cmd_start(message: types.Message): logger.info("User {user} start conversation with bot", user=message.from_user.id) await message.answer( @@ -22,34 +21,40 @@ async def cmd_start(message: types.Message): ) -@router.message(Command("help", prefix='!/')) +@router.message(Command("help", prefix="!/")) async def cmd_help(message: types.Message): - logger.info("User {user} read help in {chat}", user=message.from_user.id, chat=message.chat.id) + logger.info( + "User {user} read help in {chat}", + user=message.from_user.id, + chat=message.chat.id, + ) await message.reply( - '➕Плюсануть в карму можно начав сообщение со спасибо или плюса.\n' - '➖Минусануть - с минуса.\n' - '📩Чтобы выбрать пользователя - нужно ответить реплаем на сообщение пользователя ' - 'или упомянуть его через @ (работает даже если у пользователя нет username).\n' - '🦾Сила, с которой пользователь меняет другим карму, зависит от собственной кармы, ' - 'чем она больше, тем больше будет изменение кармы у цели ' - '(вычисляется как корень из кармы)\n' - '🤖Основные команды:\n' - '!top [chat_id] - топ юзеров по карме для текущего чата или для чата с chat_id \n' - '!about - информация о боте и его исходники\n' - '!me - посмотреть свою карму (желательно это делать в личных сообщениях с ботом)\n' - '!report {{реплаем}} - пожаловаться на сообщение модераторам\n' - '!idchat - показать Ваш id, id чата и, ' - 'если имеется, - id пользователя, которому Вы ответили командой' + "➕Плюсануть в карму можно начав сообщение со спасибо или плюса.\n" + "➖Минусануть - с минуса.\n" + "📩Чтобы выбрать пользователя - нужно ответить реплаем на сообщение пользователя " + "или упомянуть его через @ (работает даже если у пользователя нет username).\n" + "🦾Сила, с которой пользователь меняет другим карму, зависит от собственной кармы, " + "чем она больше, тем больше будет изменение кармы у цели " + "(вычисляется как корень из кармы)\n" + "🤖Основные команды:\n" + "!top [chat_id] - топ юзеров по карме для текущего чата или " + "для чата с chat_id \n" + "!about - информация о боте и его исходники\n" + "!me - посмотреть свою карму (желательно это делать в личных " + "сообщениях с ботом)\n" + "!report {{реплаем}} - пожаловаться на сообщение модераторам\n" + "!idchat - показать Ваш id, id чата и, " + "если имеется, - id пользователя, которому Вы ответили командой" ) -@router.message(Command("about", prefix='!')) +@router.message(Command("about", prefix="!")) async def cmd_about(message: types.Message): logger.info("User {user} about", user=message.from_user.id) - await message.reply('Исходники по ссылке https://github.com/bomzheg/KarmaBot') + await message.reply("Исходники по ссылке https://github.com/bomzheg/KarmaBot") -@router.message(Command('idchat', prefix='!')) +@router.message(Command("idchat", prefix="!")) async def get_idchat(message: types.Message): text = ( f"id этого чата: {hpre(message.chat.id)}\n" @@ -63,16 +68,18 @@ async def get_idchat(message: types.Message): await message.reply(text, disable_notification=True) -@router.message(Command('cancel')) +@router.message(Command("cancel")) async def cancel_state(message: types.Message, state: FSMContext): current_state = await state.get_state() if current_state is None: return - logger.info(f'Cancelling state {current_state}') + logger.info(f"Cancelling state {current_state}") # Cancel state and inform user about it await state.clear() # And remove keyboard (just in case) - await message.reply('Диалог прекращён, данные удалены', reply_markup=types.ReplyKeyboardRemove()) + await message.reply( + "Диалог прекращён, данные удалены", reply_markup=types.ReplyKeyboardRemove() + ) @router.message(F.message.content_types == types.ContentType.MIGRATE_TO_CHAT_ID) diff --git a/app/handlers/change_karma.py b/app/handlers/change_karma.py index 01fbbdd0..885f2de2 100644 --- a/app/handlers/change_karma.py +++ b/app/handlers/change_karma.py @@ -1,19 +1,20 @@ import asyncio -from aiogram import types, F, Bot, Router +from aiogram import Bot, F, Router, types from aiogram.types import ContentType from aiogram.utils.text_decorations import html_decoration as hd +from app.filters import HasTargetFilter, KarmaFilter from app.infrastructure.database.models import Chat, User +from app.models.config import Config from app.services.adaptive_trottle import AdaptiveThrottle -from app.services.change_karma import change_karma, cancel_karma_change +from app.services.change_karma import cancel_karma_change, change_karma from app.services.remove_message import remove_kb from app.services.settings import is_enable_karmic_restriction -from app.utils.exceptions import SubZeroKarma, CantChangeKarma, DontOffendRestricted +from app.utils.exceptions import CantChangeKarma, DontOffendRestricted, SubZeroKarma from app.utils.log import Logger + from . import keyboards as kb -from app.models.config import Config -from app.filters import HasTargetFilter, KarmaFilter logger = Logger(__name__) router = Router(name=__name__) @@ -37,30 +38,36 @@ async def too_fast_change_karma(message: types.Message, *_, **__): F.chat.type.in_(["group", "supergroup"]), HasTargetFilter(), KarmaFilter(), - F.content_type.in_([ - ContentType.TEXT, - - ContentType.STICKER, - - ContentType.ANIMATION, - ContentType.AUDIO, - ContentType.DOCUMENT, - ContentType.PHOTO, - ContentType.VIDEO, - ContentType.VOICE, - ]) + F.content_type.in_( + [ + ContentType.TEXT, + ContentType.STICKER, + ContentType.ANIMATION, + ContentType.AUDIO, + ContentType.DOCUMENT, + ContentType.PHOTO, + ContentType.VIDEO, + ContentType.VOICE, + ] + ), ) @a_throttle.throttled(rate=30, on_throttled=too_fast_change_karma) async def karma_change( - message: types.Message, karma: dict, user: User, chat: Chat, target: User, config: Config, bot: Bot + message: types.Message, + karma: dict, + user: User, + chat: Chat, + target: User, + config: Config, + bot: Bot, ): try: result_change_karma = await change_karma( target_user=target, chat=chat, user=user, - how_change=karma['karma_change'], - comment=karma['comment'], + how_change=karma["karma_change"], + comment=karma["comment"], bot=bot, ) except SubZeroKarma: @@ -73,23 +80,29 @@ async def karma_change( if result_change_karma.was_auto_restricted: notify_text = config.auto_restriction.render_auto_restriction( - target, result_change_karma.count_auto_restrict) - elif result_change_karma.karma_after < 0 and await is_enable_karmic_restriction(chat): + target, result_change_karma.count_auto_restrict + ) + elif result_change_karma.karma_after < 0 and await is_enable_karmic_restriction( + chat + ): notify_text = config.auto_restriction.render_negative_karma_notification( - target, result_change_karma.count_auto_restrict) + target, result_change_karma.count_auto_restrict + ) else: notify_text = "" # How much karma was changed. Sign show changed difference, not difference for cancel - how_changed_karma = result_change_karma.user_karma.karma \ - - result_change_karma.karma_after \ + how_changed_karma = ( + result_change_karma.user_karma.karma + - result_change_karma.karma_after + result_change_karma.abs_change + ) msg = await message.reply( - "{actor_name}, Вы {how_change} карму {target_name} до {karma_new:.2f} ({power:+.2f})" - "\n\n{notify_text}".format( + "{actor_name}, Вы {how_change} карму {target_name} " + "до {karma_new:.2f} ({power:+.2f})\n\n{notify_text}".format( actor_name=hd.quote(user.fullname), - how_change=get_how_change_text(karma['karma_change']), + how_change=get_how_change_text(karma["karma_change"]), target_name=hd.quote(target.fullname), karma_new=result_change_karma.karma_after, power=result_change_karma.abs_change, @@ -102,17 +115,25 @@ async def karma_change( karma_event=result_change_karma.karma_event, rollback_karma=-how_changed_karma, moderator_event=result_change_karma.moderator_event, - ) + ), ) asyncio.create_task(remove_kb(msg, config.time_to_cancel_actions)) @router.callback_query(kb.KarmaCancelCb.filter()) -async def cancel_karma(callback_query: types.CallbackQuery, callback_data: kb.KarmaCancelCb, bot: Bot): +async def cancel_karma( + callback_query: types.CallbackQuery, callback_data: kb.KarmaCancelCb, bot: Bot +): if callback_data.user_id != callback_query.from_user.id: return await callback_query.answer("Эта кнопка не для Вас", cache_time=3600) rollback_karma = float(callback_data.rollback_karma) - moderator_event_id = None if callback_data.moderator_event_id == "null" else callback_data.moderator_event_id - await cancel_karma_change(callback_data.karma_event_id, rollback_karma, moderator_event_id, bot) + moderator_event_id = ( + None + if callback_data.moderator_event_id == "null" + else callback_data.moderator_event_id + ) + await cancel_karma_change( + callback_data.karma_event_id, rollback_karma, moderator_event_id, bot + ) await callback_query.answer("Вы отменили изменение кармы", show_alert=True) await callback_query.message.delete() diff --git a/app/handlers/karma.py b/app/handlers/karma.py index 9ac1d2e4..d01e7234 100644 --- a/app/handlers/karma.py +++ b/app/handlers/karma.py @@ -1,28 +1,21 @@ import asyncio -from aiogram import types, F, Router +from aiogram import F, Router, types from aiogram.filters import Command from aiogram.utils.text_decorations import html_decoration as hd +from app.infrastructure.database.models import Chat, User from app.models.config import Config -from app.infrastructure.database.models import ( - Chat, - User -) -from app.services.karma import ( - get_top as get_karma_top, - get_me_info, - get_me_chat_info -) +from app.services.karma import get_me_chat_info, get_me_info +from app.services.karma import get_top as get_karma_top from app.services.remove_message import delete_message from app.utils.log import Logger - logger = Logger(__name__) router = Router(name=__name__) -@router.message(Command("top", prefix='!'), F.chat.type == "private") +@router.message(Command("top", prefix="!"), F.chat.type == "private") async def get_top_from_private(message: types.Message, user: User): parts = message.text.split(maxsplit=1) if len(parts) > 1: @@ -33,34 +26,40 @@ async def get_top_from_private(message: types.Message, user: User): "или с указанием id нужного чата, например:" "\n" + hd.code("!top -1001399056118") ) - logger.info("user {user} ask top karma of chat {chat}", user=user.tg_id, chat=chat.chat_id) + logger.info( + "user {user} ask top karma of chat {chat}", user=user.tg_id, chat=chat.chat_id + ) text = await get_karma_top(chat, user) await message.reply(text, disable_web_page_preview=True) -@router.message(Command("top", prefix='!')) +@router.message(Command("top", prefix="!")) async def get_top(message: types.Message, chat: Chat, user: User): - logger.info("user {user} ask top karma of chat {chat}", user=user.tg_id, chat=chat.chat_id) + logger.info( + "user {user} ask top karma of chat {chat}", user=user.tg_id, chat=chat.chat_id + ) text = await get_karma_top(chat, user) await message.reply(text, disable_web_page_preview=True) -@router.message(F.chat.type.in_(["group", "supergroup"]), Command("me", prefix='!')) -async def get_top(message: types.Message, chat: Chat, user: User, config: Config): - logger.info("user {user} ask his karma in chat {chat}", user=user.tg_id, chat=chat.chat_id) +@router.message(F.chat.type.in_(["group", "supergroup"]), Command("me", prefix="!")) +async def get_me(message: types.Message, chat: Chat, user: User, config: Config): + logger.info( + "user {user} ask his karma in chat {chat}", user=user.tg_id, chat=chat.chat_id + ) uk, number_in_top = await get_me_chat_info(chat=chat, user=user) msg = await message.reply( f"Ваша карма в данном чате: {uk.karma:.2f} ({number_in_top})", - disable_web_page_preview=True + disable_web_page_preview=True, ) asyncio.create_task(delete_message(msg, config.time_to_remove_temp_messages)) asyncio.create_task(delete_message(message, config.time_to_remove_temp_messages)) -@router.message(F.chat.type == "private", Command("me", prefix='!')) -async def get_top(message: types.Message, user: User): +@router.message(F.chat.type == "private", Command("me", prefix="!")) +async def get_me_private(message: types.Message, user: User): logger.info("user {user} ask his karma", user=user.tg_id) uks = await get_me_info(user) text = "" @@ -68,10 +67,8 @@ async def get_top(message: types.Message, user: User): text += f"\n{uk.chat.mention} {uk.karma:.2f} ({number_in_top})" if text: return await message.reply( - f"У Вас есть карма в следующих чатах:{text}", - disable_web_page_preview=True + f"У Вас есть карма в следующих чатах:{text}", disable_web_page_preview=True ) await message.reply( - f"У Вас нет никакой кармы ни в каких чатах", - disable_web_page_preview=True + "У Вас нет никакой кармы ни в каких чатах", disable_web_page_preview=True ) diff --git a/app/handlers/keyboards.py b/app/handlers/keyboards.py index 122d377c..719b6064 100644 --- a/app/handlers/keyboards.py +++ b/app/handlers/keyboards.py @@ -1,12 +1,7 @@ from aiogram.filters.callback_data import CallbackData -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup -from app.infrastructure.database.models import ( - User, - KarmaEvent, - ModeratorEvent, - Report -) +from app.infrastructure.database.models import KarmaEvent, ModeratorEvent, Report, User class KarmaCancelCb(CallbackData, prefix="karma_cancel"): @@ -37,91 +32,112 @@ class CancelReportCb(CallbackData, prefix="cancel_report"): def get_kb_karma_cancel( - user: User, karma_event: KarmaEvent, rollback_karma: float, moderator_event: ModeratorEvent + user: User, + karma_event: KarmaEvent, + rollback_karma: float, + moderator_event: ModeratorEvent, ) -> InlineKeyboardMarkup: - return InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton( - text="Отменить", - callback_data=KarmaCancelCb( - user_id=user.tg_id, - karma_event_id=karma_event.id_, - rollback_karma=f"{rollback_karma:.2f}", - moderator_event_id=moderator_event.id_ if moderator_event is not None else "null", - ).pack() - )]]) + return InlineKeyboardMarkup( + inline_keyboard=[ + [ + InlineKeyboardButton( + text="Отменить", + callback_data=KarmaCancelCb( + user_id=user.tg_id, + karma_event_id=karma_event.id_, + rollback_karma=f"{rollback_karma:.2f}", + moderator_event_id=moderator_event.id_ + if moderator_event is not None + else "null", + ).pack(), + ) + ] + ] + ) def get_kb_warn_cancel(user: User, moderator_event: ModeratorEvent): - return InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton( - text='Отменить', - callback_data=WarnCancelCb( - user_id=user.tg_id, - moderator_event_id=moderator_event.id_ - ).pack() - )]]) + return InlineKeyboardMarkup( + inline_keyboard=[ + [ + InlineKeyboardButton( + text="Отменить", + callback_data=WarnCancelCb( + user_id=user.tg_id, moderator_event_id=moderator_event.id_ + ).pack(), + ) + ] + ] + ) def get_lmgfy_kb(question: str): return InlineKeyboardMarkup( - inline_keyboard=[[ - InlineKeyboardButton( - text="Подробнее", - url=f"https://google.com/search?q={question}", - ) - ]] + inline_keyboard=[ + [ + InlineKeyboardButton( + text="Подробнее", + url=f"https://google.com/search?q={question}", + ) + ] + ] ) def get_nometa_kb(): return InlineKeyboardMarkup( - inline_keyboard=[[ - InlineKeyboardButton( - text="Подробнее", - url="https://nometa.xyz/ru.html", - ) - ]] + inline_keyboard=[ + [ + InlineKeyboardButton( + text="Подробнее", + url="https://nometa.xyz/ru.html", + ) + ] + ] ) def get_xy_problem_kb(): return InlineKeyboardMarkup( - inline_keyboard=[[ - InlineKeyboardButton( - text="Подробнее", - url="https://xyproblem.ru/", - ) - ]] + inline_keyboard=[ + [ + InlineKeyboardButton( + text="Подробнее", + url="https://xyproblem.ru/", + ) + ] + ] ) def get_paste_kb(): return InlineKeyboardMarkup( - inline_keyboard=[[ - InlineKeyboardButton( - text="dpaste.org", - url="https://dpaste.org", - ), - InlineKeyboardButton( - text="gist.github.com", - url="https://gist.github.com", - ), - ]] + inline_keyboard=[ + [ + InlineKeyboardButton( + text="dpaste.org", + url="https://dpaste.org", + ), + InlineKeyboardButton( + text="gist.github.com", + url="https://gist.github.com", + ), + ] + ] ) def get_report_reaction_kb(user: User, report: Report) -> InlineKeyboardMarkup: approve = InlineKeyboardButton( - text='Подтвердить', - callback_data=ApproveReportCb(report_id=report.id, reporter_id=user.id).pack() + text="Подтвердить", + callback_data=ApproveReportCb(report_id=report.id, reporter_id=user.id).pack(), ) decline = InlineKeyboardButton( - text='Отклонить', - callback_data=DeclineReportCb(report_id=report.id, reporter_id=user.id).pack() + text="Отклонить", + callback_data=DeclineReportCb(report_id=report.id, reporter_id=user.id).pack(), ) cancel = InlineKeyboardButton( - text='Отменить', - callback_data=CancelReportCb(report_id=report.id, reporter_id=user.id).pack() + text="Отменить", + callback_data=CancelReportCb(report_id=report.id, reporter_id=user.id).pack(), ) - return InlineKeyboardMarkup(inline_keyboard=[ - [approve, decline], - [cancel] - ]) + return InlineKeyboardMarkup(inline_keyboard=[[approve, decline], [cancel]]) diff --git a/app/handlers/moderator.py b/app/handlers/moderator.py index 989a63f3..f794abd0 100644 --- a/app/handlers/moderator.py +++ b/app/handlers/moderator.py @@ -1,10 +1,9 @@ import asyncio import random -from contextlib import suppress from aiogram import Bot, F, Router, types from aiogram.enums import ChatMemberStatus -from aiogram.exceptions import TelegramUnauthorizedError, TelegramBadRequest +from aiogram.exceptions import TelegramUnauthorizedError from aiogram.filters import Command, CommandObject, MagicData from aiogram.utils.text_decorations import html_decoration as hd from tortoise.transactions import in_transaction @@ -16,8 +15,8 @@ TargetHasPermissions, ) from app.handlers import keyboards as kb +from app.infrastructure.database.models import Chat, ChatSettings, ReportStatus, User from app.models.config import Config -from app.infrastructure.database.models import Chat, User, ReportStatus, ChatSettings from app.services.moderation import ( ban_user, delete_moderator_event, @@ -25,7 +24,11 @@ ro_user, warn_user, ) -from app.services.remove_message import delete_message, remove_kb, cleanup_command_dialog +from app.services.remove_message import ( + cleanup_command_dialog, + delete_message, + remove_kb, +) from app.services.report import register_report, resolve_report, reward_reporter from app.services.user_info import get_user_info from app.utils.exceptions import ModerationError, TimedeltaParseError @@ -38,10 +41,16 @@ @router.message( F.chat.type.in_(["group", "supergroup"]), HasTargetFilter(), - Command('report', 'admin', 'spam', prefix="/!@"), + Command("report", "admin", "spam", prefix="/!@"), ) -async def report_message(message: types.Message, chat: Chat, user: User, target: User, bot: Bot): - logger.info("user {user} report for message {message}", user=message.from_user.id, message=message.message_id) +async def report_message( + message: types.Message, chat: Chat, user: User, target: User, bot: Bot +): + logger.info( + "user {user} report for message {message}", + user=message.from_user.id, + message=message.message_id, + ) answer_message = "Спасибо за сообщение. Мы обязательно разберёмся" admins_mention = await get_mentions_admins(message.chat, bot) @@ -51,19 +60,23 @@ async def report_message(message: types.Message, chat: Chat, user: User, target: reported_user=target, chat=chat, reported_message=message.reply_to_message, - db_session=db_session + db_session=db_session, ) reaction_keyboard = kb.get_report_reaction_kb(report=report, user=user) - await message.reply(f"{answer_message}.{admins_mention}", reply_markup=reaction_keyboard) + await message.reply( + f"{answer_message}.{admins_mention}", reply_markup=reaction_keyboard + ) @router.message( F.chat.type == "private", - Command('report', 'admin', 'spam', prefix="/!@"), + Command("report", "admin", "spam", prefix="/!@"), ) async def report_private(message: types.Message): - await message.reply("Вы можете жаловаться на сообщения пользователей только в группах.") + await message.reply( + "Вы можете жаловаться на сообщения пользователей только в группах." + ) async def get_mentions_admins( @@ -74,7 +87,9 @@ async def get_mentions_admins( admins = await bot.get_chat_administrators(chat.id) random.shuffle(admins) # чтобы попадались разные админы admins_mention = "" - notifiable_admins = [admin for admin in admins if need_notify_admin(admin, ignore_anonymous)] + notifiable_admins = [ + admin for admin in admins if need_notify_admin(admin, ignore_anonymous) + ] random_five_admins = notifiable_admins[:5] for admin in random_five_admins: admins_mention += hd.link("⁠", admin.user.url) @@ -93,7 +108,11 @@ def need_notify_admin( """ if admin.user.is_bot or (ignore_anonymous and admin.is_anonymous): return False - return admin.status == ChatMemberStatus.CREATOR or admin.can_delete_messages or admin.can_restrict_members + return ( + admin.status == ChatMemberStatus.CREATOR + or admin.can_delete_messages + or admin.can_restrict_members + ) @router.message( @@ -104,7 +123,9 @@ def need_notify_admin( ~TargetHasPermissions(), BotHasPermissions(can_restrict_members=True), ) -async def cmd_ro(message: types.Message, user: User, target: User, chat: Chat, bot: Bot): +async def cmd_ro( + message: types.Message, user: User, target: User, chat: Chat, bot: Bot +): try: duration, comment = get_duration(message.text) except TimedeltaParseError as e: @@ -125,7 +146,9 @@ async def cmd_ro(message: types.Message, user: User, target: User, chat: Chat, b ~BotHasPermissions(can_restrict_members=True), ) async def cmd_ro_no_bot_permissions(message: types.Message): - await message.reply("Мне нужны соответствующие права, чтобы запрещать писать пользователям в группе.") + await message.reply( + "Мне нужны соответствующие права, чтобы запрещать писать пользователям в группе." + ) @router.message( @@ -144,7 +167,9 @@ async def cmd_ro_private(message: types.Message): ~TargetHasPermissions(), BotHasPermissions(can_restrict_members=True), ) -async def cmd_ban(message: types.Message, user: User, target: User, chat: Chat, bot: Bot): +async def cmd_ban( + message: types.Message, user: User, target: User, chat: Chat, bot: Bot +): try: duration, comment = get_duration(message.text) except TimedeltaParseError as e: @@ -165,7 +190,9 @@ async def cmd_ban(message: types.Message, user: User, target: User, chat: Chat, ~BotHasPermissions(can_restrict_members=True), ) async def cmd_ban_no_bot_permissions(message: types.Message): - await message.reply("Мне нужны соответствующие права, чтобы блокировать пользователей в группе.") + await message.reply( + "Мне нужны соответствующие права, чтобы блокировать пользователей в группе." + ) @router.message( @@ -182,22 +209,28 @@ async def cmd_ban_private(message: types.Message): Command(commands=["w", "warn"], prefix="!"), HasPermissions(can_restrict_members=True), ) -async def cmd_warn(message: types.Message, chat: Chat, target: User, user: User, config: Config, command: CommandObject): +async def cmd_warn( + message: types.Message, + chat: Chat, + target: User, + user: User, + config: Config, + command: CommandObject, +): comment = command.args or "" moderator_event = await warn_user( - moderator=user, - target_user=target, - chat=chat, - comment=comment + moderator=user, target_user=target, chat=chat, comment=comment ) - text = "Пользователь {user} получил официальное предупреждение от модератора".format( - user=target.mention_link, + text = ( + "Пользователь {user} получил официальное предупреждение от модератора".format( + user=target.mention_link, + ) ) msg = await message.reply( text, - reply_markup=kb.get_kb_warn_cancel(user=user, moderator_event=moderator_event) + reply_markup=kb.get_kb_warn_cancel(user=user, moderator_event=moderator_event), ) asyncio.create_task(remove_kb(msg, config.time_to_cancel_actions)) @@ -208,38 +241,44 @@ async def cmd_warn(message: types.Message, chat: Chat, target: User, user: User, Command(commands=["w", "warn"], prefix="!"), ) async def cmd_warn_private(message: types.Message): - await message.reply("Вы можете выдавать предупреждения пользователям только в группах.") + await message.reply( + "Вы можете выдавать предупреждения пользователям только в группах." + ) @router.message( F.chat.type == "private", - Command("info", prefix='!'), + Command("info", prefix="!"), ) async def get_info_about_user_private(message: types.Message): - await message.reply("Вы можете запрашивать информацию о пользователях только в группах.") + await message.reply( + "Вы можете запрашивать информацию о пользователях только в группах." + ) @router.message( F.chat.type.in_(["group", "supergroup"]), - Command("info", prefix='!'), + Command("info", prefix="!"), HasTargetFilter(can_be_same=True), ) -async def get_info_about_user(message: types.Message, chat: Chat, target: User, config: Config, bot: Bot): +async def get_info_about_user( + message: types.Message, chat: Chat, target: User, config: Config, bot: Bot +): info = await get_user_info(target, chat, config.date_format) target_karma = await target.get_karma(chat) if target_karma is None: target_karma = "пока не имеет кармы" - information = f"Данные на {target.mention_link} ({target_karma}):\n" + "\n".join(info) + information = f"Данные на {target.mention_link} ({target_karma}):\n" + "\n".join( + info + ) try: await bot.send_message( - message.from_user.id, - information, - disable_web_page_preview=True + message.from_user.id, information, disable_web_page_preview=True ) except TelegramUnauthorizedError: me = await bot.me() await message.reply( - f'{message.from_user.mention_html()}, напишите мне в личку ' + f"{message.from_user.mention_html()}, напишите мне в личку " f'/start и повторите команду.' ) finally: @@ -262,10 +301,11 @@ async def cmd_unhandled(message: types.Message): @router.callback_query( - kb.WarnCancelCb.filter(), - MagicData(F.user.tg_id == F.callback_data.user_id) + kb.WarnCancelCb.filter(), MagicData(F.user.tg_id == F.callback_data.user_id) ) -async def cancel_warn(callback_query: types.CallbackQuery, callback_data: kb.WarnCancelCb): +async def cancel_warn( + callback_query: types.CallbackQuery, callback_data: kb.WarnCancelCb +): from_user = callback_query.from_user await delete_moderator_event(callback_data.moderator_event_id, moderator=from_user) @@ -284,26 +324,26 @@ async def approve_report_handler( chat: Chat, bot: Bot, config: Config, - chat_settings: ChatSettings + chat_settings: ChatSettings, ): async with in_transaction() as db_session: await resolve_report( report_id=callback_data.report_id, resolved_by=user, resolution=ReportStatus.APPROVED, - db_session=db_session + db_session=db_session, ) if chat_settings.karma_counting and config.report_karma_award: karma_change_result = await reward_reporter( reporter_id=callback_data.reporter_id, chat=chat, reward_amount=config.report_karma_award, - bot=bot + bot=bot, ) await callback_query.message.edit_text( "{reporter} получил +{reward_amount} кармы в награду за репорт!".format( reporter=hd.quote(karma_change_result.karma_event.user_to.fullname), - reward_amount=config.report_karma_award + reward_amount=config.report_karma_award, ) ) delete_bot_reply = False @@ -311,44 +351,40 @@ async def approve_report_handler( delete_bot_reply = True await callback_query.answer("Вы подтвердили репорт", show_alert=delete_bot_reply) - await cleanup_command_dialog(message=callback_query.message, delete_bot_reply=delete_bot_reply) + await cleanup_command_dialog( + message=callback_query.message, delete_bot_reply=delete_bot_reply + ) @router.callback_query( - kb.DeclineReportCb.filter(), - HasPermissions(can_restrict_members=True) + kb.DeclineReportCb.filter(), HasPermissions(can_restrict_members=True) ) async def decline_report_handler( - callback_query: types.CallbackQuery, - callback_data: kb.DeclineReportCb, - user: User + callback_query: types.CallbackQuery, callback_data: kb.DeclineReportCb, user: User ): async with in_transaction() as db_session: await resolve_report( report_id=callback_data.report_id, resolved_by=user, resolution=ReportStatus.DECLINED, - db_session=db_session + db_session=db_session, ) await callback_query.answer("Вы отклонили репорт", show_alert=True) await cleanup_command_dialog(message=callback_query.message, delete_bot_reply=True) @router.callback_query( - kb.CancelReportCb.filter(), - MagicData(F.user.id == F.callback_data.reporter_id) + kb.CancelReportCb.filter(), MagicData(F.user.id == F.callback_data.reporter_id) ) async def cancel_report_handler( - callback_query: types.CallbackQuery, - callback_data: kb.CancelReportCb, - user: User + callback_query: types.CallbackQuery, callback_data: kb.CancelReportCb, user: User ): async with in_transaction() as db_session: await resolve_report( report_id=callback_data.report_id, resolved_by=user, resolution=ReportStatus.CANCELLED, - db_session=db_session + db_session=db_session, ) await callback_query.answer("Вы отменили репорт", show_alert=True) await cleanup_command_dialog(message=callback_query.message, delete_bot_reply=True) @@ -356,16 +392,20 @@ async def cancel_report_handler( @router.callback_query( kb.WarnCancelCb.filter(), - MagicData(F.callback_data.user_id != F.callback_query.from_user.id) + MagicData(F.callback_data.user_id != F.callback_query.from_user.id), +) +@router.callback_query( + kb.CancelReportCb.filter(), MagicData(F.user.id != F.callback_data.reporter_id) ) @router.callback_query( - kb.CancelReportCb.filter(), - MagicData(F.user.id != F.callback_data.reporter_id) + kb.ApproveReportCb.filter(), ~HasPermissions(can_restrict_members=True) ) -@router.callback_query(kb.ApproveReportCb.filter(), ~HasPermissions(can_restrict_members=True)) -@router.callback_query(kb.DeclineReportCb.filter(), ~HasPermissions(can_restrict_members=True)) -async def unauthorized_button_action(callback_query: types.CallbackQuery, config: Config): +@router.callback_query( + kb.DeclineReportCb.filter(), ~HasPermissions(can_restrict_members=True) +) +async def unauthorized_button_action( + callback_query: types.CallbackQuery, config: Config +): await callback_query.answer( - "Эта кнопка не для Вас", - cache_time=config.callback_query_answer_cache_time + "Эта кнопка не для Вас", cache_time=config.callback_query_answer_cache_time ) diff --git a/app/handlers/settings.py b/app/handlers/settings.py index 55720ad4..2f7c63ab 100644 --- a/app/handlers/settings.py +++ b/app/handlers/settings.py @@ -1,11 +1,15 @@ -from aiogram import types, Router +from aiogram import Router, types from aiogram.filters import Command from app.filters import HasPermissions from app.infrastructure.database.models import Chat -from app.services.settings import enable_karmic_restriction, disable_karmic_restriction, get_settings_card, \ - enable_karma_counting, disable_karma_counting - +from app.services.settings import ( + disable_karma_counting, + disable_karmic_restriction, + enable_karma_counting, + enable_karmic_restriction, + get_settings_card, +) router = Router(name=__name__) @@ -17,7 +21,7 @@ async def get_settings(message: types.Message, chat: Chat): @router.message( - Command("enable_karmic_ro", prefix='!/'), + Command("enable_karmic_ro", prefix="!/"), HasPermissions(can_restrict_members=True), ) async def enable_karmic_ro_cmd(message: types.Message, chat: Chat): @@ -34,7 +38,7 @@ async def enable_karmic_ro_cmd(message: types.Message, chat: Chat): @router.message( - Command("disable_karmic_ro", prefix='!/'), + Command("disable_karmic_ro", prefix="!/"), HasPermissions(can_restrict_members=True), ) async def disable_karmic_ro_cmd(message: types.Message, chat: Chat): @@ -43,22 +47,18 @@ async def disable_karmic_ro_cmd(message: types.Message, chat: Chat): @router.message( - Command("enable_karma", prefix='!/'), + Command("enable_karma", prefix="!/"), HasPermissions(can_delete_messages=True), ) async def enable_karma(message: types.Message, chat: Chat): await enable_karma_counting(chat) - await message.reply( - "Включён подсчёт кармы" - ) + await message.reply("Включён подсчёт кармы") @router.message( - Command("disable_karma", prefix='!/'), + Command("disable_karma", prefix="!/"), HasPermissions(can_delete_messages=True), ) async def disable_karma(message: types.Message, chat: Chat): await disable_karma_counting(chat) - await message.reply( - "Выключен подсчёт кармы" - ) + await message.reply("Выключен подсчёт кармы") diff --git a/app/handlers/superuser.py b/app/handlers/superuser.py index 993cf1a8..ee86debb 100644 --- a/app/handlers/superuser.py +++ b/app/handlers/superuser.py @@ -1,9 +1,9 @@ +from functools import partial from typing import Iterable from aiogram import Bot, Router from aiogram.filters import Command -from aiogram.types import Message, BufferedInputFile -from functools import partial +from aiogram.types import BufferedInputFile, Message from app.models.config import Config @@ -21,8 +21,10 @@ async def leave_chat(message: Message, bot: Bot): async def get_dump(_: Message, config: Config, bot: Bot): - with open(config.db.db_path, 'rb') as f: - await bot.send_document(config.dump_chat_id, BufferedInputFile(f.read(), "karma.db")) + with open(config.db.db_path, "rb") as f: + await bot.send_document( + config.dump_chat_id, BufferedInputFile(f.read(), "karma.db") + ) async def show_tagged_users(message: Message): @@ -43,6 +45,8 @@ def setup_superuser(bot_config: Config) -> Router: router.message.register(exception, Command(commands="exception")) router.message.register(leave_chat, Command(commands="get_out")) router.message.register(get_dump, Command(commands="dump")) - router.message.register(show_tagged_users, Command(commands="entities", prefix="!/")) + router.message.register( + show_tagged_users, Command(commands="entities", prefix="!/") + ) return router diff --git a/app/infrastructure/database/models/__init__.py b/app/infrastructure/database/models/__init__.py index 12fe45be..affac504 100644 --- a/app/infrastructure/database/models/__init__.py +++ b/app/infrastructure/database/models/__init__.py @@ -6,4 +6,14 @@ from app.infrastructure.database.models.user import User from app.infrastructure.database.models.user_karma import UserKarma -__all__ = [Chat, ChatType, User, UserKarma, KarmaEvent, ModeratorEvent, ChatSettings, Report, ReportStatus] +__all__ = [ + Chat, + ChatType, + User, + UserKarma, + KarmaEvent, + ModeratorEvent, + ChatSettings, + Report, + ReportStatus, +] diff --git a/app/infrastructure/database/models/chat.py b/app/infrastructure/database/models/chat.py index 0f164270..82276535 100644 --- a/app/infrastructure/database/models/chat.py +++ b/app/infrastructure/database/models/chat.py @@ -7,21 +7,20 @@ from tortoise.models import Model from tortoise.transactions import in_transaction -from app.utils.exceptions import NotHaveNeighbours from app.models.db.db import karma_filters - +from app.utils.exceptions import NotHaveNeighbours SQL_PREV_NEXT = """ SELECT prev_user_id, next_user_id -FROM (SELECT +FROM (SELECT user_id, LAG(user_id) OVER(ORDER BY karma) prev_user_id, LEAD(user_id) OVER(ORDER BY karma) next_user_id FROM user_karma WHERE chat_id = ?) -WHERE user_id = ? +WHERE user_id = ? """ @@ -29,7 +28,7 @@ class ChatType(str, Enum): private = "private" group = "group" supergroup = "supergroup" - channel = 'channel' + channel = "channel" class Chat(Model): @@ -39,9 +38,9 @@ class Chat(Model): username = fields.CharField(max_length=32, null=True) description = fields.CharField(max_length=255, null=True) # noinspection PyUnresolvedReferences - user_karma: fields.ReverseRelation['UserKarma'] # noqa: F821 + user_karma: fields.ReverseRelation["UserKarma"] # noqa: F821 # noinspection PyUnresolvedReferences - settings: fields.ReverseRelation['ChatSettings'] # noqa: F821 + settings: fields.ReverseRelation["ChatSettings"] # noqa: F821 class Meta: table = "chats" @@ -49,10 +48,7 @@ class Meta: @classmethod async def create_from_tg_chat(cls, chat): chat = await cls.create( - chat_id=chat.id, - type_=chat.type, - title=chat.title, - username=chat.username + chat_id=chat.id, type_=chat.type, title=chat.title, username=chat.username ) return chat @@ -66,10 +62,16 @@ async def get_or_create_from_tg_chat(cls, chat): @property def mention(self): - return hd.link(hd.quote(self.title), f"t.me/{self.username}") if self.username else hd.quote(self.title) + return ( + hd.link(hd.quote(self.title), f"t.me/{self.username}") + if self.username + else hd.quote(self.title) + ) def __str__(self): - rez = f"Chat with type: {self.type_} with ID {self.chat_id}, title: {self.title}" + rez = ( + f"Chat with type: {self.type_} with ID {self.chat_id}, title: {self.title}" + ) if self.username: rez += f" Username @{self.username}" if self.description: @@ -81,8 +83,13 @@ def __repr__(self): # noinspection PyUnresolvedReferences async def get_top_karma_list(self, limit: int = 15): - await self.fetch_related('user_karma') - users_karmas = await self.user_karma.order_by(*karma_filters).limit(limit).prefetch_related("user").all() + await self.fetch_related("user_karma") + users_karmas = ( + await self.user_karma.order_by(*karma_filters) + .limit(limit) + .prefetch_related("user") + .all() + ) rez = [] for user_karma in users_karmas: user = user_karma.user @@ -92,13 +99,20 @@ async def get_top_karma_list(self, limit: int = 15): return rez # noinspection PyUnresolvedReferences - async def get_neighbours(self, user) -> tuple['UserKarma', 'UserKarma', 'UserKarma']: # noqa: F821 + async def get_neighbours( + self, user + ) -> tuple["UserKarma", "UserKarma", "UserKarma"]: # noqa: F821 prev_id, next_id = await get_neighbours_id(self.chat_id, user.id) - uk = await self.user_karma.filter( - user_id__in=(prev_id, next_id) - ).prefetch_related("user").order_by(*karma_filters).all() + uk = ( + await self.user_karma.filter(user_id__in=(prev_id, next_id)) + .prefetch_related("user") + .order_by(*karma_filters) + .all() + ) - user_uk = await self.user_karma.filter(user=user).prefetch_related("user").first() + user_uk = ( + await self.user_karma.filter(user=user).prefetch_related("user").first() + ) return uk[0], user_uk, uk[1] @@ -110,7 +124,7 @@ async def get_neighbours_id(chat_id, user_id) -> typing.Tuple[int, int]: except IndexError: raise NotHaveNeighbours try: - rez = int(rez['prev_user_id']), int(rez['next_user_id']) + rez = int(rez["prev_user_id"]), int(rez["next_user_id"]) except TypeError: raise NotHaveNeighbours return rez diff --git a/app/infrastructure/database/models/chat_settings.py b/app/infrastructure/database/models/chat_settings.py index a21aa930..0982ea27 100644 --- a/app/infrastructure/database/models/chat_settings.py +++ b/app/infrastructure/database/models/chat_settings.py @@ -8,23 +8,25 @@ class ChatSettings(Model): """ information about (karma) (user) in (chat) """ + id = fields.IntField(pk=True) chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( - 'models.Chat', related_name='settings') + "models.Chat", related_name="settings" + ) karmic_restrictions: bool = fields.BooleanField(default=False) karma_counting: bool = fields.BooleanField(null=False, default=True) class Meta: - table = 'chat_settings' + table = "chat_settings" def __str__(self): # noinspection PyUnresolvedReferences rez = ( - f'Settings {self.id} of chat: {self.chat_id} ' - f'enable karma {self.karma_counting}' - f'enable karmic restrictions {self.karmic_restrictions}' + f"Settings {self.id} of chat: {self.chat_id} " + f"enable karma {self.karma_counting}" + f"enable karmic restrictions {self.karmic_restrictions}" ) return rez def __repr__(self): - return f'' + return f"" diff --git a/app/infrastructure/database/models/karma_actions.py b/app/infrastructure/database/models/karma_actions.py index e9aabf5d..78eb9fdd 100644 --- a/app/infrastructure/database/models/karma_actions.py +++ b/app/infrastructure/database/models/karma_actions.py @@ -9,23 +9,26 @@ class KarmaEvent(Model): id_ = fields.IntField(pk=True, source_field="id") user_from: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( - 'models.User', related_name='i_change_karma_events') + "models.User", related_name="i_change_karma_events" + ) user_to: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( - 'models.User', related_name='my_karma_events') + "models.User", related_name="my_karma_events" + ) chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( - 'models.Chat', related_name='karma_events') + "models.Chat", related_name="karma_events" + ) date = fields.DatetimeField(auto_now=True, null=False) how_change = fields.FloatField( description="how match change karma in percent of possible power" ) how_change_absolute = fields.FloatField( description="how match user_from change karma user_to in absolute", - source_field="how_match_change" + source_field="how_match_change", ) comment = fields.TextField(null=True) class Meta: - table = 'karma_events' + table = "karma_events" def __repr__(self): return ( @@ -35,10 +38,13 @@ def __repr__(self): @classmethod async def get_last_by_user(cls, user: User, chat: Chat, limit: int = 10): - return await cls.filter( - user_to=user, - chat=chat - ).order_by('-date').limit(limit).prefetch_related("user_from").all() + return ( + await cls.filter(user_to=user, chat=chat) + .order_by("-date") + .limit(limit) + .prefetch_related("user_from") + .all() + ) def format_event(self, date_format: str): rez = ( diff --git a/app/infrastructure/database/models/moderator_actions.py b/app/infrastructure/database/models/moderator_actions.py index a8067dd9..9c066a89 100644 --- a/app/infrastructure/database/models/moderator_actions.py +++ b/app/infrastructure/database/models/moderator_actions.py @@ -4,45 +4,49 @@ from tortoise import fields from tortoise.models import Model -from app.utils.timedelta_functions import format_timedelta -from app.models.common import TypeRestriction from app.infrastructure.database.models.chat import Chat from app.infrastructure.database.models.user import User +from app.models.common import TypeRestriction +from app.utils.timedelta_functions import format_timedelta class ModeratorEvent(Model): id_ = fields.IntField(pk=True, source_field="id") moderator: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( - 'models.User', related_name='my_moderator_events') + "models.User", related_name="my_moderator_events" + ) user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( - 'models.User', related_name='my_restriction_events') + "models.User", related_name="my_restriction_events" + ) chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( - 'models.Chat', related_name='moderator_events') + "models.Chat", related_name="moderator_events" + ) date = fields.DatetimeField(auto_now=True, null=False) type_restriction = fields.CharField(max_length=20) timedelta_restriction = fields.TimeDeltaField(null=True) comment = fields.TextField(null=True) class Meta: - table = 'moderator_events' + table = "moderator_events" def __repr__(self): # noinspection PyUnresolvedReferences return ( - f"ModeratorEvent {self.id_} from moderator {self.moderator_id} to {self.user_id}, date {self.date}, " - f"type_restriction {self.type_restriction} timedelta_restriction {self.timedelta_restriction}" + f"ModeratorEvent {self.id_} from moderator {self.moderator_id} " + f"to {self.user_id}, date {self.date}, type_restriction {self.type_restriction} " + f"timedelta_restriction {self.timedelta_restriction}" ) @classmethod async def save_new_action( - cls, - moderator: User, - user: User, - chat: Chat, - type_restriction: str, - duration: timedelta = None, - comment: str = "", - using_db=None, + cls, + moderator: User, + user: User, + chat: Chat, + type_restriction: str, + duration: timedelta = None, + comment: str = "", + using_db=None, ): moderator_event = ModeratorEvent( moderator=moderator, @@ -50,21 +54,26 @@ async def save_new_action( chat=chat, type_restriction=type_restriction, timedelta_restriction=duration, - comment=comment[:200] + comment=comment[:200], ) await moderator_event.save(using_db=using_db) return moderator_event @classmethod async def get_last_by_user(cls, user: User, chat: Chat, limit: int = 10): - return await cls.filter( - user=user, - chat=chat - ).order_by('-date').limit(limit).prefetch_related('moderator').all() + return ( + await cls.filter(user=user, chat=chat) + .order_by("-date") + .limit(limit) + .prefetch_related("moderator") + .all() + ) def format_event(self, date_format: str) -> str: - rez = f"{self.date.date().strftime(date_format)} " \ - f"{TypeRestriction[self.type_restriction].get_emoji()}{self.type_restriction} " + rez = ( + f"{self.date.date().strftime(date_format)} " + f"{TypeRestriction[self.type_restriction].get_emoji()}{self.type_restriction} " + ) if self.timedelta_restriction: rez += f"{format_timedelta(self.timedelta_restriction)} " @@ -72,5 +81,5 @@ def format_event(self, date_format: str) -> str: rez += f"от {self.moderator.mention_no_link}" if self.comment: - rez += f" \"{hd.quote(self.comment)}\"" + rez += f' "{hd.quote(self.comment)}"' return rez diff --git a/app/infrastructure/database/models/report.py b/app/infrastructure/database/models/report.py index 4866e924..1d38f7d4 100644 --- a/app/infrastructure/database/models/report.py +++ b/app/infrastructure/database/models/report.py @@ -19,27 +19,24 @@ class ReportStatus(Enum): class Report(Model): id = fields.IntField(pk=True) reporter: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( - 'models.User', - related_name='made_reports' + "models.User", related_name="made_reports" ) reported_user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( - 'models.User', - related_name='got_reports' + "models.User", related_name="got_reports" ) chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( - 'models.Chat', - related_name='reports' + "models.Chat", related_name="reports" ) created_time = fields.DatetimeField(auto_now=True, null=False) resolution_time = fields.DatetimeField(null=True) reported_message_id = fields.BigIntField(generated=False, null=False) - reported_message_content = fields.CharField(null=False, max_length=TG_MESSAGE_MAX_LEN) + reported_message_content = fields.CharField( + null=False, max_length=TG_MESSAGE_MAX_LEN + ) resolved_by: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( - 'models.User', - related_name='resolved_reports', - null=True + "models.User", related_name="resolved_reports", null=True ) status = fields.CharEnumField(ReportStatus, null=False) class Meta: - table = 'reports' + table = "reports" diff --git a/app/infrastructure/database/models/user.py b/app/infrastructure/database/models/user.py index dca39cd4..09314ccd 100644 --- a/app/infrastructure/database/models/user.py +++ b/app/infrastructure/database/models/user.py @@ -7,14 +7,14 @@ from tortoise.exceptions import DoesNotExist from tortoise.models import Model -from app.models.common import TypeRestriction -from app.utils.exceptions import UserWithoutUserIdError from app.infrastructure.database.models.chat import Chat from app.models import dto +from app.models.common import TypeRestriction +from app.utils.exceptions import UserWithoutUserIdError if TYPE_CHECKING: - from app.infrastructure.database.models.user_karma import UserKarma from app.infrastructure.database.models.moderator_actions import ModeratorEvent + from app.infrastructure.database.models.user_karma import UserKarma class User(Model): @@ -37,13 +37,14 @@ async def create_from_tg_user(cls, user: types.User): first_name=user.first_name, last_name=user.last_name, username=user.username, - is_bot=user.is_bot + is_bot=user.is_bot, ) return user async def update_user_data(self, user_tg): - # TODO изучить фреймворк лучше - уверен есть встроенная функция для обновления только в случае расхождений + # TODO изучить фреймворк лучше - уверен есть встроенная функция для + # обновления только в случае расхождений changed = False if self.tg_id is None and user_tg.id is not None: @@ -100,7 +101,7 @@ def mention_no_link(self): @property def fullname(self): if self.last_name is not None: - return ' '.join((self.first_name, self.last_name)) + return " ".join((self.first_name, self.last_name)) return self.first_name or self.username or str(self.tg_id) or str(self.id) async def get_uk(self, chat: Chat) -> "UserKarma": @@ -123,12 +124,14 @@ async def get_number_in_top_karma(self, chat: Chat) -> int: async def has_now_ro_db(self, chat: Chat): my_restrictions = await self.my_restriction_events.filter( - chat=chat, - type_restriction=TypeRestriction.ro.name + chat=chat, type_restriction=TypeRestriction.ro.name ).all() for my_restriction in my_restrictions: - if my_restriction.timedelta_restriction \ - and my_restriction.date + my_restriction.timedelta_restriction > datetime.now(): + if ( + my_restriction.timedelta_restriction + and my_restriction.date + my_restriction.timedelta_restriction + > datetime.now() + ): return True return False @@ -139,7 +142,7 @@ def to_json(self): first_name=self.first_name, last_name=self.last_name, username=self.username, - is_bot=self.is_bot + is_bot=self.is_bot, ) def __str__(self): diff --git a/app/infrastructure/database/models/user_karma.py b/app/infrastructure/database/models/user_karma.py index 99e6ecb2..ef8ebf0b 100644 --- a/app/infrastructure/database/models/user_karma.py +++ b/app/infrastructure/database/models/user_karma.py @@ -18,31 +18,47 @@ class UserKarma(Model): """ information about (karma) (user) in (chat) """ + uc_id = fields.IntField(pk=True) - user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField('models.User', related_name='karma') - chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField('models.Chat', related_name='user_karma') + user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField( + "models.User", related_name="karma" + ) + chat: fields.ForeignKeyRelation[Chat] = fields.ForeignKeyField( + "models.Chat", related_name="user_karma" + ) karma = fields.FloatField(default=DEFAULT_KARMA) class Meta: - table = 'user_karma' - unique_together = ('user', 'chat') + table = "user_karma" + unique_together = ("user", "chat") def __str__(self): # noinspection PyUnresolvedReferences - rez = f'UserKarma: id{self.uc_id}, karma: {self.karma}, user_id {self.user_id}, chat_id {self.chat_id}' + rez = ( + f"UserKarma: id{self.uc_id}, karma: {self.karma}, " + f"user_id {self.user_id}, chat_id {self.chat_id}" + ) return rez def __repr__(self): return str(self) - async def change(self, user_changed: User, how_change: float, skip_power_check: bool, using_db=None): + async def change( + self, + user_changed: User, + how_change: float, + skip_power_check: bool, + using_db=None, + ): """ change karma to (self.user) from (user_changed) (how_change) must be from -inf to +inf """ if how_change == 0: - raise ValueError(f"how_change must be float and not 0 but it is {how_change}") - await self.fetch_related('chat', using_db=using_db) + raise ValueError( + f"how_change must be float and not 0 but it is {how_change}" + ) + await self.fetch_related("chat", using_db=using_db) if skip_power_check: power = INF @@ -50,11 +66,14 @@ async def change(self, user_changed: User, how_change: float, skip_power_check: power = await self.get_power(user_changed, self.chat) if power < 0.01: - logger.info("user {user} try to change karma but have negative karma", user=user_changed.tg_id) + logger.info( + "user {user} try to change karma but have negative karma", + user=user_changed.tg_id, + ) raise SubZeroKarma( "User have too small karma", user_id=user_changed.id, - chat_id=self.chat.chat_id + chat_id=self.chat.chat_id, ) change_sign = +1 if how_change > 0 else -1 abs_how_change = min(abs(how_change), power) @@ -65,13 +84,13 @@ async def change(self, user_changed: User, how_change: float, skip_power_check: @classmethod async def change_or_create( - cls, - target_user: User, - chat: Chat, - user_changed: User, - how_change: float, - skip_power_check: bool, - using_db=None, + cls, + target_user: User, + chat: Chat, + user_changed: User, + how_change: float, + skip_power_check: bool, + using_db=None, ): """ change karma to (target_user) from (user_changed) in (chat) @@ -112,12 +131,12 @@ async def number_in_top(self) -> int: @classmethod async def all_to_json(cls, chat_id: int = None) -> dict: if chat_id is not None: - uks = await cls.filter(chat_id=chat_id).prefetch_related("user").order_by(*karma_filters) - return { - chat_id: [ - {**uk.user.to_json(), "karma": uk.karma} for uk in uks - ] - } + uks = ( + await cls.filter(chat_id=chat_id) + .prefetch_related("user") + .order_by(*karma_filters) + ) + return {chat_id: [{**uk.user.to_json(), "karma": uk.karma} for uk in uks]} else: all_data = {} for chat in await Chat.all(): diff --git a/app/middlewares/db_middleware.py b/app/middlewares/db_middleware.py index b7e1e91c..1c6fdadd 100644 --- a/app/middlewares/db_middleware.py +++ b/app/middlewares/db_middleware.py @@ -1,8 +1,8 @@ # partially from https://github.com/aiogram/bot -from typing import Optional, Callable, Dict, Any, Awaitable +from typing import Any, Awaitable, Callable, Dict, Optional -from aiogram import types, BaseMiddleware +from aiogram import BaseMiddleware, types from aiogram.dispatcher.event.bases import CancelHandler from aiogram.types import TelegramObject @@ -11,7 +11,6 @@ from app.utils.lock_factory import LockFactory from app.utils.log import Logger - logger = Logger(__name__) @@ -24,7 +23,7 @@ async def __call__( self, handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], event: TelegramObject, - data: Dict[str, Any] + data: Dict[str, Any], ) -> Any: chat: types.Chat = data.get("event_chat", None) user: types.User = data.get("event_from_user", None) @@ -33,11 +32,13 @@ async def __call__( await self.setup_chat(data, user, chat) return await handler(event, data) - async def setup_chat(self, data: dict, user: types.User, chat: Optional[types.Chat] = None): + async def setup_chat( + self, data: dict, user: types.User, chat: Optional[types.Chat] = None + ): try: async with self.lock_factory.get_lock(user.id): user = await User.get_or_create_from_tg_user(user) - if chat and chat.type != 'private': + if chat and chat.type != "private": async with self.lock_factory.get_lock(chat.id): chat = await Chat.get_or_create_from_tg_chat(chat) data["chat_settings"] = await get_chat_settings(chat=chat) diff --git a/app/models/config/auto_restriction.py b/app/models/config/auto_restriction.py index e56c68f7..e9238da9 100644 --- a/app/models/config/auto_restriction.py +++ b/app/models/config/auto_restriction.py @@ -35,7 +35,7 @@ def it_was_last_restriction(self, count: int) -> bool: def next_will_be_last_restriction(self, count: int) -> bool: return self.plan.next_will_be_last_restriction(count) - def render_auto_restriction(self, user: 'User', count_auto_restrict: int): + def render_auto_restriction(self, user: "User", count_auto_restrict: int): if count_auto_restrict <= 0: return "" text = "{target}, Уровень вашей кармы стал ниже {negative_limit}.\n".format( @@ -50,14 +50,18 @@ def render_auto_restriction(self, user: 'User', count_auto_restrict: int): "Вам установлена карма {karma_after}. " "Если Ваша карма снова достигнет {threshold} " "Ваше наказание будет строже.".format( - duration=self.plan.get_early_restriction_printable_duration(count_auto_restrict), + duration=self.plan.get_early_restriction_printable_duration( + count_auto_restrict + ), karma_after=self.after_restriction_karma, threshold=self.threshold, ) ) return text - def render_negative_karma_notification(self, user: 'User', count_auto_restrict: int): + def render_negative_karma_notification( + self, user: "User", count_auto_restrict: int + ): if self.next_will_be_last_restriction(count_auto_restrict): template = ( "Внимание {username}!\nУ Вас отрицательная карма. " @@ -76,5 +80,7 @@ def render_negative_karma_notification(self, user: 'User', count_auto_restrict: return template.format( username=user.mention_link, threshold=self.threshold, - duration=self.plan.get_next_restriction_printable_duration(count_auto_restrict), + duration=self.plan.get_next_restriction_printable_duration( + count_auto_restrict + ), ) diff --git a/app/models/db/db.py b/app/models/db/db.py index 54ea2e61..cc3d23bd 100644 --- a/app/models/db/db.py +++ b/app/models/db/db.py @@ -3,19 +3,15 @@ from app.models.config import DBConfig from app.utils.log import Logger - logger = Logger(__name__) -__models__ = ['app.infrastructure.database.models'] +__models__ = ["app.infrastructure.database.models"] karma_filters = ("-karma", "uc_id") async def db_init(db_config: DBConfig): db_url = db_config.create_url_config() logger.info("connecting to db {db_url}", db_url=db_url) - await Tortoise.init( - db_url=db_url, - modules={'models': __models__} - ) + await Tortoise.init(db_url=db_url, modules={"models": __models__}) async def on_shutdown(): diff --git a/app/services/adaptive_trottle.py b/app/services/adaptive_trottle.py index f713454b..9344c467 100644 --- a/app/services/adaptive_trottle.py +++ b/app/services/adaptive_trottle.py @@ -5,11 +5,10 @@ from aiogram import types -from app.infrastructure.database.models import User, Chat +from app.infrastructure.database.models import Chat, User from app.utils.exceptions import Throttled from app.utils.log import Logger - logger = Logger(__name__) DEFAULT_RATE = 1 @@ -19,10 +18,10 @@ def __init__(self): self.last_calls = {} def throttled( - self, - rate: typing.Union[float, int] = DEFAULT_RATE, - key: str = None, - on_throttled: typing.Optional[typing.Callable] = None + self, + rate: typing.Union[float, int] = DEFAULT_RATE, + key: str = None, + on_throttled: typing.Optional[typing.Callable] = None, ): rate = timedelta(seconds=rate) @@ -31,32 +30,45 @@ def decorator(func): @functools.wraps(func) async def wrapped(*args, **kwargs): - chat: Chat = kwargs['chat'] - user: User = kwargs['user'] - target: User = kwargs['target'] + chat: Chat = kwargs["chat"] + user: User = kwargs["user"] + target: User = kwargs["target"] message: types.Message = args[0] - if self.check_time_throttle(message.date, rate, current_key, chat.chat_id, user.tg_id, target.tg_id): + if self.check_time_throttle( + message.date, + rate, + current_key, + chat.chat_id, + user.tg_id, + target.tg_id, + ): return await func(*args, **kwargs) else: - await process_on_throttled(on_throttled, current_key, rate, *args, **kwargs) + await process_on_throttled( + on_throttled, current_key, rate, *args, **kwargs + ) return wrapped return decorator def check_time_throttle( - self, - call_at: datetime, - rate: int, - key: str, - chat_id: int, - user_id: int, - target_user_id: int + self, + call_at: datetime, + rate: int, + key: str, + chat_id: int, + user_id: int, + target_user_id: int, ): logger.debug( - "check throttle time for chat {chat_id}, user {user_id}, target {target_user_id} and key {key}", - chat_id=chat_id, user_id=user_id, target_user_id=target_user_id, key=key, + "check throttle time for chat {chat_id}, user {user_id}, " + "target {target_user_id} and key {key}", + chat_id=chat_id, + user_id=user_id, + target_user_id=target_user_id, + key=key, ) bucket = self.get_bucket(chat_id, user_id, target_user_id) @@ -64,7 +76,9 @@ def check_time_throttle( try: last = bucket[key] except KeyError: - logger.debug("there is no one last call", ) + logger.debug( + "there is no one last call", + ) return True finally: bucket[key] = call_at @@ -74,18 +88,24 @@ def check_time_throttle( return call_at - last > rate def get_bucket(self, chat_id: int, user_id: int, target_user_id: int): - return self.last_calls.setdefault(chat_id, {}).setdefault(user_id, {}).setdefault(target_user_id, {}) + return ( + self.last_calls.setdefault(chat_id, {}) + .setdefault(user_id, {}) + .setdefault(target_user_id, {}) + ) def set_bucket(self, chat_id: int, user_id: int, target_user_id: int, bucket: dict): - self.last_calls.setdefault(chat_id, {}).setdefault(user_id, {})[target_user_id] = bucket + self.last_calls.setdefault(chat_id, {}).setdefault(user_id, {})[ + target_user_id + ] = bucket async def process_on_throttled( - on_throttled: typing.Callable, - key: str, - rate: typing.Union[float, int], - *args, - **kwargs + on_throttled: typing.Callable, + key: str, + rate: typing.Union[float, int], + *args, + **kwargs ): if on_throttled: if asyncio.iscoroutinefunction(on_throttled): @@ -93,8 +113,8 @@ async def process_on_throttled( else: on_throttled(*args, **kwargs) else: - chat: Chat = kwargs['chat'] - user: User = kwargs['user'] + chat: Chat = kwargs["chat"] + user: User = kwargs["user"] raise Throttled( key=key, rate=rate, diff --git a/app/services/change_karma.py b/app/services/change_karma.py index 177c0e77..7fdf5bce 100644 --- a/app/services/change_karma.py +++ b/app/services/change_karma.py @@ -3,21 +3,24 @@ from tortoise.transactions import in_transaction from app.config.main import load_config -from app.models.common import TypeRestriction from app.infrastructure.database.models import ( - User, Chat, - UserKarma, KarmaEvent, - ModeratorEvent + ModeratorEvent, + User, + UserKarma, +) +from app.models.common import TypeRestriction +from app.services.moderation import ( + auto_restrict, + get_count_auto_restrict, + user_has_now_ro, ) -from app.services.moderation import auto_restrict, user_has_now_ro, get_count_auto_restrict from app.services.settings import is_enable_karmic_restriction from app.utils.exceptions import AutoLike, DontOffendRestricted from app.utils.log import Logger from app.utils.types import ResultChangeKarma - logger = Logger(__name__) config = load_config() @@ -27,21 +30,24 @@ def can_change_karma(target_user: User, user: User): async def change_karma( - user: User, - target_user: User, - chat: Chat, - how_change: float, - bot: Bot, - comment: str = "", - is_reward: bool = False + user: User, + target_user: User, + chat: Chat, + how_change: float, + bot: Bot, + comment: str = "", + is_reward: bool = False, ) -> ResultChangeKarma: if not can_change_karma(target_user, user): logger.info("user {user} try to change self or bot karma ", user=user.tg_id) raise AutoLike(user_id=user.tg_id, chat_id=chat.chat_id) if how_change < 0 and await user_has_now_ro(target_user, chat, bot): - logger.info("user {user} try to change karma of another user {target} with RO ", - user=user.tg_id, target=target_user.tg_id) + logger.info( + "user {user} try to change karma of another user {target} with RO ", + user=user.tg_id, + target=target_user.tg_id, + ) raise DontOffendRestricted(user_id=user.tg_id, chat_id=chat.chat_id) async with in_transaction() as conn: @@ -51,7 +57,7 @@ async def change_karma( user_changed=user, how_change=how_change, using_db=conn, - skip_power_check=is_reward + skip_power_check=is_reward, ) ke = KarmaEvent( user_from=user, @@ -66,12 +72,13 @@ async def change_karma( "user {user} change karma of {target_user} in chat {chat}", user=user.tg_id, target_user=target_user.tg_id, - chat=chat.chat_id + chat=chat.chat_id, ) karma_after = uk.karma - if config.auto_restriction.need_restrict(uk.karma) \ - and await is_enable_karmic_restriction(chat): + if config.auto_restriction.need_restrict( + uk.karma + ) and await is_enable_karmic_restriction(chat): count_auto_restrict, moderator_event = await auto_restrict( bot=bot, @@ -83,7 +90,9 @@ async def change_karma( await uk.save(using_db=conn) was_restricted = True else: - count_auto_restrict = await get_count_auto_restrict(target_user, chat, bot=bot) + count_auto_restrict = await get_count_auto_restrict( + target_user, chat, bot=bot + ) moderator_event = None was_restricted = False @@ -98,7 +107,9 @@ async def change_karma( ) -async def cancel_karma_change(karma_event_id: int, rollback_karma: float, moderator_event_id: int, bot: Bot): +async def cancel_karma_change( + karma_event_id: int, rollback_karma: float, moderator_event_id: int, bot: Bot +): async with in_transaction() as conn: karma_event = await KarmaEvent.get(id_=karma_event_id) @@ -111,7 +122,7 @@ async def cancel_karma_change(karma_event_id: int, rollback_karma: float, modera user_karma = await UserKarma.get(chat_id=chat_id, user_id=user_to_id) user_karma.karma = user_karma.karma + rollback_karma - await user_karma.save(update_fields=['karma'], using_db=conn) + await user_karma.save(update_fields=["karma"], using_db=conn) await karma_event.delete(using_db=conn) if moderator_event_id is not None: moderator_event = await ModeratorEvent.get(id_=moderator_event_id) @@ -126,14 +137,19 @@ async def cancel_karma_change(karma_event_id: int, rollback_karma: float, modera can_send_media_messages=True, can_add_web_page_previews=True, can_send_other_messages=True, - ) + ), ) elif moderator_event.type_restriction == TypeRestriction.karmic_ban.name: - await bot.unban_chat_member(chat_id=chat_id, user_id=restricted_user.tg_id, only_if_banned=True) + await bot.unban_chat_member( + chat_id=chat_id, user_id=restricted_user.tg_id, only_if_banned=True + ) await moderator_event.delete(using_db=conn) logger.info( "user {user} cancel change karma to user {target} in chat {chat}", - user=user_from_id, target=user_to_id, chat=chat_id) + user=user_from_id, + target=user_to_id, + chat=chat_id, + ) diff --git a/app/services/find_target_user.py b/app/services/find_target_user.py index 369f8f22..5b6e0aad 100644 --- a/app/services/find_target_user.py +++ b/app/services/find_target_user.py @@ -4,18 +4,19 @@ from pyrogram.errors import UsernameNotOccupied from tortoise.exceptions import MultipleObjectsReturned +from app.infrastructure.database.models import User from app.models import dto from app.models.config import TgClientConfig -from app.infrastructure.database.models import User from app.services.user_getter import UserGetter from app.utils.exceptions import UserWithoutUserIdError from app.utils.log import Logger - logger = Logger(__name__) -def get_target_user(message: types.Message, can_be_same=False, can_be_bot=False) -> dto.TargetUser | None: +def get_target_user( + message: types.Message, can_be_same=False, can_be_bot=False +) -> dto.TargetUser | None: """ Target user can be take from reply or by mention :param message: @@ -62,17 +63,21 @@ def has_target_user( def is_one_user(user_1: dto.TargetUser | None, user_2: dto.TargetUser | None) -> bool: - if all([ - user_1.id is not None, - user_2.id is not None, - user_1.id == user_2.id, - ]): + if all( + [ + user_1.id is not None, + user_2.id is not None, + user_1.id == user_2.id, + ] + ): return True - if all([ - user_1.username is not None, - user_2.username is not None, - user_1.username == user_2.username, - ]): + if all( + [ + user_1.username is not None, + user_2.username is not None, + user_1.username == user_2.username, + ] + ): return True return False @@ -117,13 +122,18 @@ def is_bot_username(username: str) -> bool: return username is not None and username[-3:] == "bot" -async def get_db_user_by_tg_user(target: dto.TargetUser, tg_client_config: TgClientConfig) -> User: +async def get_db_user_by_tg_user( + target: dto.TargetUser, tg_client_config: TgClientConfig +) -> User: exception: Exception try: target_user = await User.get_or_create_from_tg_user(target) except MultipleObjectsReturned as e: - logger.warning("Strange, multiple username? check id={id}, username={username}", - id=target.id, username=target.username) + logger.warning( + "Strange, multiple username? check id={id}, username={username}", + id=target.id, + username=target.username, + ) exception = e # In target can be user with only username except UserWithoutUserIdError as e: diff --git a/app/services/karma.py b/app/services/karma.py index 65ffe980..3589175f 100644 --- a/app/services/karma.py +++ b/app/services/karma.py @@ -8,7 +8,9 @@ async def get_top(chat: Chat, user: User, limit: int = 15): top_karmas = await chat.get_top_karma_list(limit) - text_list = format_output([(i, user, karma) for i, (user, karma) in enumerate(top_karmas, 1)]) + text_list = format_output( + [(i, user, karma) for i, (user, karma) in enumerate(top_karmas, 1)] + ) text = add_caption(text_list) try: prev_uk, user_uk, next_uk = await chat.get_neighbours(user) @@ -20,19 +22,24 @@ async def get_top(chat: Chat, user: User, limit: int = 15): neighbours_karmas = [] if prev_uk.user.id not in user_ids: text = add_separator(text) - neighbours_karmas.append((number_user_in_top - 1, prev_uk.user, prev_uk.karma_round)) + neighbours_karmas.append( + (number_user_in_top - 1, prev_uk.user, prev_uk.karma_round) + ) if user_uk.user.id not in user_ids: - neighbours_karmas.append((number_user_in_top, user_uk.user, user_uk.karma_round)) + neighbours_karmas.append( + (number_user_in_top, user_uk.user, user_uk.karma_round) + ) if next_uk.user.id not in user_ids: - neighbours_karmas.append((number_user_in_top + 1, next_uk.user, next_uk.karma_round)) + neighbours_karmas.append( + (number_user_in_top + 1, next_uk.user, next_uk.karma_round) + ) additional_users = format_output(neighbours_karmas) return text + "\n" + additional_users def format_output(list_karmas: typing.List[typing.Tuple[int, User, float]]) -> str: return "\n".join( - [f"{i} {user.mention_no_link} {hbold(karma)}" - for i, user, karma in list_karmas] + [f"{i} {user.mention_no_link} {hbold(karma)}" for i, user, karma in list_karmas] ) diff --git a/app/services/moderation.py b/app/services/moderation.py index 8a0ed817..e9c8deeb 100644 --- a/app/services/moderation.py +++ b/app/services/moderation.py @@ -1,24 +1,25 @@ from datetime import timedelta from aiogram import Bot -from aiogram.types import ChatMemberRestricted from aiogram.exceptions import TelegramBadRequest +from aiogram.types import ChatMemberRestricted from tortoise.backends.base.client import TransactionContext from app.config import moderation from app.config.main import load_config +from app.infrastructure.database.models import Chat, ModeratorEvent, User from app.models.common import TypeRestriction -from app.infrastructure.database.models import ModeratorEvent, User, Chat from app.utils.exceptions import CantRestrict from app.utils.log import Logger -from app.utils.timedelta_functions import parse_timedelta_from_text, format_timedelta - +from app.utils.timedelta_functions import format_timedelta, parse_timedelta_from_text logger = Logger(__name__) config = load_config() -async def warn_user(moderator: User, target_user: User, chat: Chat, comment: str) -> ModeratorEvent: +async def warn_user( + moderator: User, target_user: User, chat: Chat, comment: str +) -> ModeratorEvent: return await ModeratorEvent.save_new_action( moderator=moderator, user=target_user, @@ -28,7 +29,9 @@ async def warn_user(moderator: User, target_user: User, chat: Chat, comment: str ) -async def ban_user(chat: Chat, target: User, admin: User, duration: timedelta, comment: str, bot: Bot) -> str: +async def ban_user( + chat: Chat, target: User, admin: User, duration: timedelta, comment: str, bot: Bot +) -> str: await restrict( bot=bot, chat=chat, @@ -36,15 +39,21 @@ async def ban_user(chat: Chat, target: User, admin: User, duration: timedelta, c admin=admin, duration=duration, comment=comment, - type_restriction=TypeRestriction.ban + type_restriction=TypeRestriction.ban, + ) + text = "Пользователь {user} попал в бан этого чата.".format( + user=target.mention_link ) - text = "Пользователь {user} попал в бан этого чата.".format(user=target.mention_link) if duration < moderation.FOREVER_RESTRICT_DURATION: - text += " Он сможет вернуться через {duration}".format(duration=format_timedelta(duration)) + text += " Он сможет вернуться через {duration}".format( + duration=format_timedelta(duration) + ) return text -async def ro_user(chat: Chat, target: User, admin: User, duration: timedelta, comment: str, bot: Bot) -> str: +async def ro_user( + chat: Chat, target: User, admin: User, duration: timedelta, comment: str, bot: Bot +) -> str: await restrict( bot=bot, chat=chat, @@ -54,21 +63,24 @@ async def ro_user(chat: Chat, target: User, admin: User, duration: timedelta, co comment=comment, type_restriction=TypeRestriction.ro, ) - return "Пользователь {user} сможет только читать сообщения на протяжении {duration}".format( + return ( + "Пользователь {user} сможет только читать " + "сообщения на протяжении {duration}" + ).format( user=target.mention_link, duration=format_timedelta(duration), ) async def restrict( - bot: Bot, - chat: Chat, - target: User, - admin: User, - duration: timedelta, - comment: str, - type_restriction: TypeRestriction, - using_db=None + bot: Bot, + chat: Chat, + target: User, + admin: User, + duration: timedelta, + comment: str, + type_restriction: TypeRestriction, + using_db=None, ) -> ModeratorEvent: try: # restrict in telegram @@ -80,8 +92,11 @@ async def restrict( ) except TelegramBadRequest as e: raise CantRestrict( - text=e.message, user_id=target.tg_id, chat_id=chat.chat_id, - reason=comment, type_event=type_restriction.name + text=e.message, + user_id=target.tg_id, + chat_id=chat.chat_id, + reason=comment, + type_event=type_restriction.name, ) else: moderator_event = await ModeratorEvent.save_new_action( @@ -105,7 +120,9 @@ async def restrict( def get_moderator_message_args(text: str) -> tuple[str, str]: - _, *args = text.split(maxsplit=2) # in text: command_duration_comments like: "!ro 13d don't flood" + _, *args = text.split( + maxsplit=2 + ) # in text: command_duration_comments like: "!ro 13d don't flood" if not args: return "", "" duration_text = args[0] @@ -125,7 +142,9 @@ def get_duration(text: str) -> tuple[timedelta, str]: async def user_has_now_ro(user: User, chat: Chat, bot: Bot) -> bool: try: - chat_member = await bot.get_chat_member(chat_id=chat.chat_id, user_id=user.tg_id) + chat_member = await bot.get_chat_member( + chat_id=chat.chat_id, user_id=user.tg_id + ) except TelegramBadRequest as e: # TODO #102 probably we need to disable karmic ro for chats with hidden members? if "user not found" in e.message: @@ -138,10 +157,7 @@ async def user_has_now_ro(user: User, chat: Chat, bot: Bot) -> bool: async def auto_restrict( - target: User, - chat: Chat, - bot: Bot, - using_db: TransactionContext | None = None + target: User, chat: Chat, bot: Bot, using_db: TransactionContext | None = None ) -> tuple[int, ModeratorEvent]: """ return count auto restrict @@ -157,7 +173,9 @@ async def auto_restrict( count=count_auto_restrict, ) - current_restriction = config.auto_restriction.get_next_restriction(count_auto_restrict) + current_restriction = config.auto_restriction.get_next_restriction( + count_auto_restrict + ) moderator_event = await restrict( bot=bot, @@ -178,22 +196,31 @@ async def get_count_auto_restrict( bot_user: User | None = None, bot: Bot | None = None, ) -> int: - assert bot is not None or bot_user is not None, "One of bot and bot_user must be not None" + assert ( + bot is not None or bot_user is not None + ), "One of bot and bot_user must be not None" if bot_user is None: bot_user = await User.get_or_create_from_tg_user(await bot.me()) return await ModeratorEvent.filter( - moderator=bot_user, user=target, chat=chat, - type_restriction__in=(TypeRestriction.karmic_ro.name, TypeRestriction.karmic_ban.name), + moderator=bot_user, + user=target, + chat=chat, + type_restriction__in=( + TypeRestriction.karmic_ro.name, + TypeRestriction.karmic_ban.name, + ), ).count() -async def delete_moderator_event(moderator_event_id: int, moderator: User | None = None): +async def delete_moderator_event( + moderator_event_id: int, moderator: User | None = None +): moderator_event = await ModeratorEvent.get(id_=moderator_event_id) logger.info( - '{moderator_event} was deleted {by}', + "{moderator_event} was deleted {by}", moderator_event=repr(moderator_event), - by='' if not moderator else f'by {moderator.username}' + by="" if not moderator else f"by {moderator.username}", ) await moderator_event.delete() diff --git a/app/services/report.py b/app/services/report.py index 63596efa..0f188e90 100644 --- a/app/services/report.py +++ b/app/services/report.py @@ -4,17 +4,17 @@ from aiogram import Bot from tortoise.backends.base.client import BaseDBAsyncClient -from app.infrastructure.database.models import User, Chat, Report, ReportStatus +from app.infrastructure.database.models import Chat, Report, ReportStatus, User from app.services.change_karma import change_karma from app.utils.types import ResultChangeKarma async def register_report( - reporter: User, - reported_user: User, - chat: Chat, - reported_message: aiogram.types.Message, - db_session: BaseDBAsyncClient, + reporter: User, + reported_user: User, + chat: Chat, + reported_message: aiogram.types.Message, + db_session: BaseDBAsyncClient, ) -> Report: report = await Report.create( reporter=reporter, @@ -23,17 +23,16 @@ async def register_report( reported_message_id=reported_message.message_id, reported_message_content=reported_message.html_text, status=ReportStatus.PENDING, - - using_db=db_session + using_db=db_session, ) return report async def resolve_report( - report_id: int, - resolved_by: User, - resolution: ReportStatus, - db_session: BaseDBAsyncClient + report_id: int, + resolved_by: User, + resolution: ReportStatus, + db_session: BaseDBAsyncClient, ): report = await Report.get(id=report_id, using_db=db_session) report.resolved_by = resolved_by @@ -43,10 +42,7 @@ async def resolve_report( async def reward_reporter( - reporter_id: int, - reward_amount: int, - chat: Chat, - bot: Bot + reporter_id: int, reward_amount: int, chat: Chat, bot: Bot ) -> ResultChangeKarma: from_user = await User.get_or_create_from_tg_user(await bot.get_me()) target_user = await User.get(id=reporter_id) @@ -57,5 +53,5 @@ async def reward_reporter( how_change=reward_amount, bot=bot, comment="Report reward", - is_reward=True + is_reward=True, ) diff --git a/app/services/user_info.py b/app/services/user_info.py index ef1f3970..ea91d2cf 100644 --- a/app/services/user_info.py +++ b/app/services/user_info.py @@ -1,9 +1,12 @@ -from app.infrastructure.database.models import User, Chat, ModeratorEvent, KarmaEvent +from app.infrastructure.database.models import Chat, KarmaEvent, ModeratorEvent, User async def get_user_info(user: User, chat: Chat, date_format: str): moderation_events = await ModeratorEvent.get_last_by_user(user, chat) karma_events = await KarmaEvent.get_last_by_user(user, chat) - rez = [(event.date, event.format_event(date_format)) for event in [*moderation_events, *karma_events]] + rez = [ + (event.date, event.format_event(date_format)) + for event in [*moderation_events, *karma_events] + ] rez.sort(key=lambda t: t[0]) return [elem[1] for elem in rez] diff --git a/app/utils/types.py b/app/utils/types.py index e17f8321..94a9b28e 100644 --- a/app/utils/types.py +++ b/app/utils/types.py @@ -1,6 +1,6 @@ from typing import NamedTuple -from app.infrastructure.database.models import UserKarma, KarmaEvent, ModeratorEvent +from app.infrastructure.database.models import KarmaEvent, ModeratorEvent, UserKarma class ResultChangeKarma(NamedTuple): From 9c32d55345036d9610584058dc52c3d06226a013 Mon Sep 17 00:00:00 2001 From: Lamroy95 Date: Fri, 27 Oct 2023 10:49:34 +0400 Subject: [PATCH 4/4] Formatting --- app/services/adaptive_trottle.py | 2 +- app/services/change_karma.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/services/adaptive_trottle.py b/app/services/adaptive_trottle.py index 9344c467..9d61e6e2 100644 --- a/app/services/adaptive_trottle.py +++ b/app/services/adaptive_trottle.py @@ -105,7 +105,7 @@ async def process_on_throttled( key: str, rate: typing.Union[float, int], *args, - **kwargs + **kwargs, ): if on_throttled: if asyncio.iscoroutinefunction(on_throttled): diff --git a/app/services/change_karma.py b/app/services/change_karma.py index 7fdf5bce..57665aae 100644 --- a/app/services/change_karma.py +++ b/app/services/change_karma.py @@ -79,7 +79,6 @@ async def change_karma( if config.auto_restriction.need_restrict( uk.karma ) and await is_enable_karmic_restriction(chat): - count_auto_restrict, moderator_event = await auto_restrict( bot=bot, chat=chat,