diff --git a/src/ocab_core/modules_system/public_api/__init__.py b/src/ocab_core/modules_system/public_api/__init__.py index 85a5d60..d338538 100644 --- a/src/ocab_core/modules_system/public_api/__init__.py +++ b/src/ocab_core/modules_system/public_api/__init__.py @@ -7,3 +7,4 @@ from .public_api import ( register_router, set_my_commands, ) +from .utils import Utils diff --git a/src/ocab_core/modules_system/public_api/utils.py b/src/ocab_core/modules_system/public_api/utils.py new file mode 100644 index 0000000..22b574b --- /dev/null +++ b/src/ocab_core/modules_system/public_api/utils.py @@ -0,0 +1,12 @@ +import re + +CLEAN_HTML = re.compile("<.*?>") + + +class Utils: + @staticmethod + def code_format(code: str, lang: str): + if lang: + return f'
{code}
'
+ else:
+ return f"{code}" diff --git a/src/ocab_core/modules_system/safe/policy.py b/src/ocab_core/modules_system/safe/policy.py index 10d1cc2..def90d0 100644 --- a/src/ocab_core/modules_system/safe/policy.py +++ b/src/ocab_core/modules_system/safe/policy.py @@ -15,7 +15,7 @@ from RestrictedPython.Guards import ( safer_getattr, ) -from ocab_core.logger import log_new +from ocab_core.logger import log from ocab_core.modules_system.safe.zope_guards import extra_safe_builtins @@ -89,7 +89,7 @@ ALLOWED_IMPORTS = [ def safes_getattr(object, name, default=None, getattr=safer_getattr): if isinstance(object, Bot) and name == "token": - log_new("Bot.token is not allowed") + log("Bot.token is not allowed") raise Exception("Bot.token is not allowed") return getattr(object, name, default) diff --git a/src/ocab_core/modules_system/safe/zope_guards.py b/src/ocab_core/modules_system/safe/zope_guards.py index c7e77f8..e5e6436 100644 --- a/src/ocab_core/modules_system/safe/zope_guards.py +++ b/src/ocab_core/modules_system/safe/zope_guards.py @@ -109,3 +109,117 @@ def guarded_all(seq): extra_safe_builtins["all"] = guarded_all + +valid_inplace_types = (list, set) + +inplace_slots = { + "+=": "__iadd__", + "-=": "__isub__", + "*=": "__imul__", + "/=": (1 / 2 == 0) and "__idiv__" or "__itruediv__", + "//=": "__ifloordiv__", + "%=": "__imod__", + "**=": "__ipow__", + "<<=": "__ilshift__", + ">>=": "__irshift__", + "&=": "__iand__", + "^=": "__ixor__", + "|=": "__ior__", +} + + +def __iadd__(x, y): + x += y + return x + + +def __isub__(x, y): + x -= y + return x + + +def __imul__(x, y): + x *= y + return x + + +def __idiv__(x, y): + x /= y + return x + + +def __ifloordiv__(x, y): + x //= y + return x + + +def __imod__(x, y): + x %= y + return x + + +def __ipow__(x, y): + x **= y + return x + + +def __ilshift__(x, y): + x <<= y + return x + + +def __irshift__(x, y): + x >>= y + return x + + +def __iand__(x, y): + x &= y + return x + + +def __ixor__(x, y): + x ^= y + return x + + +def __ior__(x, y): + x |= y + return x + + +inplace_ops = { + "+=": __iadd__, + "-=": __isub__, + "*=": __imul__, + "/=": __idiv__, + "//=": __ifloordiv__, + "%=": __imod__, + "**=": __ipow__, + "<<=": __ilshift__, + ">>=": __irshift__, + "&=": __iand__, + "^=": __ixor__, + "|=": __ior__, +} + + +def protected_inplacevar(op, var, expr): + """Do an inplace operation + + If the var has an inplace slot, then disallow the operation + unless the var an instance of ``valid_inplace_types``. + """ + if hasattr(var, inplace_slots[op]) and not isinstance(var, valid_inplace_types): + try: + cls = var.__class__ + except AttributeError: + cls = type(var) + raise TypeError( + "Augmented assignment to %s objects is not allowed" + " in untrusted code" % cls.__name__ + ) + return inplace_ops[op](var, expr) + + +extra_safe_builtins["_inplacevar_"] = protected_inplacevar diff --git a/src/ocab_modules/standard/create_report_apps/create_report.py b/src/ocab_modules/standard/create_report_apps/create_report.py index 3e7d9c5..743fd80 100644 --- a/src/ocab_modules/standard/create_report_apps/create_report.py +++ b/src/ocab_modules/standard/create_report_apps/create_report.py @@ -1,64 +1,136 @@ from aiogram import Bot, Router +from aiogram.enums import ParseMode from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup -from aiogram.types import BufferedInputFile, Message +from aiogram.types import ( + BufferedInputFile, + KeyboardButton, + Message, + ReplyKeyboardMarkup, + ReplyKeyboardRemove, +) -from ocab_core.modules_system.public_api import get_fsm_context +from ocab_core.modules_system.public_api import Utils, get_fsm_context + +from .report import Report router = Router() class ReportState(StatesGroup): - input_kernel_info = State() + input_system_info = State() input_app_name = State() input_problem_step_by_step = State() + input_actual_result = State() + input_expected_result = State() + input_additional_info = State() + + +system_info_code = """echo "SESSION_TYPE: ${XDG_SESSION_TYPE:-Unknown}" +[ -f /etc/os-release ] && grep "^PRETTY_NAME=" /etc/os-release | cut -d= -f2 \ +| tr -d '"' | xargs echo "OS: " +echo "Kernel: $(uname -r)" +echo "DE: ${XDG_CURRENT_DESKTOP:-Unknown}" +grep "^model name" /proc/cpuinfo | head -n1 | cut -d: -f2 \ +| xargs echo "CPU: " +lspci | grep "VGA compatible controller" | cut -d: -f3 \ +| xargs -I{} echo "GPU: {}" +""" + + +system_info_message = """Укажите параметры свой системы. +Собрать информацию о системе можно с помощью данного скрипта: +""" + Utils.code_format( + system_info_code, + "shell", +) async def start_report(chat_id: int, bot: Bot): await bot.send_message( chat_id=chat_id, - text="Какая версия ядра у тебя на " - "текущий момент? Можно узнать " - "командой `uname -rm`", - parse_mode="Markdown", + text=system_info_message, + parse_mode=ParseMode.HTML, + reply_markup=ReplyKeyboardRemove(), ) state = await get_fsm_context(chat_id, chat_id) - await state.set_state(ReportState.input_kernel_info) + await state.set_state(ReportState.input_system_info) -@router.message(ReportState.input_kernel_info) -async def kernel_version_entered(message: Message, state: FSMContext): - await state.update_data(kernel=message.text) - await message.answer(text="В каком приложении возникла проблема?") +app_info_message = """Укажите название и версию приложения. +Узнать можно с помощью данной команды:""" + Utils.code_format( + "rpm -qa | grep -i НАЗВАНИЕ_ПРИЛОЖЕНИЯ", "shell" +) + + +@router.message(ReportState.input_system_info) +async def system_entered(message: Message, state: FSMContext): + await state.update_data(system=message.text) + await message.answer( + text=app_info_message, + parse_mode=ParseMode.HTML, + ) await state.set_state(ReportState.input_app_name) +step_by_step_message = ( + """Опиши проблему пошагово, что ты делал, что происходило, что не так.""" +) + + @router.message(ReportState.input_app_name) async def app_name_entered(message: Message, state: FSMContext): - await state.update_data(app_name=message.text) - await message.answer( - text="Опиши проблему пошагово, что ты делал, что происходило, что не так" - ) + await state.update_data(app=message.text) + await message.answer(text=step_by_step_message) await state.set_state(ReportState.input_problem_step_by_step) @router.message(ReportState.input_problem_step_by_step) async def problem_step_by_step_entered(message: Message, state: FSMContext): await state.update_data(problem_step_by_step=message.text) - await message.answer(text="Вот твой отчет сообщением, а также файлом:") + await message.answer(text="Опиши, что произошло (фактический результат).") + await state.set_state(ReportState.input_actual_result) + + +@router.message(ReportState.input_actual_result) +async def actual_result_entered(message: Message, state: FSMContext): + await state.update_data(actual=message.text) + await message.answer(text="Опиши ожидаемый результат.") + await state.set_state(ReportState.input_expected_result) + + +@router.message(ReportState.input_expected_result) +async def expected_result_entered(message: Message, state: FSMContext): + await state.update_data(expected=message.text) + await message.answer( + text="Если есть дополнительная информация, то напиши ее.", + reply_markup=ReplyKeyboardMarkup( + resize_keyboard=True, + keyboard=[ + [KeyboardButton(text="Дополнительной информации нет")], + ], + ), + ) + await state.set_state(ReportState.input_additional_info) + + +@router.message(ReportState.input_additional_info) +async def additional_info_entered(message: Message, state: FSMContext): + if message.text == "Дополнительной информации нет": + additional_info = "" + else: + additional_info = message.text + await state.update_data(additional=additional_info) + await message.answer( + text="Вот твой отчет сообщением, а также файлом:", + reply_markup=ReplyKeyboardRemove(), + ) data = await state.get_data() - report = f"""Стенд с ошибкой: -# uname -rm -{data['kernel']} + report = Report(data) + file_report = report.export(html=False).encode() -Шаги, приводящие к ошибке: - -{data['problem_step_by_step']} -""" - await message.answer(text=report) - await message.answer_document( - document=BufferedInputFile(report.encode(), "report.txt") - ) + await message.answer(text=report.export(html=True), parse_mode=ParseMode.HTML) + await message.answer_document(document=BufferedInputFile(file_report, "report.txt")) await state.clear() diff --git a/src/ocab_modules/standard/create_report_apps/report.py b/src/ocab_modules/standard/create_report_apps/report.py new file mode 100644 index 0000000..d90e21c --- /dev/null +++ b/src/ocab_modules/standard/create_report_apps/report.py @@ -0,0 +1,53 @@ +import aiogram + + +class ReportFormatter: + def __init__(self, html=True): + self.html = html + + def bold(self, string): + if self.html: + return f"{self.text(string)}" + return self.text(string) + + def text(self, string): + if self.html: + return aiogram.html.quote(string) + return string + + +class Report: + def __init__(self, data: dict): + self.data = data + + def export(self, html=True): + data = self.data + f = ReportFormatter(html) + + report = f"""{f.bold("Стенд с ошибкой:")} + +{f.text(data['system'])} + +{f.bold("Версия программы:")} + +{f.text(data['app'])} + +{f.bold("Шаги, приводящие к ошибке:")} + +{f.text(data['problem_step_by_step'])} + +{f.bold("Результат:")} + +{f.text(data['actual'])} + +{f.bold("Ожидаемый результат:")} + +{f.text(data['expected'])} + """ + if data["additional"] != "": + report += f"""{f.bold("Дополнительно:")} + +{f.text(data['additional'])} + """ + + return report