Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restrict on negative karma #72

Merged
merged 28 commits into from
Nov 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1089dda
add transaction to change_karma services
Nov 9, 2020
45de209
split ban and ro to handler part and service part
Nov 9, 2020
10a2dcf
fixes, separate logic between functions, refactoring
Nov 10, 2020
627ede4
add __all__ for linter fix
Nov 10, 2020
bd6282a
add automatically restriction after very low karma
Nov 10, 2020
dc73e4a
fixes
Nov 10, 2020
2634b76
add config param to enable/disable auto restrict on negative karma
Nov 11, 2020
c4a2c69
move render message in separate function
Nov 11, 2020
d91399f
move condition to separate function
Nov 11, 2020
997b1a1
move TypeRestriction to models package
Nov 11, 2020
3dc84a5
rename variable
Nov 11, 2020
12c4fbf
add function for check has user restriction or not
Nov 11, 2020
687af15
fix exception clause
bomzheg Nov 26, 2020
f8e157c
fix typehint (change destination in pyrogram)
bomzheg Nov 26, 2020
342c24f
add auto restrict on negative karma
bomzheg Nov 26, 2020
51a2ab0
last one restriction on negative karma must be ban
bomzheg Nov 26, 2020
3116eb0
text fixes
bomzheg Nov 26, 2020
644b5f1
update .gitignore
Nov 26, 2020
bae0093
fix texts
Nov 26, 2020
96d6394
refactor more logic karmic ro
Nov 26, 2020
b347aad
user cant negative karma to target with RO
Nov 26, 2020
f423165
BUGFIXES, fix cancel change karma
Nov 26, 2020
a467f30
correct shield restricted from decrease karma and notify user for that
Nov 27, 2020
105f639
fix typos
Nov 27, 2020
0ce1153
fix race condition in case one user change karma and make it less tha…
Nov 27, 2020
71e8745
rename consts
Nov 27, 2020
87d6844
add few info to README.md
Nov 27, 2020
b8fc588
Merge branch 'master' into restrict-on-negative-karma
bomzheg Nov 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ log/
*/.pytest_cache/
jsons/
*.session
__pycache__/
db_data/
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ to the group administrators
* !idchat - get id of chat, your id, and id of replayed user

moderator commands list:
* !ro !mute [DURATION] - restrict replyed user for DURATION.
* !ban [DURATION] - kick replyed user for DURATION
* !ro !mute [DURATION] [@mention] - restrict replied or mentioned user for DURATION.
* !ban [DURATION] [@mention] - kick replied user for DURATION
* DURATION in format [AAAy][BBBw][CCCd][DDDh][EEEm][FFFs] where:
* AAA - count of years (more that one years is permanent)
* BBB - count of weeks
Expand All @@ -30,8 +30,8 @@ moderator commands list:
* EEE - count of minutes
* FFF - count of seconds (less that 30 seconds will be mean 30 seconds)
* you have to specify one or more duration part without spaces
* !warn, !w - official warn user from moderator
* !info - information about user (karma changes, restrictions, warns)
* !warn, !w [@mention] - official warn user from moderator
* !info [@mention] - information about user (karma changes, restrictions, warns)

superuser commands list:
* /generate_invite_logchat - if bot is admin in chat of LOG_CHAT_ID from config.py bot generates invite link to that
Expand Down
55 changes: 52 additions & 3 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,33 @@
"""
import os
import secrets
import typing
from datetime import timedelta
from functools import partial
from pathlib import Path

from aiogram import Bot
from dotenv import load_dotenv

from app.models.common import TypeRestriction

app_dir: Path = Path(__file__).parent.parent
load_dotenv(str(app_dir / '.env'))

PLUS = "+"
PLUS_WORDS = frozenset(
{"спасибо", "спс", "спасибочки", "благодарю", "пасиба", "пасеба", "посеба", "благодарочка", "thx", "мерси", "выручил"}
)
PLUS_WORDS = frozenset({
bomzheg marked this conversation as resolved.
Show resolved Hide resolved
"спасибо",
"спс",
"спасибочки",
"благодарю",
"пасиба",
"пасеба",
"посеба",
"благодарочка",
"thx",
"мерси",
"выручил",
})
PLUS_TRIGGERS = frozenset({PLUS, *PLUS_WORDS})
PLUS_EMOJI = frozenset({"👍", })
MINUS = "-"
Expand All @@ -22,6 +38,39 @@

TIME_TO_CANCEL_ACTIONS = 60

DEFAULT_RESTRICT_DURATION = timedelta(hours=1)
FOREVER_RESTRICT_DURATION = timedelta(days=666)

# auto restrict when karma less than NEGATIVE_KARMA_TO_RESTRICT
ENABLE_AUTO_RESTRICT_ON_NEGATIVE_KARMA = bool(int(os.getenv(
"ENABLE_AUTO_RESTRICT_ON_NEGATIVE_KARMA", default=0)))

NEGATIVE_KARMA_TO_RESTRICT = -100
KARMA_AFTER_RESTRICT = -80


class RestrictionPlanElem(typing.NamedTuple):
duration: timedelta
type_restriction: TypeRestriction


RESTRICTIONS_PLAN: typing.List[RestrictionPlanElem] = [
bomzheg marked this conversation as resolved.
Show resolved Hide resolved
RestrictionPlanElem(timedelta(days=7), TypeRestriction.karmic_ro),
RestrictionPlanElem(timedelta(days=30), TypeRestriction.karmic_ro),
RestrictionPlanElem(FOREVER_RESTRICT_DURATION, TypeRestriction.karmic_ban),
]

RO_ACTION = partial(Bot.restrict_chat_member, can_send_messages=False)
BAN_ACTION = Bot.kick_chat_member

action_for_restrict = {
TypeRestriction.ban: BAN_ACTION,
TypeRestriction.ro: RO_ACTION,
TypeRestriction.karmic_ro: RO_ACTION,
TypeRestriction.karmic_ban: BAN_ACTION,
}
COMMENT_AUTO_RESTRICT = f"Карма ниже {NEGATIVE_KARMA_TO_RESTRICT}"

PROG_NAME = "KarmaBot"
PROG_DESC = (
"This program is a Python 3+ script. The script launches a bot in Telegram,"
Expand Down
77 changes: 63 additions & 14 deletions app/handlers/change_karma.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

from aiogram import types
from aiogram.types import ContentType
from aiogram.utils.markdown import hbold
from loguru import logger

from app.misc import dp
from app import config
from app.models import Chat, User
from app.services.change_karma import change_karma, cancel_karma_change
from app.utils.exceptions import SubZeroKarma, AutoLike
from app.utils.exceptions import SubZeroKarma, CantChangeKarma, DontOffendRestricted
from app.services.remove_message import remove_kb_after_sleep
from . import keyboards as kb
from ..services.adaptive_trottle import AdaptiveThrottle
from app.services.adaptive_trottle import AdaptiveThrottle
from app.services.moderation import it_was_last_one_auto_restrict
from app.utils.timedelta_functions import format_timedelta


a_throttle = AdaptiveThrottle()
Expand All @@ -37,35 +39,82 @@ async def too_fast_change_karma(message: types.Message, *_, **__):
async def karma_change(message: types.Message, karma: dict, user: User, chat: Chat, target: User):

try:
uk, abs_change, karma_event = await change_karma(
result_change_karma = await change_karma(
target_user=target,
chat=chat,
user=user,
how_change=karma['karma_change'],
comment=karma['comment']
comment=karma['comment'],
bot=message.bot,
)
except SubZeroKarma:
return await message.reply("У Вас слишком мало кармы для этого")
except AutoLike:
except DontOffendRestricted:
return await message.reply("Не обижай его, он и так наказан!")
except CantChangeKarma as e:
logger.info("user {user} can't change karma, {e}", user=user.tg_id, e=e)
return
if result_change_karma.count_auto_restrict:
notify_auto_restrict_text = await render_text_auto_restrict(result_change_karma.count_auto_restrict, target)
else:
notify_auto_restrict_text = ""

# How match 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 \
+ result_change_karma.abs_change

msg = await message.reply(
"Вы {how_change} карму {name} до {karma_new} ({power:+.2f})".format(
"Вы {how_change} карму <b>{name}</b> до <b>{karma_new:.2f}</b> ({power:+.2f})"
"\n\n{notify_auto_restrict_text}".format(
how_change=get_how_change_text(karma['karma_change']),
name=hbold(target.fullname),
karma_new=hbold(uk.karma_round),
power=abs_change,
name=target.fullname,
karma_new=result_change_karma.karma_after,
power=result_change_karma.abs_change,
notify_auto_restrict_text=notify_auto_restrict_text
),
disable_web_page_preview=True,
reply_markup=kb.get_kb_karma_cancel(user, karma_event)
reply_markup=kb.get_kb_karma_cancel(
user=user,
karma_event=result_change_karma.karma_event,
rollback_karma=-how_changed_karma,
moderator_event=result_change_karma.moderator_event,
)
)
asyncio.create_task(remove_kb_after_sleep(msg, config.TIME_TO_CANCEL_ACTIONS))


async def render_text_auto_restrict(count_auto_restrict: int, target: User):
# TODO чото надо сделать с этим чтобы понятно объяснить за что RO и что будет в следующий раз
text = "{target}, Уровень вашей кармы стал ниже {negative_limit}.\n".format(
target=target.mention_link,
negative_limit=config.NEGATIVE_KARMA_TO_RESTRICT,
)
if it_was_last_one_auto_restrict(count_auto_restrict):
text += "Это был последний разрешённый раз. Теперь вы получаете вечное наказание."
else:
text += (
"За это вы наказаны на срок {duration}\n"
"Вам установлена карма {karma_after}. "
"Если Ваша карма снова достигнет {karma_to_restrict} "
"Ваше наказание будет строже.".format(
duration=format_timedelta(config.RESTRICTIONS_PLAN[count_auto_restrict - 1].duration),
karma_after=config.KARMA_AFTER_RESTRICT,
karma_to_restrict=config.NEGATIVE_KARMA_TO_RESTRICT,
)
)
return text


@dp.callback_query_handler(kb.cb_karma_cancel.filter())
async def cancel_karma(callback_query: types.CallbackQuery, callback_data: typing.Dict[str, str]):
if int(callback_data['user_id']) != callback_query.from_user.id:
return await callback_query.answer("Эта кнопка не для вас", cache_time=3600)
await cancel_karma_change(callback_data['action_id'])
user_cancel_id = int(callback_data['user_id'])
if user_cancel_id != callback_query.from_user.id:
return await callback_query.answer("Эта кнопка не для Вас", cache_time=3600)
karma_event_id = int(callback_data['karma_event_id'])
rollback_karma = float(callback_data['rollback_karma'])
moderator_event_id = callback_data['moderator_event_id']
moderator_event_id = None if moderator_event_id == "null" else int(moderator_event_id)
await cancel_karma_change(karma_event_id, rollback_karma, moderator_event_id, callback_query.bot)
await callback_query.answer("Вы отменили изменение кармы", show_alert=True)
await callback_query.message.delete()
5 changes: 2 additions & 3 deletions app/handlers/karma.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ async def get_top_from_private(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)
if len(parts) > 1:
chat = await Chat.get(chat_id=int(parts[1]))
else:
return await message.reply(
"Эту команду можно использовать только в группах "
"или с указанием id нужного чата, например:"
Expand All @@ -37,15 +36,15 @@ async def get_top(message: types.Message, chat: Chat, user: User):
await message.reply(text, disable_web_page_preview=True)


@dp.message_handler(ChatType.is_group_or_super_group, commands=["me"], commands_prefix='!')
@dp.message_handler(chat_type=[ChatType.GROUP, ChatType.SUPERGROUP], commands=["me"], commands_prefix='!')
@dp.throttled(rate=15)
async def get_top(message: types.Message, chat: Chat, user: User):
logger.info("user {user} ask his karma in chat {chat}", user=user.tg_id, chat=chat.chat_id)
uk, _ = await UserKarma.get_or_create(chat=chat, user=user)
await message.reply(f"Ваша карма в данном чате: {uk.karma_round}", disable_web_page_preview=True)


@dp.message_handler(ChatType.is_private, commands=["me"], commands_prefix='!')
@dp.message_handler(chat_type=ChatType.PRIVATE, commands=["me"], commands_prefix='!')
@dp.throttled(rate=15)
async def get_top(message: types.Message, user: User):
logger.info("user {user} ask his karma", user=user.tg_id)
Expand Down
15 changes: 11 additions & 4 deletions app/handlers/keyboards.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.utils.callback_data import CallbackData

from app.models import User, KarmaEvent
from app.models import User, KarmaEvent, ModeratorEvent

cb_karma_cancel = CallbackData("karma_cancel", "user_id", "action_id")
cb_karma_cancel = CallbackData("karma_cancel", "user_id", "karma_event_id", "rollback_karma", "moderator_event_id")


def get_kb_karma_cancel(user: User, action: KarmaEvent) -> InlineKeyboardMarkup:
def get_kb_karma_cancel(
user: User, karma_event: KarmaEvent, rollback_karma: float, moderator_event: ModeratorEvent
) -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(
"Отменить", callback_data=cb_karma_cancel.new(user_id=user.tg_id, action_id=action.id_)
"Отменить", callback_data=cb_karma_cancel.new(
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",
)
)]])
Loading