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