{
  "schemaVersion": "1.0",
  "item": {
    "slug": "telegram-colored-choices-buttons",
    "name": "tg buttons",
    "source": "tencent",
    "type": "skill",
    "category": "AI 智能",
    "sourceUrl": "https://clawhub.ai/dandysuper/telegram-colored-choices-buttons",
    "canonicalUrl": "https://clawhub.ai/dandysuper/telegram-colored-choices-buttons",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/telegram-colored-choices-buttons",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=telegram-colored-choices-buttons",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.sh",
      "SKILL.md",
      "scripts/setup_openclaw_bot.sh"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/telegram-colored-choices-buttons"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/telegram-colored-choices-buttons",
    "agentPageUrl": "https://openagent3.xyz/skills/telegram-colored-choices-buttons/agent",
    "manifestUrl": "https://openagent3.xyz/skills/telegram-colored-choices-buttons/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/telegram-colored-choices-buttons/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Purpose",
        "body": "Every time the bot presents choices to the user, it MUST automatically color each button based on how critical, irreversible, or different the choices are. The bot never sends plain/unstyled choice buttons — color is always applied."
      },
      {
        "title": "Core Rule",
        "body": "Whenever the bot sends 2+ choices to a user, classify each choice and assign a style automatically. This is not optional."
      },
      {
        "title": "Telegram Bot API — Button Styles",
        "body": "Two fields on InlineKeyboardButton and KeyboardButton:\n\nstyle (String, Optional) — Button color:\n\n(omit) — Default accent/blue. The recommended / safe / primary action.\n\"destructive\" — Red. Irreversible, dangerous, or high-stakes actions.\n\"secondary\" — Gray/muted. Low-priority, dismiss, skip, or neutral actions.\n\n\n\nicon_custom_emoji_id (String, Optional) — Custom emoji icon on the button."
      },
      {
        "title": "Automatic Classification Rules",
        "body": "When the bot builds a set of choice buttons, it MUST classify every choice into one of three tiers before sending:"
      },
      {
        "title": "Tier 1 — Default (accent/blue): The recommended path",
        "body": "Apply when the choice is:\n\nThe safest or most common action\nA positive confirmation (\"Yes\", \"Continue\", \"Accept\", \"Start\")\nThe action the bot would recommend\nMoving forward in a flow\n\nDo: omit the style field (or set to null)."
      },
      {
        "title": "Tier 2 — Destructive (red): High-stakes or irreversible",
        "body": "Apply when the choice:\n\nDeletes, removes, or permanently changes something\nCancels an in-progress operation that loses work\nBlocks, bans, or restricts a user\nRejects, declines, or refuses something important\nSpends money, tokens, or credits\nCannot be undone easily\n\nDo: set \"style\": \"destructive\"."
      },
      {
        "title": "Tier 3 — Secondary (gray): Low-priority or escape hatch",
        "body": "Apply when the choice:\n\nSkips, dismisses, or postpones (\"Maybe later\", \"Not now\")\nIs a neutral fallback (\"Back\", \"Cancel\" when nothing is lost)\nShows more info without committing (\"Details\", \"Help\")\nIs the least important option in the set\n\nDo: set \"style\": \"secondary\"."
      },
      {
        "title": "How to Decide — Contrast Matters",
        "body": "When choices differ in criticality, the colors MUST reflect that contrast:\n\nHigh contrast — choices have very different consequences:\n\n\"Delete my account\" → destructive (red)\n \"Keep my account\"   → default (blue)\n\nMedium contrast — one main action, one escape:\n\n\"Subscribe\"   → default (blue)\n \"Not now\"     → secondary (gray)\n\nLow contrast — choices are roughly equal:\n\n\"Option A\" → default (blue)\n \"Option B\" → default (blue)\n \"Skip\"     → secondary (gray)\n\nMultiple tiers in one set:\n\n\"Confirm purchase\"  → default (blue)     — recommended\n \"Change amount\"     → secondary (gray)   — neutral/back\n \"Cancel order\"      → destructive (red)  — loses progress"
      },
      {
        "title": "Classification Examples",
        "body": "Bot asks: \"Approve this document?\"\n\n[\n  [{\"text\": \"✅ Approve\", \"callback_data\": \"approve\"},\n   {\"text\": \"❌ Reject\", \"callback_data\": \"reject\", \"style\": \"destructive\"}],\n  [{\"text\": \"⏭ Review later\", \"callback_data\": \"skip\", \"style\": \"secondary\"}]\n]\n\nBot asks: \"Pick a plan:\"\n\n[\n  [{\"text\": \"Free Plan\", \"callback_data\": \"free\"},\n   {\"text\": \"Pro Plan\", \"callback_data\": \"pro\"}],\n  [{\"text\": \"Compare plans\", \"callback_data\": \"compare\", \"style\": \"secondary\"}]\n]\n\n(Equal choices = both default; info link = secondary)\n\nBot asks: \"Delete all messages in this chat?\"\n\n[\n  [{\"text\": \"🗑 Delete all\", \"callback_data\": \"delete_all\", \"style\": \"destructive\"}],\n  [{\"text\": \"Keep messages\", \"callback_data\": \"keep\"}]\n]\n\n(Destructive action is red; safe action is the default blue)\n\nBot asks: \"Transfer 500 tokens to @user?\"\n\n[\n  [{\"text\": \"Send 500 tokens\", \"callback_data\": \"send\", \"style\": \"destructive\"},\n   {\"text\": \"Cancel\", \"callback_data\": \"cancel\", \"style\": \"secondary\"}]\n]\n\n(Spending = destructive since it costs something; cancel = secondary)"
      },
      {
        "title": "Implementation — Python Auto-Classifier",
        "body": "The bot MUST use a classifier function to determine style. Here is the reference implementation:\n\nimport re\n\n# Keywords that signal each tier (case-insensitive, matched against button text + callback_data)\nDESTRUCTIVE_SIGNALS = [\n    r\"\\bdelete\\b\", r\"\\bremove\\b\", r\"\\bban\\b\", r\"\\bblock\\b\",\n    r\"\\breject\\b\", r\"\\bdecline\\b\", r\"\\brevoke\\b\", r\"\\bterminate\\b\",\n    r\"\\bcancel order\\b\", r\"\\bcancel subscription\\b\",\n    r\"\\bunsubscribe\\b\", r\"\\bdestroy\\b\", r\"\\bpurge\\b\",\n    r\"\\bspend\\b\", r\"\\btransfer\\b\", r\"\\bpay\\b\", r\"\\bsend.*tokens?\\b\",\n    r\"\\breset\\b\", r\"\\bclear all\\b\", r\"\\bwipe\\b\",\n    r\"\\bleave\\b\", r\"\\bquit\\b\", r\"\\bdisconnect\\b\",\n]\n\nSECONDARY_SIGNALS = [\n    r\"\\bskip\\b\", r\"\\bnot now\\b\", r\"\\bmaybe later\\b\", r\"\\blater\\b\",\n    r\"\\bback\\b\", r\"\\bdismiss\\b\", r\"\\bclose\\b\",\n    r\"\\bdetails\\b\", r\"\\bmore info\\b\", r\"\\bhelp\\b\", r\"\\babout\\b\",\n    r\"\\bno thanks\\b\", r\"\\bnevermind\\b\",\n    r\"\\bcancel$\",  # plain \"cancel\" (no lost work) = secondary, not destructive\n]\n\n\ndef classify_button_style(text: str, callback_data: str = \"\", context_hint: str = \"\") -> str | None:\n    \"\"\"\n    Automatically determine the button style based on its text and context.\n\n    Returns:\n        \"destructive\" — red button (irreversible / high-stakes)\n        \"secondary\"   — gray button (low-priority / dismiss)\n        None          — default blue button (primary / recommended)\n\n    context_hint: optional extra context like \"this action costs money\"\n    \"\"\"\n    combined = f\"{text} {callback_data} {context_hint}\".lower()\n\n    # Check destructive first (higher priority)\n    for pattern in DESTRUCTIVE_SIGNALS:\n        if re.search(pattern, combined):\n            return \"destructive\"\n\n    # Then secondary\n    for pattern in SECONDARY_SIGNALS:\n        if re.search(pattern, combined):\n            return \"secondary\"\n\n    # Default = primary (blue)\n    return None\n\n\ndef build_choice_buttons(choices: list[dict]) -> list[list[dict]]:\n    \"\"\"\n    Takes a list of raw choices and returns Bot API inline_keyboard rows\n    with styles automatically assigned.\n\n    Each choice dict:\n        text (str):          Button label (required)\n        data (str):          callback_data (required unless url is set)\n        url (str):           URL button (optional, mutually exclusive with data)\n        style (str|None):    Override style — if set, skip auto-classification\n        context (str):       Extra hint for classifier (e.g. \"costs money\")\n        emoji_id (str):      Custom emoji ID (optional)\n        row (int):           Force button into a specific row (optional)\n\n    Returns list of rows suitable for inline_keyboard.\n    \"\"\"\n    # Group by row\n    row_map: dict[int, list[dict]] = {}\n    auto_row = 0\n    for i, choice in enumerate(choices):\n        btn: dict = {\"text\": choice[\"text\"]}\n\n        # Action\n        if \"url\" in choice:\n            btn[\"url\"] = choice[\"url\"]\n        else:\n            btn[\"callback_data\"] = choice.get(\"data\", choice[\"text\"].lower().replace(\" \", \"_\"))\n\n        # Style — use override if provided, else auto-classify\n        if \"style\" in choice and choice[\"style\"] is not None:\n            btn[\"style\"] = choice[\"style\"]\n        else:\n            auto_style = classify_button_style(\n                choice[\"text\"],\n                choice.get(\"data\", \"\"),\n                choice.get(\"context\", \"\"),\n            )\n            if auto_style:\n                btn[\"style\"] = auto_style\n\n        # Custom emoji\n        if \"emoji_id\" in choice:\n            btn[\"icon_custom_emoji_id\"] = choice[\"emoji_id\"]\n\n        # Row assignment\n        target_row = choice.get(\"row\", auto_row)\n        row_map.setdefault(target_row, []).append(btn)\n\n        # Auto-advance row every 2 buttons\n        if len(row_map.get(auto_row, [])) >= 2:\n            auto_row += 1\n\n    return [row_map[k] for k in sorted(row_map.keys())]"
      },
      {
        "title": "Using the classifier in the bot:",
        "body": "import requests\n\ndef send_choices(bot_token, chat_id, text, choices, parse_mode=\"HTML\"):\n    \"\"\"Send a message with auto-colored choice buttons.\"\"\"\n    keyboard = build_choice_buttons(choices)\n    payload = {\n        \"chat_id\": chat_id,\n        \"text\": text,\n        \"parse_mode\": parse_mode,\n        \"reply_markup\": {\"inline_keyboard\": keyboard},\n    }\n    url = f\"https://api.telegram.org/bot{bot_token}/sendMessage\"\n    resp = requests.post(url, json=payload)\n    resp.raise_for_status()\n    return resp.json()\n\n# The bot just passes raw choices — colors are assigned automatically:\nsend_choices(TOKEN, chat_id, \"Approve this document?\", [\n    {\"text\": \"✅ Approve\", \"data\": \"approve\"},\n    {\"text\": \"❌ Reject\", \"data\": \"reject\"},          # auto → destructive (red)\n    {\"text\": \"⏭ Review later\", \"data\": \"later\"},       # auto → secondary (gray)\n])"
      },
      {
        "title": "Usage with python-telegram-bot library",
        "body": "If the library version does not yet expose style, pass it via api_kwargs.\n\nfrom telegram import InlineKeyboardButton, InlineKeyboardMarkup\n\ndef auto_button(text, callback_data, context=\"\"):\n    style = classify_button_style(text, callback_data, context)\n    kwargs = {\"style\": style} if style else {}\n    return InlineKeyboardButton(text, callback_data=callback_data, api_kwargs=kwargs)\n\nkeyboard = InlineKeyboardMarkup([\n    [auto_button(\"Approve\", \"approve\"),\n     auto_button(\"Reject\", \"reject\")],\n    [auto_button(\"Skip\", \"skip\")],\n])\nawait update.message.reply_text(\"Pick an option:\", reply_markup=keyboard)"
      },
      {
        "title": "Reply Keyboard — Same Rules Apply",
        "body": "{\n  \"chat_id\": \"<CHAT_ID>\",\n  \"text\": \"Delete your data?\",\n  \"reply_markup\": {\n    \"keyboard\": [\n      [\n        {\"text\": \"Keep my data\"},\n        {\"text\": \"Delete everything\", \"style\": \"destructive\"}\n      ]\n    ],\n    \"resize_keyboard\": true,\n    \"one_time_keyboard\": true\n  }\n}"
      },
      {
        "title": "Custom Emoji on Buttons",
        "body": "Can be combined with style on the same button:\n\n{\"text\": \"Boost\", \"callback_data\": \"boost\", \"style\": \"destructive\", \"icon_custom_emoji_id\": \"5368324170671202286\"}"
      },
      {
        "title": "Shell Helper",
        "body": "Quick test with colored buttons:\n\n./SKILL.sh <BOT_TOKEN> <CHAT_ID>"
      },
      {
        "title": "References",
        "body": "Telegram Bot API: https://core.telegram.org/bots/api\nBot API Changelog: https://core.telegram.org/bots/api-changelog\nTelegram Blog Announcement: https://telegram.org/blog/crafting-android-design-and-more"
      }
    ],
    "body": "Openclaw AI Bot — Colored Choice Buttons Skill\nPurpose\n\nEvery time the bot presents choices to the user, it MUST automatically color each button based on how critical, irreversible, or different the choices are. The bot never sends plain/unstyled choice buttons — color is always applied.\n\nCore Rule\n\nWhenever the bot sends 2+ choices to a user, classify each choice and assign a style automatically. This is not optional.\n\nTelegram Bot API — Button Styles\n\nTwo fields on InlineKeyboardButton and KeyboardButton:\n\nstyle (String, Optional) — Button color:\n\n(omit) — Default accent/blue. The recommended / safe / primary action.\n\"destructive\" — Red. Irreversible, dangerous, or high-stakes actions.\n\"secondary\" — Gray/muted. Low-priority, dismiss, skip, or neutral actions.\n\nicon_custom_emoji_id (String, Optional) — Custom emoji icon on the button.\n\nAutomatic Classification Rules\n\nWhen the bot builds a set of choice buttons, it MUST classify every choice into one of three tiers before sending:\n\nTier 1 — Default (accent/blue): The recommended path\n\nApply when the choice is:\n\nThe safest or most common action\nA positive confirmation (\"Yes\", \"Continue\", \"Accept\", \"Start\")\nThe action the bot would recommend\nMoving forward in a flow\n\nDo: omit the style field (or set to null).\n\nTier 2 — Destructive (red): High-stakes or irreversible\n\nApply when the choice:\n\nDeletes, removes, or permanently changes something\nCancels an in-progress operation that loses work\nBlocks, bans, or restricts a user\nRejects, declines, or refuses something important\nSpends money, tokens, or credits\nCannot be undone easily\n\nDo: set \"style\": \"destructive\".\n\nTier 3 — Secondary (gray): Low-priority or escape hatch\n\nApply when the choice:\n\nSkips, dismisses, or postpones (\"Maybe later\", \"Not now\")\nIs a neutral fallback (\"Back\", \"Cancel\" when nothing is lost)\nShows more info without committing (\"Details\", \"Help\")\nIs the least important option in the set\n\nDo: set \"style\": \"secondary\".\n\nHow to Decide — Contrast Matters\n\nWhen choices differ in criticality, the colors MUST reflect that contrast:\n\nHigh contrast — choices have very different consequences:\n\n \"Delete my account\" → destructive (red)\n \"Keep my account\"   → default (blue)\n\n\nMedium contrast — one main action, one escape:\n\n \"Subscribe\"   → default (blue)\n \"Not now\"     → secondary (gray)\n\n\nLow contrast — choices are roughly equal:\n\n \"Option A\" → default (blue)\n \"Option B\" → default (blue)\n \"Skip\"     → secondary (gray)\n\n\nMultiple tiers in one set:\n\n \"Confirm purchase\"  → default (blue)     — recommended\n \"Change amount\"     → secondary (gray)   — neutral/back\n \"Cancel order\"      → destructive (red)  — loses progress\n\nClassification Examples\n\nBot asks: \"Approve this document?\"\n\n[\n  [{\"text\": \"✅ Approve\", \"callback_data\": \"approve\"},\n   {\"text\": \"❌ Reject\", \"callback_data\": \"reject\", \"style\": \"destructive\"}],\n  [{\"text\": \"⏭ Review later\", \"callback_data\": \"skip\", \"style\": \"secondary\"}]\n]\n\n\nBot asks: \"Pick a plan:\"\n\n[\n  [{\"text\": \"Free Plan\", \"callback_data\": \"free\"},\n   {\"text\": \"Pro Plan\", \"callback_data\": \"pro\"}],\n  [{\"text\": \"Compare plans\", \"callback_data\": \"compare\", \"style\": \"secondary\"}]\n]\n\n\n(Equal choices = both default; info link = secondary)\n\nBot asks: \"Delete all messages in this chat?\"\n\n[\n  [{\"text\": \"🗑 Delete all\", \"callback_data\": \"delete_all\", \"style\": \"destructive\"}],\n  [{\"text\": \"Keep messages\", \"callback_data\": \"keep\"}]\n]\n\n\n(Destructive action is red; safe action is the default blue)\n\nBot asks: \"Transfer 500 tokens to @user?\"\n\n[\n  [{\"text\": \"Send 500 tokens\", \"callback_data\": \"send\", \"style\": \"destructive\"},\n   {\"text\": \"Cancel\", \"callback_data\": \"cancel\", \"style\": \"secondary\"}]\n]\n\n\n(Spending = destructive since it costs something; cancel = secondary)\n\nImplementation — Python Auto-Classifier\n\nThe bot MUST use a classifier function to determine style. Here is the reference implementation:\n\nimport re\n\n# Keywords that signal each tier (case-insensitive, matched against button text + callback_data)\nDESTRUCTIVE_SIGNALS = [\n    r\"\\bdelete\\b\", r\"\\bremove\\b\", r\"\\bban\\b\", r\"\\bblock\\b\",\n    r\"\\breject\\b\", r\"\\bdecline\\b\", r\"\\brevoke\\b\", r\"\\bterminate\\b\",\n    r\"\\bcancel order\\b\", r\"\\bcancel subscription\\b\",\n    r\"\\bunsubscribe\\b\", r\"\\bdestroy\\b\", r\"\\bpurge\\b\",\n    r\"\\bspend\\b\", r\"\\btransfer\\b\", r\"\\bpay\\b\", r\"\\bsend.*tokens?\\b\",\n    r\"\\breset\\b\", r\"\\bclear all\\b\", r\"\\bwipe\\b\",\n    r\"\\bleave\\b\", r\"\\bquit\\b\", r\"\\bdisconnect\\b\",\n]\n\nSECONDARY_SIGNALS = [\n    r\"\\bskip\\b\", r\"\\bnot now\\b\", r\"\\bmaybe later\\b\", r\"\\blater\\b\",\n    r\"\\bback\\b\", r\"\\bdismiss\\b\", r\"\\bclose\\b\",\n    r\"\\bdetails\\b\", r\"\\bmore info\\b\", r\"\\bhelp\\b\", r\"\\babout\\b\",\n    r\"\\bno thanks\\b\", r\"\\bnevermind\\b\",\n    r\"\\bcancel$\",  # plain \"cancel\" (no lost work) = secondary, not destructive\n]\n\n\ndef classify_button_style(text: str, callback_data: str = \"\", context_hint: str = \"\") -> str | None:\n    \"\"\"\n    Automatically determine the button style based on its text and context.\n\n    Returns:\n        \"destructive\" — red button (irreversible / high-stakes)\n        \"secondary\"   — gray button (low-priority / dismiss)\n        None          — default blue button (primary / recommended)\n\n    context_hint: optional extra context like \"this action costs money\"\n    \"\"\"\n    combined = f\"{text} {callback_data} {context_hint}\".lower()\n\n    # Check destructive first (higher priority)\n    for pattern in DESTRUCTIVE_SIGNALS:\n        if re.search(pattern, combined):\n            return \"destructive\"\n\n    # Then secondary\n    for pattern in SECONDARY_SIGNALS:\n        if re.search(pattern, combined):\n            return \"secondary\"\n\n    # Default = primary (blue)\n    return None\n\n\ndef build_choice_buttons(choices: list[dict]) -> list[list[dict]]:\n    \"\"\"\n    Takes a list of raw choices and returns Bot API inline_keyboard rows\n    with styles automatically assigned.\n\n    Each choice dict:\n        text (str):          Button label (required)\n        data (str):          callback_data (required unless url is set)\n        url (str):           URL button (optional, mutually exclusive with data)\n        style (str|None):    Override style — if set, skip auto-classification\n        context (str):       Extra hint for classifier (e.g. \"costs money\")\n        emoji_id (str):      Custom emoji ID (optional)\n        row (int):           Force button into a specific row (optional)\n\n    Returns list of rows suitable for inline_keyboard.\n    \"\"\"\n    # Group by row\n    row_map: dict[int, list[dict]] = {}\n    auto_row = 0\n    for i, choice in enumerate(choices):\n        btn: dict = {\"text\": choice[\"text\"]}\n\n        # Action\n        if \"url\" in choice:\n            btn[\"url\"] = choice[\"url\"]\n        else:\n            btn[\"callback_data\"] = choice.get(\"data\", choice[\"text\"].lower().replace(\" \", \"_\"))\n\n        # Style — use override if provided, else auto-classify\n        if \"style\" in choice and choice[\"style\"] is not None:\n            btn[\"style\"] = choice[\"style\"]\n        else:\n            auto_style = classify_button_style(\n                choice[\"text\"],\n                choice.get(\"data\", \"\"),\n                choice.get(\"context\", \"\"),\n            )\n            if auto_style:\n                btn[\"style\"] = auto_style\n\n        # Custom emoji\n        if \"emoji_id\" in choice:\n            btn[\"icon_custom_emoji_id\"] = choice[\"emoji_id\"]\n\n        # Row assignment\n        target_row = choice.get(\"row\", auto_row)\n        row_map.setdefault(target_row, []).append(btn)\n\n        # Auto-advance row every 2 buttons\n        if len(row_map.get(auto_row, [])) >= 2:\n            auto_row += 1\n\n    return [row_map[k] for k in sorted(row_map.keys())]\n\nUsing the classifier in the bot:\nimport requests\n\ndef send_choices(bot_token, chat_id, text, choices, parse_mode=\"HTML\"):\n    \"\"\"Send a message with auto-colored choice buttons.\"\"\"\n    keyboard = build_choice_buttons(choices)\n    payload = {\n        \"chat_id\": chat_id,\n        \"text\": text,\n        \"parse_mode\": parse_mode,\n        \"reply_markup\": {\"inline_keyboard\": keyboard},\n    }\n    url = f\"https://api.telegram.org/bot{bot_token}/sendMessage\"\n    resp = requests.post(url, json=payload)\n    resp.raise_for_status()\n    return resp.json()\n\n# The bot just passes raw choices — colors are assigned automatically:\nsend_choices(TOKEN, chat_id, \"Approve this document?\", [\n    {\"text\": \"✅ Approve\", \"data\": \"approve\"},\n    {\"text\": \"❌ Reject\", \"data\": \"reject\"},          # auto → destructive (red)\n    {\"text\": \"⏭ Review later\", \"data\": \"later\"},       # auto → secondary (gray)\n])\n\nUsage with python-telegram-bot library\n\nIf the library version does not yet expose style, pass it via api_kwargs.\n\nfrom telegram import InlineKeyboardButton, InlineKeyboardMarkup\n\ndef auto_button(text, callback_data, context=\"\"):\n    style = classify_button_style(text, callback_data, context)\n    kwargs = {\"style\": style} if style else {}\n    return InlineKeyboardButton(text, callback_data=callback_data, api_kwargs=kwargs)\n\nkeyboard = InlineKeyboardMarkup([\n    [auto_button(\"Approve\", \"approve\"),\n     auto_button(\"Reject\", \"reject\")],\n    [auto_button(\"Skip\", \"skip\")],\n])\nawait update.message.reply_text(\"Pick an option:\", reply_markup=keyboard)\n\nReply Keyboard — Same Rules Apply\n{\n  \"chat_id\": \"<CHAT_ID>\",\n  \"text\": \"Delete your data?\",\n  \"reply_markup\": {\n    \"keyboard\": [\n      [\n        {\"text\": \"Keep my data\"},\n        {\"text\": \"Delete everything\", \"style\": \"destructive\"}\n      ]\n    ],\n    \"resize_keyboard\": true,\n    \"one_time_keyboard\": true\n  }\n}\n\nCustom Emoji on Buttons\n\nCan be combined with style on the same button:\n\n{\"text\": \"Boost\", \"callback_data\": \"boost\", \"style\": \"destructive\", \"icon_custom_emoji_id\": \"5368324170671202286\"}\n\nShell Helper\n\nQuick test with colored buttons:\n\n./SKILL.sh <BOT_TOKEN> <CHAT_ID>\n\nReferences\nTelegram Bot API: https://core.telegram.org/bots/api\nBot API Changelog: https://core.telegram.org/bots/api-changelog\nTelegram Blog Announcement: https://telegram.org/blog/crafting-android-design-and-more"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/dandysuper/telegram-colored-choices-buttons",
    "publisherUrl": "https://clawhub.ai/dandysuper/telegram-colored-choices-buttons",
    "owner": "dandysuper",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/telegram-colored-choices-buttons",
    "downloadUrl": "https://openagent3.xyz/downloads/telegram-colored-choices-buttons",
    "agentUrl": "https://openagent3.xyz/skills/telegram-colored-choices-buttons/agent",
    "manifestUrl": "https://openagent3.xyz/skills/telegram-colored-choices-buttons/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/telegram-colored-choices-buttons/agent.md"
  }
}