You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Discord.py 抽奖结束命令实现技术求助

Hey there! Let's figure out how to implement that end command for your giveaway bot. The main issue with your current setup is that the asyncio.sleep(timer) call in the start command is blocking, and there's no way to interrupt it early. Here's a step-by-step solution using asyncio Tasks and expanded database storage to fix this:


1. Update Your Database Schema

First, your storingStuff table needs more fields to store all critical giveaway details (since we'll need to access them independently in the end command). Run these SQL statements once to modify your table:

ALTER TABLE storingStuff ADD COLUMN prize TEXT;
ALTER TABLE storingStuff ADD COLUMN winners_count INTEGER;
ALTER TABLE storingStuff ADD COLUMN channel_id INTEGER;
ALTER TABLE storingStuff ADD COLUMN end_timestamp INTEGER;

2. Track Giveaway Tasks in Your Bot

Add a dictionary to your main Bot instance to map giveaway message IDs to their running asyncio Tasks. This lets us cancel the sleep/end process early:

# In your main bot file (e.g., main.py)
client = commands.Bot(command_prefix='!', intents=discord.Intents.all())
client.giveaway_tasks = {}  # Key: giveaway message ID, Value: asyncio.Task

3. Modify the Start Command to Use Tasks

We'll split the giveaway logic into two parts: starting the giveaway, and a separate async function that handles the sleep and final winner selection. This function will be wrapped in a Task so we can cancel it later.

Here's the updated Start cog code:

import asyncio
import time
import random
import datetime
import discord
from discord.ext import commands
import aiosqlite

class Start(commands.Cog):
    def __init__(self, client):
        self.client = client

    def convert(self, timer):
        pos = ["s", "m", "h", "d"]
        time_dict = {"s" : 1, "m" : 60, "h" : 3600, "d" : 3600*24}
        unit = timer[-1]
        if unit not in pos:
            return -1
        try:
            val = int(timer[:-1])
        except:
            return -2
        return val * time_dict[unit]

    async def end_giveaway(self, ctx, gaw_msg_id, prize, winners_count, end_timestamp):
        try:
            # Wait until the giveaway ends (or get cancelled early)
            await asyncio.sleep(end_timestamp - time.time())
        except asyncio.CancelledError:
            # This triggers when the end command cancels the task
            pass

        # Fetch the giveaway message
        try:
            gaw_msg = await ctx.channel.fetch_message(gaw_msg_id)
        except discord.NotFound:
            return  # Message was deleted, nothing to do

        # Get valid participants (exclude bots)
        users = await gaw_msg.reactions[0].users().flatten()
        users = [user for user in users if not user.bot]

        # Select winners
        users_mention = []
        if len(users) >= winners_count:
            winners = random.sample(users, winners_count)
            users_mention = [winner.mention for winner in winners]
        else:
            users_mention = ["No valid participants"]

        displayed_winners = ",".join(users_mention)
        endembed = discord.Embed(
            title=f"{prize}",
            description=f"Winner: {displayed_winners}\nHosted by: {ctx.author.mention}",
            color=ctx.author.color,
            timestamp=datetime.datetime.utcfromtimestamp(end_timestamp)
        )
        endembed.set_footer(text='Ended at \u200b')
        await gaw_msg.edit(
            content="<:done:939940228746072096> **GIVEAWAY ENDED** <:done:939940228746072096>",
            embed=endembed
        )
        await ctx.send(f"Congratulations {displayed_winners}! You won the **{prize}**.\n{gaw_msg.jump_url}")

        # Clean up: remove task and database entry
        if gaw_msg_id in self.client.giveaway_tasks:
            del self.client.giveaway_tasks[gaw_msg_id]
        async with aiosqlite.connect("main.db") as db:
            await db.execute(f'DELETE FROM storingStuff WHERE msgID = {gaw_msg_id}')
            await db.commit()

    @commands.command()
    async def start(self, ctx, duration, winners: str, *, prize):
        timer = self.convert(duration)
        # Validate input
        if timer == -1:
            await ctx.send("Invalid time unit! Use `s` (seconds), `m` (minutes), `h` (hours), or `d` (days).")
            return
        if timer == -2:
            await ctx.send("Invalid time value! Enter a number followed by a unit (e.g., `10m`).")
            return

        try:
            winners_count = int(winners.replace("w",""))
            if winners_count <= 0:
                await ctx.send("Number of winners must be at least 1!")
                return
        except ValueError:
            await ctx.send("Invalid winners format! Use something like `1w` or `3w`.")
            return

        await ctx.message.delete()
        end_timestamp = int(time.time() + timer)
        # Create giveaway embed
        embed = discord.Embed(
            title=f"{prize}",
            description=f'React with 🎉 to enter\nEnds: <t:{end_timestamp}:R> (<t:{end_timestamp}>)\nHosted by {ctx.author.mention}\n',
            color=ctx.author.color,
            timestamp=datetime.datetime.utcfromtimestamp(end_timestamp)
        )
        embed.set_footer(text=f'Winners : {winners_count} | Ends at \u200b')
        gaw_msg = await ctx.send(
            content="<:spadess:939938117736091678> **GIVEAWAY** <:spadess:939938117736091678>",
            embed=embed
        )
        await gaw_msg.add_reaction("🎉")

        # Store giveaway details in database
        async with aiosqlite.connect("main.db") as db:
            await db.execute('''
                INSERT INTO storingStuff (msgID, guildID, prize, winners_count, channel_id, end_timestamp)
                VALUES (?, ?, ?, ?, ?, ?)
            ''', (gaw_msg.id, ctx.guild.id, prize, winners_count, ctx.channel.id, end_timestamp))
            await db.commit()

        # Start the giveaway end task and store it
        task = asyncio.create_task(
            self.end_giveaway(ctx, gaw_msg.id, prize, winners_count, end_timestamp)
        )
        self.client.giveaway_tasks[gaw_msg.id] = task

def setup(client):
    client.add_cog(Start(client))

4. Implement the End Command

Create a new Cog to handle ending giveaways early. This command will fetch the giveaway details from the database, cancel the running task, and trigger the winner selection process:

import discord
from discord.ext import commands
import aiosqlite

class End(commands.Cog):
    def __init__(self, client):
        self.client = client

    @commands.command()
    async def end(self, ctx, msg_id: int):
        # Fetch giveaway details from database
        async with aiosqlite.connect("main.db") as db:
            cursor = await db.execute('''
                SELECT prize, winners_count, channel_id, end_timestamp, guildID
                FROM storingStuff WHERE msgID = ?
            ''', (msg_id,))
            data = await cursor.fetchone()
            if not data:
                await ctx.send("No giveaway found with that message ID!")
                return

            prize, winners_count, channel_id, end_timestamp, guild_id = data

        # Verify user is in the same guild as the giveaway
        if ctx.guild.id != guild_id:
            await ctx.send("You can only end giveaways hosted in this server!")
            return

        # Fetch the giveaway channel and message
        try:
            channel = self.client.get_channel(channel_id) or await self.client.fetch_channel(channel_id)
            gaw_msg = await channel.fetch_message(msg_id)
        except discord.NotFound:
            await ctx.send("Giveaway message not found!")
            return

        # Check if giveaway is already ended
        if "GIVEAWAY ENDED" in gaw_msg.content:
            await ctx.send("This giveaway has already ended!")
            return

        # Cancel the running task if it exists
        if msg_id in self.client.giveaway_tasks:
            task = self.client.giveaway_tasks[msg_id]
            task.cancel()
            await ctx.send("Ending the giveaway early...")
        else:
            # If task doesn't exist (e.g., it finished already), trigger end logic manually
            await self.client.get_cog("Start").end_giveaway(
                ctx, msg_id, prize, winners_count, end_timestamp
            )
            await ctx.send("Giveaway ended!")

def setup(client):
    client.add_cog(End(client))

Key Explanations:

  • Task Management: Using asyncio.create_task lets us run the giveaway end logic in the background. We can cancel this task at any time with task.cancel(), which triggers the asyncio.CancelledError we catch in end_giveaway.
  • Database Storage: Storing all giveaway details (prize, winners count, channel ID) lets the end command operate independently without needing the original start command context.
  • Error Handling: Added checks for invalid inputs, missing messages, and already-ended giveaways to make the bot more robust.

To use this, just add both cogs to your bot, update your database schema, and test with commands like:

  • !start 10m 1w Free Nitro (starts a 10-minute giveaway for 1 winner)
  • !end 123456789012345678 (ends the giveaway with the specified message ID)

内容的提问来源于stack exchange,提问作者user18323078

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 14:24:06