1
0
Fork 0
mirror of synced 2024-04-29 10:12:29 +12:00
Bonfire/cogs/raffle.py

206 lines
6.4 KiB
Python

from discord.ext import commands
from collections import defaultdict
import utils
import discord
import re
import asyncio
import random
class Raffle(commands.Cog):
"""Used to hold custom raffles"""
raffles = defaultdict(list)
def create_raffle(self, ctx, title, num):
raffle = GuildRaffle(ctx, title, num)
self.raffles[ctx.guild.id].append(raffle)
raffle.start()
@commands.command(name="raffles")
@commands.guild_only()
@utils.can_run(send_messages=True)
async def _raffles(self, ctx):
"""Used to print the current running raffles on the server
EXAMPLE: !raffles
RESULT: A list of the raffles setup on this server"""
raffles = self.raffles[ctx.guild.id]
if len(raffles) == 0:
await ctx.send("There are currently no raffles setup on this server!")
return
embed = discord.Embed(title=f"Raffles in {ctx.guild.name}")
for num, raffle in enumerate(raffles):
embed.add_field(
name=f"Raffle {num + 1}",
value=f"Title: {raffle.title}\n"
f"Total Entrants: {len(raffle.entrants)}\n"
f"Ends in {raffle.remaining}",
inline=False,
)
await ctx.send(embed=embed)
@commands.group(invoke_without_command=True)
@commands.guild_only()
@utils.can_run(send_messages=True)
async def raffle(self, ctx, raffle_id: int):
"""Used to enter a raffle running on this server
If there is more than one raffle running, provide an ID of the raffle you want to enter
EXAMPLE: !raffle 1
RESULT: You've entered the first raffle!"""
try:
raffle = self.raffles[ctx.guild.id][raffle_id - 1]
except IndexError:
await ctx.send(f"I could not find a raffle for ID {raffle_id}")
await self._raffles.invoke(ctx)
else:
if raffle.enter(ctx.author):
await ctx.send(f"You have just joined the raffle {raffle['title']}")
else:
await ctx.send("You have already entered this raffle!")
@raffle.command(name="create", aliases=["start", "begin", "add"])
@commands.guild_only()
@utils.can_run(kick_members=True)
async def raffle_create(self, ctx):
"""This is used in order to create a new server raffle
EXAMPLE: !raffle create
RESULT: A follow-along for setting up a new raffle"""
author = ctx.author
channel = ctx.channel
await ctx.send(
"Ready to start a new raffle! Please respond with the title you would like to use for this raffle!"
)
try:
msg = await ctx.bot.wait_for(
"message",
check=lambda m: m.author == author and m.channel == channel,
timeout=120,
)
except asyncio.TimeoutError:
await ctx.send("You took too long! >:c")
return
title = msg.content
fmt = (
"Alright, your new raffle will be titled:\n\n{}\n\nHow long would you like this raffle to run for? "
"The format should be [number] [length] for example, `2 days` or `1 hour` or `30 minutes` etc. "
"The minimum for this is 10 minutes, and the maximum is 3 days"
)
await ctx.send(fmt.format(title))
# Our check to ensure that a proper length of time was passed
def check(m):
if m.author == author and m.channel == channel:
return (
re.search(r"\d+ (minutes?|hours?|days?)", m.content.lower())
is not None
)
else:
return False
try:
msg = await ctx.bot.wait_for("message", timeout=120, check=check)
except asyncio.TimeoutError:
await ctx.send("You took too long! >:c")
return
# Lets get the length provided, based on the number and type passed
num, term = re.search(
r"(\d+) (minutes?|hours?|days?)", msg.content.lower()
).groups()
# This should be safe to convert, we already made sure with our check earlier this would match
num = int(num)
# Now lets ensure this meets our min/max
if "minute" in term:
num = num * 60
elif "hour" in term:
num = num * 60 * 60
elif "day" in term:
num = num * 24 * 60 * 60
if not 60 < num < 259200:
await ctx.send(
"Length provided out of range! The minimum for this is 10 minutes, and the maximum is 3 days"
)
return
self.create_raffle(ctx, title, num)
await ctx.send("I have just saved your new raffle!")
def setup(bot):
bot.add_cog(Raffle(bot))
class GuildRaffle:
def __init__(self, ctx, title, expires):
self._ctx = ctx
self.title = title
self.expires = expires
self.entrants = set()
self.task = None
@property
def guild(self):
return self._ctx.guild
@property
def db(self):
return self._ctx.bot.db
def start(self):
self.task = self._ctx.bot.loop.call_later(self.expires, self.end_raffle())
@property
def remaining(self):
minutes, seconds = divmod(self.task.when(), 60)
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
return f"{days} days, {hours} hours, {minutes} minutes, {seconds} seconds"
def enter(self, entrant):
self.entrants.add(entrant)
async def end_raffle(self):
entrants = {e for e in self.entrants if self.guild.get_member(e.id)}
query = """
SELECT
COALESCE(raffle_alerts, default_alerts) AS channel,
FROM
guilds
WHERE
id = $1
AND
COALESCE(raffle_alerts, default_alerts) IS NOT NULL
"""
channel = None
result = await self.db.fetch(query, self.guild.id)
if result:
channel = self.guild.get_channel(result["channel"])
if channel is None:
return
if entrants:
winner = random.SystemRandom().choice(self.entrants)
await channel.send(
f"The winner of the raffle `{self.title}` is {winner.mention}! Congratulations!"
)
else:
await channel.send(
f"There were no entrants to the raffle `{self.title}`, who are in this server currently!"
)