Skip to content

Commit 3113b4f

Browse files
authored
Merge pull request chigwell#240 from omid-io/fix/create-poll-error-179
fix(tools): support union chat_id and validate options in create_poll (chigwell#179)
2 parents b022230 + 74afda7 commit 3113b4f

2 files changed

Lines changed: 358 additions & 27 deletions

File tree

‎telegram_mcp/tools/messages.py‎

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2172,29 +2172,98 @@ async def get_pinned_messages(chat_id: Union[int, str], account: str = None) ->
21722172
@with_account(readonly=False)
21732173
@validate_id("chat_id")
21742174
async def create_poll(
2175-
chat_id: int,
2175+
chat_id: Union[int, str],
21762176
question: str,
2177-
options: list,
2177+
options: Union[List[str], List[Dict[str, Any]], str],
21782178
multiple_choice: bool = False,
21792179
quiz_mode: bool = False,
21802180
public_votes: bool = True,
2181-
close_date: str = None,
2182-
account: str = None,
2181+
close_date: Optional[str] = None,
2182+
account: Optional[str] = None,
21832183
) -> str:
21842184
"""
21852185
Create a poll in a chat using Telegram's native poll feature.
21862186
21872187
Args:
2188-
chat_id: The ID of the chat to send the poll to
2189-
question: The poll question
2190-
options: List of answer options (2-10 options)
2191-
multiple_choice: Whether users can select multiple answers
2192-
quiz_mode: Whether this is a quiz (has correct answer)
2193-
public_votes: Whether votes are public
2194-
close_date: Optional close date in ISO format (YYYY-MM-DD HH:MM:SS)
2188+
chat_id: The ID or username of the chat to send the poll to.
2189+
question: The poll question.
2190+
options: List of answer options (2-10 options). Can be a list of strings
2191+
or option objects, or a JSON string / comma-separated string.
2192+
multiple_choice: Whether users can select multiple answers.
2193+
quiz_mode: Whether this is a quiz (has correct answer).
2194+
public_votes: Whether votes are public.
2195+
close_date: Optional close date in ISO format (YYYY-MM-DD HH:MM:SS).
2196+
account: Account name to use (optional).
21952197
"""
21962198
try:
2199+
# Validate question
2200+
if not question or not str(question).strip():
2201+
return "Error: Poll question cannot be empty."
2202+
question_text = str(question).strip()
2203+
if len(question_text) > 300:
2204+
return "Error: Poll question cannot exceed 300 characters."
2205+
2206+
# Parse and normalize options
2207+
if isinstance(options, str):
2208+
options_str = options.strip()
2209+
if options_str.startswith("[") and options_str.endswith("]"):
2210+
try:
2211+
parsed = json.loads(options_str)
2212+
if isinstance(parsed, list):
2213+
options = parsed
2214+
except Exception:
2215+
pass
2216+
if isinstance(options, str):
2217+
sep = "\n" if "\n" in options_str else ","
2218+
options = [opt.strip() for opt in options_str.split(sep) if opt.strip()]
2219+
2220+
if not isinstance(options, (list, tuple)):
2221+
return "Error: Poll options must be a list of strings."
2222+
2223+
raw_options = options
2224+
normalized_options: List[str] = []
2225+
for opt in raw_options:
2226+
if isinstance(opt, dict):
2227+
# Try common keys used by LLMs: "option", "text", "value", "title", "label"
2228+
val = None
2229+
for key in ("option", "text", "value", "title", "label"):
2230+
if key in opt and opt[key] is not None:
2231+
val = str(opt[key]).strip()
2232+
break
2233+
if val is None:
2234+
# Pick the first non-empty value in the dict
2235+
for v in opt.values():
2236+
if v is not None and str(v).strip():
2237+
val = str(v).strip()
2238+
break
2239+
opt_str = val if val is not None else ""
2240+
else:
2241+
opt_str = str(opt).strip()
2242+
2243+
if not opt_str:
2244+
return "Error: Poll options cannot be empty."
2245+
if len(opt_str) > 100:
2246+
return "Error: Each poll option cannot exceed 100 characters."
2247+
normalized_options.append(opt_str)
2248+
2249+
if len(normalized_options) < 2:
2250+
return "Error: Poll must have at least 2 options."
2251+
if len(normalized_options) > 10:
2252+
return "Error: Poll can have at most 10 options."
2253+
2254+
if len(set(normalized_options)) != len(normalized_options):
2255+
return "Error: Poll options must be unique."
2256+
2257+
# Parse close date if provided
2258+
close_date_obj = None
2259+
if close_date:
2260+
try:
2261+
close_date_obj = datetime.fromisoformat(close_date.replace("Z", "+00:00"))
2262+
except ValueError:
2263+
return "Invalid close_date format. Use YYYY-MM-DD HH:MM:SS format."
2264+
21972265
cl = get_client(account)
2266+
await ensure_connected(cl)
21982267
entity = await resolve_entity(chat_id, cl)
21992268

22002269
if is_chat_allowlist_enabled() and not is_chat_allowed(chat_id, entity):
@@ -2207,30 +2276,16 @@ async def create_poll(
22072276
chat_id=chat_id,
22082277
)
22092278

2210-
# Validate options
2211-
if len(options) < 2:
2212-
return "Error: Poll must have at least 2 options."
2213-
if len(options) > 10:
2214-
return "Error: Poll can have at most 10 options."
2215-
2216-
# Parse close date if provided
2217-
close_date_obj = None
2218-
if close_date:
2219-
try:
2220-
close_date_obj = datetime.fromisoformat(close_date.replace("Z", "+00:00"))
2221-
except ValueError:
2222-
return f"Invalid close_date format. Use YYYY-MM-DD HH:MM:SS format."
2223-
22242279
# Create the poll using InputMediaPoll with SendMediaRequest
22252280
from telethon.tl.types import InputMediaPoll, Poll, PollAnswer, TextWithEntities
22262281
import random
22272282

22282283
poll = Poll(
22292284
id=random.randint(0, 2**63 - 1),
2230-
question=TextWithEntities(text=question, entities=[]),
2285+
question=TextWithEntities(text=question_text, entities=[]),
22312286
answers=[
22322287
PollAnswer(text=TextWithEntities(text=option, entities=[]), option=bytes([i]))
2233-
for i, option in enumerate(options)
2288+
for i, option in enumerate(normalized_options)
22342289
],
22352290
# Telethon 1.44 made `hash` a required argument on Poll. It caches
22362291
# server-side results, so a poll being created sends 0.

0 commit comments

Comments
 (0)