fix
Some checks failed
CI / Lint (ruff + mypy) (push) Failing after 32s
CI / Run tests (push) Has been skipped
CI / Docker build test (push) Successful in 11s

This commit is contained in:
2026-04-02 18:38:57 +07:00
parent 02afbd23ec
commit ef40fc25ee
4 changed files with 120 additions and 17 deletions

View File

@@ -4,7 +4,7 @@
## Что умеет ## Что умеет
- показывает пользователю только его кнопку, если он привязан по `operator_user_id` - показывает пользователю только его кнопку, если он привязан по `operator_user_id` или `operator_user_ids`
- позволяет админам видеть все кнопки - позволяет админам видеть все кнопки
- после выбора персонажа предлагает статус: `open`, `backstage`, `delay`, `rest` - после выбора персонажа предлагает статус: `open`, `backstage`, `delay`, `rest`
- после выбора статуса просит фразу для публикации - после выбора статуса просит фразу для публикации
@@ -36,7 +36,7 @@ uv sync
- `button_text` — текст на кнопке - `button_text` — текст на кнопке
- `display_name` — имя в посте - `display_name` — имя в посте
- `link` — ссылка на бота - `link` — ссылка на бота
- `operator_user_id` — Telegram user id человека, который может менять этот статус - `operator_user_id` или `operator_user_ids` — Telegram user id человека, который может менять этот статус
- `phrases` — шаблонные фразы под каждый статус - `phrases` — шаблонные фразы под каждый статус
## Запуск ## Запуск

View File

@@ -19,7 +19,7 @@
"link": "http://t.me/astat_ovhdRPCM_bot", "link": "http://t.me/astat_ovhdRPCM_bot",
"pronouns": "he/him", "pronouns": "he/him",
"emoji": "🌟", "emoji": "🌟",
"operator_user_id": 123456789, "operator_user_ids": [123456789, 987654321],
"default_status": "backstage", "default_status": "backstage",
"phrases": { "phrases": {
"open": "принимает тейки.", "open": "принимает тейки.",

View File

@@ -1,10 +1,10 @@
{ {
"header": "🌟🌟 🎀avigation ⠘ OVERHEARD 🌟\n⎯ шоу должно продолжаться.", "header": "🌟🌟 🎀𝑎𝑣𝑖𝑔𝑎𝑡𝑖𝑜𝑛𝒪𝖵𝖤𝖱𝖧𝖤𝖠𝖱𝒟 🌟\n⎯ 🤩ꫝᥱ 𝐒𝑜𝑤 𝑚ꪊ𝑠𝑡 𝑔𝑜 𝑜ꪀ.⬜",
"intro_links": "rules: https://telegra.ph/example\nplot: https://telegra.ph/example\nboost: https://t.me/boost/example", "intro_links": " 𝘳𝘶𝘭𝘦𝘴 (https://telegra.ph/%F0%9D%96%B1%F0%9D%96%AF%F0%9D%96%A2%F0%9D%96%AC-%F0%9D%92%AA%F0%9D%96%B5%F0%9D%96%A4%F0%9D%96%B1%F0%9D%96%A7%F0%9D%96%A4%F0%9D%96%A0%F0%9D%96%B1%F0%9D%92%9F--ehto-11-23) 😻 𝗉𝐥𝐨𝗍 😻 𝖻𝗈𝗈𝗌𝗍 (https://t.me/boost/OverheardRPCM)\n 😻 𝖱𝖳 𝐩𝗈𝗌𝗍 (https://t.me/overheardinfo/3) 𝖬𝖯 𝗂𝗇𝖿𝗈 (https://telegra.ph/C%F0%9D%92%AANDITI%F0%9D%92%AANS-12-03) 𝒑𝑟𝑖𝑐𝑒 (https://telegra.ph/%F0%9D%92%AAUR-PR%E2%84%90CE--uslugi-12-03) 😻",
"projects_block": "🌟 RP projects: https://t.me/archive/7\n🌟 RPCM channels: https://t.me/archive/18", "projects_block": "🌟🌟🌟 𝐊аmалᦢги⠘\n🌟 𝖱𝖯 𝗉𝗋𝗈𝗃𝖾𝖼𝗍𝗌 (https://t.me/archiveOverheardRPCM/7) & 𝖱𝖯𝖢𝖬 𝖼𝗁𝖺𝗇𝗇𝖾𝗅𝗌 (https://t.me/archiveOverheardRPCM/18)",
"actors_title": "\"The Dearest Actors\" administration", "actors_title": " ﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋\n\"𝓣𝑒 𝔇𝑒𝑎𝑟𝑒𝑠𝑡 🎀𝑐𝑡𝑜𝑟𝑠\" 😻𝖠𝖣𝖬𝖨𝖭𝖨𝖲𝖳𝖱𝖠𝖳𝖨𝖮𝖭🌟",
"legend": "🌟 исполняет роль 😻 принимает тейки\n🌟 в закулисье 😻 не принимает тейки\n🌟 антракт 😻 рест\n🌟 задержки 😻 отвечает с задержкой", "legend": "🌟исполняет роль 😻 принимает тейки\nв закулисье 😻 не принимает тейки \nантракт 😻 рест🌟",
"footer": "life channel: https://t.me/lifeExample\narchive: https://t.me/archiveExample", "footer": " ﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋\n 😻 🤩𝖨𝖥𝖤 𝖢𝖧𝖠𝖭𝖭𝖤𝖫 (https://t.me/lifeOverheardPRCM) & 🤩𝖱𝖢𝖧𝖨𝖵𝖤 (https://t.me/archiveOverheardRPCM) 😻",
"status_labels": { "status_labels": {
"open": "исполняет роль", "open": "исполняет роль",
"backstage": "в закулисье", "backstage": "в закулисье",
@@ -12,6 +12,38 @@
"rest": "антракт" "rest": "антракт"
}, },
"actors": [ "actors": [
{
"key": "liebe",
"button_text": "Либэ",
"display_name": "LIEBE",
"link": "http://t.me/harurpcmoverheardbot",
"pronouns": "she/her",
"emoji": "🌟",
"operator_user_id": 5203570193,
"default_status": "backstage",
"phrases": {
"open": "принимает тейки.",
"backstage": "в закулисье.",
"delay": "отвечает с задержкой.",
"rest": "на антракте."
}
},
{
"key": "motsiel",
"button_text": "Моцэ",
"display_name": "MOTSIEL",
"link": "http://t.me/OverheardRPCM_motsiel_bot",
"pronouns": "he/him",
"emoji": "🌟",
"operator_user_ids": [1378007191, 6364049891],
"default_status": "delay",
"phrases": {
"open": "принимает тейки.",
"backstage": "в закулисье.",
"delay": "задержки. ⭐",
"rest": "на антракте."
}
},
{ {
"key": "astat", "key": "astat",
"button_text": "Астат", "button_text": "Астат",
@@ -19,13 +51,29 @@
"link": "http://t.me/astat_ovhdRPCM_bot", "link": "http://t.me/astat_ovhdRPCM_bot",
"pronouns": "he/him", "pronouns": "he/him",
"emoji": "🌟", "emoji": "🌟",
"operator_user_id": 123456789, "operator_user_id": 1021293938,
"default_status": "backstage", "default_status": "backstage",
"phrases": { "phrases": {
"open": "принимает тейки.", "open": "принимает тейки.",
"backstage": "в закулисье.", "backstage": "в закулисье.",
"delay": "ответит с задержкой.", "delay": "ответит с задержкой.",
"rest": "ушел на антракт." "rest": "на антракте."
}
},
{
"key": "skafandr",
"button_text": "Скаф",
"display_name": "SKAFANDR",
"link": "http://t.me/Alxschfovhd_bot",
"pronouns": "he/him",
"emoji": "🌟",
"operator_user_id": 7647479588,
"default_status": "backstage",
"phrases": {
"open": "принимает тейки.",
"backstage": "в закулисье.",
"delay": "отвечает с задержкой.",
"rest": "на антракте."
} }
}, },
{ {
@@ -35,13 +83,61 @@
"link": "http://t.me/Marioverheard_bot", "link": "http://t.me/Marioverheard_bot",
"pronouns": "she/her", "pronouns": "she/her",
"emoji": "🌟", "emoji": "🌟",
"operator_user_id": 987654321, "operator_user_id": 2114793249,
"default_status": "open", "default_status": "open",
"phrases": { "phrases": {
"open": "исполняет роль.", "open": "исполняет роль.",
"backstage": "в закулисье.", "backstage": "в закулисье.",
"delay": "есть задержка.", "delay": "есть задержка.",
"rest": "антракт." "rest": "на антракте."
}
},
{
"key": "marcus",
"button_text": "Маркус",
"display_name": "MARCUS",
"link": "http://t.me/Marcus_OVHD_bot",
"pronouns": "he/him",
"emoji": "🌟",
"operator_user_id": 637396085,
"default_status": "backstage",
"phrases": {
"open": "принимает тейки.",
"backstage": "в закулисье.",
"delay": "отвечает с задержкой.",
"rest": "на антракте."
}
},
{
"key": "leo",
"button_text": "Лео",
"display_name": "LEO",
"link": "http://t.me/LEOoverh_bot",
"pronouns": "he/him",
"emoji": "🌟",
"operator_user_id": 6730780021,
"default_status": "backstage",
"phrases": {
"open": "принимает тейки.",
"backstage": "в закулисье.",
"delay": "отвечает с задержкой.",
"rest": "на антракте."
}
},
{
"key": "lein",
"button_text": "Лейн",
"display_name": "LEIN",
"link": "http://t.me/Lein_OVHD_Bot",
"pronouns": "he/him",
"emoji": "🌟",
"operator_user_id": 6751720805,
"default_status": "backstage",
"phrases": {
"open": "принимает тейки.",
"backstage": "в закулисье.",
"delay": "отвечает с задержкой.",
"rest": "на антракте."
} }
} }
] ]

View File

@@ -31,10 +31,17 @@ class SessionForm(StatesGroup):
waiting_for_phrase = State() waiting_for_phrase = State()
def actor_operator_ids(actor: dict[str, Any]) -> set[int]:
if "operator_user_ids" in actor:
return {int(user_id) for user_id in actor["operator_user_ids"]}
return {int(actor["operator_user_id"])}
def build_actor_lookup(config: dict) -> dict[int, dict[str, Any]]: def build_actor_lookup(config: dict) -> dict[int, dict[str, Any]]:
lookup: dict[int, dict[str, Any]] = {} lookup: dict[int, dict[str, Any]] = {}
for actor in config["actors"]: for actor in config["actors"]:
lookup[int(actor["operator_user_id"])] = actor for user_id in actor_operator_ids(actor):
lookup[user_id] = actor
return lookup return lookup
@@ -45,7 +52,7 @@ def is_allowed(user_id: int, actor_lookup: dict[int, dict[str, Any]], admin_ids:
def build_actor_keyboard(config: dict, user_id: int, admin_ids: set[int]) -> InlineKeyboardBuilder: def build_actor_keyboard(config: dict, user_id: int, admin_ids: set[int]) -> InlineKeyboardBuilder:
keyboard = InlineKeyboardBuilder() keyboard = InlineKeyboardBuilder()
for actor in config["actors"]: for actor in config["actors"]:
if user_id in admin_ids or int(actor["operator_user_id"]) == user_id: if user_id in admin_ids or user_id in actor_operator_ids(actor):
keyboard.button(text=actor["button_text"], callback_data=f"actor:{actor['key']}") keyboard.button(text=actor["button_text"], callback_data=f"actor:{actor['key']}")
keyboard.adjust(2) keyboard.adjust(2)
return keyboard return keyboard
@@ -96,7 +103,7 @@ async def actor_handler(callback: CallbackQuery, settings, actor_lookup: dict, a
await callback.answer("Персонаж не найден.", show_alert=True) await callback.answer("Персонаж не найден.", show_alert=True)
return return
if user_id not in settings.admin_ids and int(actor["operator_user_id"]) != user_id: if user_id not in settings.admin_ids and user_id not in actor_operator_ids(actor):
await callback.answer("Можно менять только свой статус.", show_alert=True) await callback.answer("Можно менять только свой статус.", show_alert=True)
return return
@@ -127,7 +134,7 @@ async def status_handler(
await callback.answer("Персонаж не найден.", show_alert=True) await callback.answer("Персонаж не найден.", show_alert=True)
return return
if user_id not in settings.admin_ids and int(actor["operator_user_id"]) != user_id: if user_id not in settings.admin_ids and user_id not in actor_operator_ids(actor):
await callback.answer("Можно менять только свой статус.", show_alert=True) await callback.answer("Можно менять только свой статус.", show_alert=True)
return return