From 9e1d6322b679a1395a4fd13299c124e8856a940b Mon Sep 17 00:00:00 2001 From: Lacky1234union Date: Fri, 16 Oct 2026 00:06:52 +0300 Subject: [PATCH] =?UTF-8?q?feat(bot):=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA?= =?UTF-8?q?=D0=B0=20=C2=AB=F0=9F=9A=80=20=D0=97=D0=B0=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D0=B8=D1=82=D1=8C=C2=BB=20=D0=BF=D1=80=D1=8F?= =?UTF-8?q?=D0=BC=D0=BE=20=D0=B2=20=D0=BF=D0=B0=D0=BF=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit При непустом черновике в папке — «🚀 Закоммитить · N»: нет сообщения → спрашивает его и сразу показывает подтверждение пуша; есть → сразу подтверждение. На подтверждении — «✏️ Изменить сообщение». Раньше Push был спрятан в «🧾 Черновик» → «💬» → «🚀». Refs #9 Co-Authored-By: Claude Opus 5.5 --- app/bot/routers/commit.py | 41 +++++++++++++++++++++++++++++------ app/texts.py | 2 ++ config/texts.yaml | 2 ++ tests/bot/test_commit_push.py | 27 +++++++++++++++++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/app/bot/routers/commit.py b/app/bot/routers/commit.py index 866f8ee..3374947 100644 --- a/app/bot/routers/commit.py +++ b/app/bot/routers/commit.py @@ -59,6 +59,7 @@ class Act(StrEnum): PUSH = "ps" PUSH_GO = "pg" PUSH_FORCE = "pf" + COMMIT = "cm" class CommitCb(CallbackData, prefix="c"): @@ -179,8 +180,10 @@ def render_dir( kb.button(text=t.new_dir, callback_data=CommitCb(a=Act.NEW_DIR)) sizes.append(2) if draft_count: + # the main action once something is staged — right in the folder, not two screens deep + kb.button(text=t.commit_button.format(count=draft_count), callback_data=CommitCb(a=Act.COMMIT)) kb.button(text=t.draft_button.format(count=draft_count), callback_data=CommitCb(a=Act.DRAFT)) - sizes.append(1) + sizes += [1, 1] if path: kb.button(text=t.up, callback_data=CommitCb(a=Act.UP)) sizes.append(1) @@ -260,6 +263,14 @@ def build_router( kb.adjust(1) return text, kb.as_markup() + def confirm_screen(draft: DraftView) -> tuple[str, InlineKeyboardMarkup]: + builder = InlineKeyboardBuilder() + builder.button(text=t.push_go, callback_data=CommitCb(a=Act.PUSH_GO)) + builder.button(text=t.edit_message, callback_data=CommitCb(a=Act.MESSAGE)) + builder.button(text=texts.menu.back, callback_data=CommitCb(a=Act.TO_DIR)) + builder.adjust(1) + return t.push_confirm.format(summary=summary(draft), message=draft.message), builder.as_markup() + def summary(draft: DraftView) -> str: ops = {ChangeOp.ADD: t.op_add, ChangeOp.MODIFY: t.op_modify, ChangeOp.DELETE: t.op_delete} return "\n".join(f"{ops[c.op]} /{c.path}" for c in draft.changes) @@ -466,17 +477,28 @@ def build_router( return case Act.MESSAGE: await state.set_state(Flow.message) + await state.update_data(commit_then_push=False) await msg.edit_text(t.ask_message, reply_markup=to_dir_keyboard(texts)) case Act.PUSH: draft = await drafts.get(uid) if draft is None or not draft.changes or not draft.message: await query.answer() return - builder = InlineKeyboardBuilder() - builder.button(text=t.push_go, callback_data=CommitCb(a=Act.PUSH_GO)) - builder.button(text=texts.menu.back, callback_data=CommitCb(a=Act.DRAFT)) - confirm = t.push_confirm.format(summary=summary(draft), message=draft.message) - await msg.edit_text(confirm, reply_markup=builder.as_markup()) + confirm, markup = confirm_screen(draft) + await msg.edit_text(confirm, reply_markup=markup) + case Act.COMMIT: + draft = await drafts.get(uid) + if draft is None or not draft.changes: + await query.answer() + return + if draft.message: + confirm, markup = confirm_screen(draft) + await msg.edit_text(confirm, reply_markup=markup) + else: + # ask for the message, then go straight to the push confirmation + await state.set_state(Flow.message) + await state.update_data(commit_then_push=True) + await msg.edit_text(t.ask_message, reply_markup=to_dir_keyboard(texts)) case Act.PUSH_GO | Act.PUSH_FORCE: await query.answer() await do_push(msg, state, member, overwrite=callback_data.a is Act.PUSH_FORCE) @@ -549,7 +571,12 @@ def build_router( return await drafts.set_message(member.tg_id, text) await state.set_state(None) - screen, kb = await draft_screen(state, member.tg_id) + draft = await drafts.get(member.tg_id) + if (await state.get_data()).get("commit_then_push") and draft is not None and draft.changes: + await state.update_data(commit_then_push=False) + screen, kb = confirm_screen(draft) + else: + screen, kb = await draft_screen(state, member.tg_id) await message.answer(screen, reply_markup=kb) @router.message(Flow.content, F.text | F.document) diff --git a/app/texts.py b/app/texts.py index 803e65a..f6ad02d 100644 --- a/app/texts.py +++ b/app/texts.py @@ -110,6 +110,8 @@ class CommitTexts(_Strict): ask_message: str bad_message: str push_button: str + commit_button: str + edit_message: str push_confirm: str push_go: str pushing: str diff --git a/config/texts.yaml b/config/texts.yaml index 7df9dd1..c4ba489 100644 --- a/config/texts.yaml +++ b/config/texts.yaml @@ -113,6 +113,8 @@ commit: ask_message: "Сообщение коммита: до 200 символов, первая строка — до 72." bad_message: "Сообщение: 1–200 символов, первая строка — до 72. Пришлите другое." push_button: "🚀 Push" + commit_button: "🚀 Закоммитить · {count}" + edit_message: "✏️ Изменить сообщение" push_confirm: |- Запушить в main? diff --git a/tests/bot/test_commit_push.py b/tests/bot/test_commit_push.py index 158e7bc..e41c347 100644 --- a/tests/bot/test_commit_push.py +++ b/tests/bot/test_commit_push.py @@ -63,3 +63,30 @@ async def test_message_then_push_lands_in_remote(tg: TgClient, texts: Texts, rem tg.session.requests.clear() await tg.send(ADMIN, texts.menu.commit) assert not any(label.startswith("🧾") for label in labels(tg.session.sent(SendMessage)[-1].reply_markup)) + + +async def test_commit_button_in_folder_asks_message_then_confirms( + tg: TgClient, texts: Texts, remote: Path, tmp_path: Path +) -> None: + t = texts.commit + await tg.send(ADMIN, texts.menu.commit) + await tg.press(ADMIN, cb(Act.NEW_FILE)) + await tg.send(ADMIN, "quick.txt") + await tg.send(ADMIN, "быстро") + + folder = tg.session.sent(SendMessage)[-1] + assert t.commit_button.format(count=1) in labels(folder.reply_markup) + + await tg.press(ADMIN, cb(Act.COMMIT)) + assert tg.session.sent(EditMessageText)[-1].text == t.ask_message + await tg.send(ADMIN, "feat: быстрый коммит") + + confirm = tg.session.sent(SendMessage)[-1] + assert confirm.text.startswith("Запушить в main?") and "feat: быстрый коммит" in confirm.text + assert labels(confirm.reply_markup) == [t.push_go, t.edit_message, texts.menu.back] + + await tg.press(ADMIN, cb(Act.PUSH_GO)) + assert tg.session.sent(EditMessageText)[-1].text.startswith("✅ Запушено: ") + clone = tmp_path / "verify2" + git(tmp_path, "clone", "-q", str(remote), str(clone)) + assert (clone / "quick.txt").read_text(encoding="utf-8") == "быстро" -- 2.54.0