1
0
Fork 0
mirror of synced 2024-05-18 11:32:26 +12:00
Bonfire/cogs/raffle.py
2017-06-07 03:30:19 -05:00

270 lines
11 KiB
Python

from discord.ext import commands
import discord
from . import utils
import random
import pendulum
import re
import asyncio
import traceback
import rethinkdb as r
class Raffle:
def __init__(self, bot):
self.bot = bot
self.bot.loop.create_task(self.raffle_task())
async def raffle_task(self):
while True:
try:
await self.check_raffles()
except Exception as error:
with open("error_log", 'a') as f:
traceback.print_tb(error.__traceback__, file=f)
print('{0.__class__.__name__}: {0}'.format(error), file=f)
await asyncio.sleep(900)
async def check_raffles(self):
# This is used to periodically check the current raffles, and see if they have ended yet
# If the raffle has ended, we'll pick a winner from the entrants
raffles = self.bot.db.load('raffles')
if raffles is None:
return
for raffle in raffles:
server = self.bot.get_guild(int(raffle['server_id']))
title = raffle['title']
entrants = raffle['entrants']
raffle_id = raffle['id']
# Check to see if this cog can find the server in question
if server is None:
await self.bot.db.query(r.table('raffles').get(raffle_id).delete())
continue
now = pendulum.utcnow()
expires = pendulum.parse(raffle['expires'])
# Now lets compare and see if this raffle has ended, if not just continue
if expires > now:
continue
# Make sure there are actually entrants
if len(entrants) == 0:
fmt = 'Sorry, but there were no entrants for the raffle `{}`!'.format(title)
else:
winner = None
count = 0
while winner is None:
winner = server.get_member(int(random.SystemRandom().choice(entrants)))
# Lets make sure we don't get caught in an infinite loop
# Realistically having more than 50 random entrants found that aren't in the server anymore
# Isn't something that should be an issue, but better safe than sorry
count += 1
if count >= 50:
break
if winner is None:
fmt = 'I couldn\'t find an entrant that is still in this server, for the raffle `{}`!'.format(title)
else:
fmt = 'The raffle `{}` has just ended! The winner is {}!'.format(title, winner.display_name)
channel_id = self.bot.db.load('server_settings', key=server.id,
pluck='notifications_channel') or server.id
channel = self.bot.get_channel(channel_id)
try:
await channel.send(fmt)
except (discord.Forbidden, AttributeError):
pass
# No matter which one of these matches were met, the raffle has ended and we want to remove it
await self.bot.db.query(r.table('raffles').get(raffle_id).delete())
@commands.command()
@commands.guild_only()
@utils.custom_perms(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"""
r_filter = {'server_id': str(ctx.message.guild.id)}
raffles = self.bot.db.load('raffles', table_filter=r_filter)
if raffles is None:
await ctx.send("There are currently no raffles setup on this server!")
return
# For EVERY OTHER COG, when we get one result, it is nice to have it return that exact object
# This is the only cog where that is different, so just to make this easier lets throw it
# back in a one-indexed list, for easier parsing
if isinstance(raffles, dict):
raffles = [raffles]
fmt = "\n\n".join("**Raffle:** {}\n**Title:** {}\n**Total Entrants:** {}\n**Ends:** {} UTC".format(
num + 1,
raffle['title'],
len(raffle['entrants']),
raffle['expires']) for num, raffle in enumerate(raffles))
await ctx.send(fmt)
@commands.group(invoke_without_command=True)
@commands.guild_only()
@utils.custom_perms(send_messages=True)
async def raffle(self, ctx, raffle_id: int = 0):
"""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!"""
# Lets let people use 1 - (length of raffles) and handle 0 base ourselves
raffle_id -= 1
r_filter = {'server_id': str(ctx.message.guild.id)}
author = ctx.message.author
raffles = self.bot.db.load('raffles', table_filter=r_filter)
if raffles is None:
await ctx.send("There are currently no raffles setup on this server!")
return
if isinstance(raffles, list):
raffle_count = len(raffles)
elif isinstance(raffles, dict):
raffles = [raffles]
raffle_count = 1
# There is only one raffle, so use the first's info
if raffle_count == 1:
entrants = raffles[0]['entrants']
# Lets make sure that the user hasn't already entered the raffle
if str(author.id) in entrants:
await ctx.send("You have already entered this raffle!")
return
entrants.append(str(author.id))
update = {
'entrants': entrants,
'id': raffles[0]['id']
}
self.bot.db.save('raffles', update)
await ctx.send("{} you have just entered the raffle!".format(author.mention))
# Otherwise, make sure the author gave a valid raffle_id
elif raffle_id in range(raffle_count - 1):
entrants = raffles[raffle_id]['entrants']
# Lets make sure that the user hasn't already entered the raffle
if str(author.id) in entrants:
await ctx.send("You have already entered this raffle!")
return
entrants.append(str(author.id))
# Since we have no good thing to filter things off of, lets use the internal rethinkdb id
update = {
'entrants': entrants,
'id': raffles[0]['id']
}
self.bot.db.save('raffles', update)
await ctx.send("{} you have just entered the raffle!".format(author.mention))
else:
fmt = "Please provide a valid raffle ID, as there are more than one setup on the server! " \
"There are currently `{}` raffles running, use {}raffles to view the current running raffles".format(
raffle_count, ctx.prefix)
await ctx.send(fmt)
@raffle.command(pass_context=True, name='create', aliases=['start', 'begin', 'add'])
@commands.guild_only()
@utils.custom_perms(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.message.author
server = ctx.message.guild
channel = ctx.message.channel
now = pendulum.utcnow()
await ctx.send(
"Ready to start a new raffle! Please respond with the title you would like to use for this raffle!")
check = lambda m: m.author == author and m.channel == channel
try:
msg = await self.bot.wait_for('message', check=check, 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 months"
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("\d+ (minutes?|hours?|days?|weeks?|months?)", m.content.lower()) is not None
else:
return False
try:
msg = await self.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("\d+ (minutes?|hours?|days?|weeks?|months?)", msg.content.lower()).group(0).split(' ')
# 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 and (num < 10 or num > 129600):
await ctx.send(
"Length provided out of range! The minimum for this is 10 minutes, and the maximum is 3 months")
return
elif "hour" in term and num > 2160:
await ctx.send(
"Length provided out of range! The minimum for this is 10 minutes, and the maximum is 3 months")
return
elif "day" in term and num > 90:
await ctx.send(
"Length provided out of range! The minimum for this is 10 minutes, and the maximum is 3 months")
return
elif "week" in term and num > 12:
await ctx.send(
"Length provided out of range! The minimum for this is 10 minutes, and the maximum is 3 months")
return
elif "month" in term and num > 3:
await ctx.send(
"Length provided out of range! The minimum for this is 10 minutes, and the maximum is 3 months")
return
# Pendulum only accepts the plural version of terms, lets make sure this is added
term = term if term.endswith('s') else term + 's'
# If we're in the range, lets just pack this in a dictionary we can pass to set the time we want, then set that
payload = {term: num}
expires = now.add(**payload)
# Now we're ready to add this as a new raffle
entry = {
'title': title,
'expires': expires.to_datetime_string(),
'entrants': [],
'author': str(author.id),
'server_id': str(server.id)
}
# We don't want to pass a filter to this, because we can have multiple raffles per server
self.bot.db.save('raffles', entry)
await ctx.send("I have just saved your new raffle!")
def setup(bot):
bot.add_cog(Raffle(bot))