A modern, easy-to-use, feature-rich async-ready API wrapper for Discord written in Python
—
Discord's native poll system allows creating interactive polls with multiple choice answers, emojis, and automatic vote counting. Disnake provides full support for creating, managing, and responding to polls.
Represents the content of poll questions and answers, supporting both text and emoji elements.
class PollMedia:
def __init__(
self,
text: str,
emoji: Optional[Union[Emoji, PartialEmoji, str]] = None
):
"""
Create poll media (question or answer content).
Parameters:
- text: Text content for the media
- emoji: Optional emoji to display with the text
"""
@property
def text(self) -> Optional[str]:
"""Text content of the media."""
@property
def emoji(self) -> Optional[Union[Emoji, PartialEmoji]]:
"""Emoji associated with the media."""Represents a single answer option in a poll with vote tracking.
class PollAnswer:
def __init__(self, *, data: PollAnswerPayload):
"""
Poll answer option.
Parameters:
- data: Raw answer data from Discord API
"""
@property
def id(self) -> int:
"""Answer ID."""
@property
def media(self) -> PollMedia:
"""Media content for this answer."""
@property
def vote_count(self) -> int:
"""Number of votes this answer has received."""
@property
def me_voted(self) -> bool:
"""Whether the current user voted for this answer."""
def get_voters(self, *, limit: Optional[int] = None, after: Optional[Snowflake] = None) -> PollAnswerIterator:
"""
Get users who voted for this answer.
Parameters:
- limit: Maximum number of voters to retrieve
- after: Get voters after this user ID
Returns:
Iterator for poll answer voters
"""Complete poll object with question, answers, and configuration.
class Poll:
def __init__(self, *, data: PollPayload, state: ConnectionState):
"""
Discord poll instance.
Parameters:
- data: Raw poll data from Discord API
- state: Connection state for API calls
"""
@property
def question(self) -> PollMedia:
"""Poll question content."""
@property
def answers(self) -> List[PollAnswer]:
"""All answer options for the poll."""
@property
def expiry(self) -> Optional[datetime]:
"""When the poll expires and stops accepting votes."""
@property
def allow_multiselect(self) -> bool:
"""Whether users can select multiple answers."""
@property
def layout_type(self) -> PollLayoutType:
"""Layout type for displaying the poll."""
@property
def total_vote_count(self) -> int:
"""Total number of votes across all answers."""
@property
def is_finalized(self) -> bool:
"""Whether the poll has been finalized (expired or manually ended)."""
def get_answer(self, answer_id: int) -> Optional[PollAnswer]:
"""
Get a specific answer by ID.
Parameters:
- answer_id: Answer ID to look up
Returns:
PollAnswer if found, None otherwise
"""
async def end(self) -> Message:
"""
End the poll immediately.
Returns:
Updated message with finalized poll
"""
@classmethod
def create(
cls,
question: Union[str, PollMedia],
*answers: Union[str, PollMedia],
duration: Optional[Union[int, timedelta]] = None,
allow_multiselect: bool = False,
) -> PollCreatePayload:
"""
Create poll data for sending in a message.
Parameters:
- question: Poll question text or media
- answers: Answer options (2-10 answers required)
- duration: Poll duration in hours (1-168) or timedelta
- allow_multiselect: Whether to allow multiple answer selection
Returns:
Poll creation payload for message sending
"""import disnake
from disnake import Poll, PollMedia
@bot.slash_command(description="Create a simple poll")
async def poll(
inter: disnake.ApplicationCommandInteraction,
question: str = disnake.Param(description="Poll question"),
option1: str = disnake.Param(description="First option"),
option2: str = disnake.Param(description="Second option"),
option3: str = disnake.Param(description="Third option", default=None),
option4: str = disnake.Param(description="Fourth option", default=None),
duration: int = disnake.Param(description="Duration in hours (1-168)", default=24)
):
# Collect non-empty options
options = [option1, option2]
if option3:
options.append(option3)
if option4:
options.append(option4)
# Create poll
poll_data = Poll.create(
question=question,
*options,
duration=duration,
allow_multiselect=False
)
await inter.response.send_message(poll=poll_data)
@bot.slash_command(description="Create a poll with emojis")
async def emoji_poll(inter: disnake.ApplicationCommandInteraction):
poll_data = Poll.create(
question=PollMedia("What's your favorite fruit?", "🍎"),
PollMedia("Apple", "🍎"),
PollMedia("Banana", "🍌"),
PollMedia("Orange", "🍊"),
PollMedia("Grapes", "🍇"),
duration=48,
allow_multiselect=True
)
await inter.response.send_message(
"Vote for your favorite fruits! (You can pick multiple)",
poll=poll_data
)from datetime import timedelta
@bot.slash_command(description="Create a multi-select poll")
async def multiselect_poll(inter: disnake.ApplicationCommandInteraction):
poll_data = Poll.create(
question="Which programming languages do you know?",
"Python",
"JavaScript",
"Java",
"C++",
"Go",
"Rust",
duration=timedelta(days=3),
allow_multiselect=True
)
await inter.response.send_message(
"Select all languages you're familiar with:",
poll=poll_data
)
@bot.slash_command(description="Create a timed poll")
async def quick_poll(inter: disnake.ApplicationCommandInteraction):
poll_data = Poll.create(
question="Should we start the meeting now?",
"Yes, let's start",
"Wait 5 more minutes",
"Postpone to later",
duration=1 # 1 hour only
)
await inter.response.send_message(
"Quick decision needed:",
poll=poll_data
)@bot.event
async def on_message(message):
# Check if message has a poll
if message.poll:
print(f"Poll found: {message.poll.question.text}")
print(f"Total votes: {message.poll.total_vote_count}")
for answer in message.poll.answers:
print(f" {answer.media.text}: {answer.vote_count} votes")
@bot.slash_command(description="Get poll results")
async def poll_results(
inter: disnake.ApplicationCommandInteraction,
message_id: str = disnake.Param(description="Message ID containing the poll")
):
try:
message = await inter.channel.fetch_message(int(message_id))
if not message.poll:
await inter.response.send_message("That message doesn't contain a poll.", ephemeral=True)
return
poll = message.poll
embed = disnake.Embed(
title="Poll Results",
description=f"**{poll.question.text}**",
color=disnake.Color.blue()
)
if poll.is_finalized:
embed.add_field(name="Status", value="✅ Finalized", inline=True)
elif poll.expiry:
embed.add_field(name="Expires", value=f"<t:{int(poll.expiry.timestamp())}:R>", inline=True)
embed.add_field(name="Total Votes", value=str(poll.total_vote_count), inline=True)
# Add results for each answer
for i, answer in enumerate(poll.answers, 1):
percentage = (answer.vote_count / poll.total_vote_count * 100) if poll.total_vote_count > 0 else 0
value = f"{answer.vote_count} votes ({percentage:.1f}%)"
if answer.media.emoji:
name = f"{answer.media.emoji} {answer.media.text}"
else:
name = f"{i}. {answer.media.text}"
embed.add_field(name=name, value=value, inline=False)
await inter.response.send_message(embed=embed)
except (ValueError, disnake.NotFound):
await inter.response.send_message("Message not found.", ephemeral=True)
@bot.slash_command(description="End a poll early")
async def end_poll(
inter: disnake.ApplicationCommandInteraction,
message_id: str = disnake.Param(description="Message ID containing the poll")
):
try:
message = await inter.channel.fetch_message(int(message_id))
if not message.poll:
await inter.response.send_message("That message doesn't contain a poll.", ephemeral=True)
return
if message.poll.is_finalized:
await inter.response.send_message("That poll is already finalized.", ephemeral=True)
return
# End the poll
updated_message = await message.poll.end()
await inter.response.send_message(f"Poll ended! Final results posted in {message.jump_url}")
except (ValueError, disnake.NotFound):
await inter.response.send_message("Message not found.", ephemeral=True)
except disnake.Forbidden:
await inter.response.send_message("I don't have permission to end that poll.", ephemeral=True)@bot.slash_command(description="See who voted for a specific answer")
async def poll_voters(
inter: disnake.ApplicationCommandInteraction,
message_id: str = disnake.Param(description="Message ID containing the poll"),
answer_number: int = disnake.Param(description="Answer number (1, 2, 3, etc.)")
):
try:
message = await inter.channel.fetch_message(int(message_id))
if not message.poll:
await inter.response.send_message("That message doesn't contain a poll.", ephemeral=True)
return
poll = message.poll
if answer_number < 1 or answer_number > len(poll.answers):
await inter.response.send_message(f"Invalid answer number. Must be 1-{len(poll.answers)}.", ephemeral=True)
return
answer = poll.answers[answer_number - 1]
voters = []
# Get up to 25 voters for this answer
async for user in answer.get_voters(limit=25):
voters.append(user.display_name)
if not voters:
await inter.response.send_message(f"No votes yet for: {answer.media.text}", ephemeral=True)
return
embed = disnake.Embed(
title="Poll Voters",
description=f"**Answer:** {answer.media.text}\n**Votes:** {answer.vote_count}",
color=disnake.Color.green()
)
voters_text = "\n".join(voters)
if len(voters) == 25:
voters_text += "\n... and more"
embed.add_field(name="Voters", value=voters_text, inline=False)
await inter.response.send_message(embed=embed)
except (ValueError, disnake.NotFound):
await inter.response.send_message("Message not found.", ephemeral=True)Install with Tessl CLI
npx tessl i tessl/pypi-disnake