"""This cog allows users to interact with their `Matches <https://fancyjesse.com/projects/matches>`_ account."""
import logging
import discord
from discord.ext import commands
from utils import checks, quickembed
from utils.fjclasses import DiscordUser, Match, Superstar
logger = logging.getLogger(__name__)
[docs]class Matches(commands.Cog):
"""The Matches cog class."""
def __init__(self, bot):
self.bot = bot
@commands.command(name='current-match', aliases=['currentmatch'])
async def current_match_info(self, ctx):
"""Display details for the current `Match`.
:param ctx: The invocation context.
"""
user = DiscordUser(ctx.author)
rows = user.search_match_by_current()
if rows:
embed = Match(rows[0].id).info_embed()
else:
embed = quickembed.error(desc='No match found', user=user)
await ctx.send(embed=embed)
@commands.command(name='last-match', aliases=['lastmatch'])
async def recent_match_info(self, ctx):
"""Displays details for the last completed `Match`.
:param ctx: The invocation context.
"""
user = DiscordUser(ctx.author)
rows = user.search_match_by_recent_completed()
if rows:
embed = Match(rows[0].id).info_embed()
else:
embed = quickembed.error(desc='No match found', user=user)
await ctx.send(embed=embed)
@commands.command(name='events', aliases=['ppv'])
async def upcoming_events(self, ctx):
"""Displays a list of upcoming PPV events in order.
:param ctx: The invocation context.
"""
user = DiscordUser(ctx.author)
ppvs = user.future_events(ppv_check=1)
embed = quickembed.info(desc='Upcoming Events (PT)', user=user)
embed.add_field(
name='\u200b',
value='\n'.join(
['{} - **{}**'.format(e['date_time'], e['name']) for e in ppvs]
),
)
await ctx.send(embed=embed)
@commands.command(name='info', aliases=['bio', 'superstar', 'lookup'])
async def superstar_info(self, ctx, *, name):
"""Displays the `Superstar`'s information.
.. Note:
If the `name` matches multiple `Superstar`s, a list of the matching `Superstar`s is presented.
The requester would then have to select one of the options available.
:param ctx: The invocation context.
:param name: The name of the `Superstar`.
"""
user = DiscordUser(ctx.author)
superstar_list = user.search_superstar_by_name(name)
if not superstar_list:
embed = quickembed.error(
desc="Unable to find Superstars matching '{}'".format(name), user=user
)
else:
if len(superstar_list) > 1:
msg = 'Select Superstar from List ...\n```'
for i, e in enumerate(superstar_list):
msg = msg + '{}. {}\n'.format(i + 1, e.name)
msg = msg + '```'
await ctx.send(embed=quickembed.question(desc=msg, user=user))
response = await self.bot.wait_for(
'message', check=checks.is_number(ctx.author), timeout=15.0
)
try:
index = int(response.content)
embed = Superstar(superstar_list[index - 1].id).info_embed()
except (ValueError, IndexError):
embed = quickembed.error(desc='Invalid index', user=user)
else:
embed = Superstar(superstar_list[0].id).info_embed()
await ctx.send(embed=embed)
@commands.command(name='birthdays')
async def superstar_birthdays(self, ctx):
"""Display a list of upcoming `Superstar` birthdays.
:param ctx: The invocation context.
"""
user = DiscordUser(ctx.author)
bdays = user.superstar_birthday_upcoming()
embed = quickembed.info(desc='Upcoming Birthdays', user=user)
embed.add_field(
name='\u200b',
value='{}'.format(
'\n'.join(['[{}] - {}'.format(b['dob'], b['name']) for b in bdays])
),
)
await ctx.send(embed=embed)
@commands.command(name='leaderboard1', aliases=['top1'])
async def leaderboard_season1(self, ctx):
"""Displays the leaderboard for Season 1.
:param ctx: The invocation context.
"""
user = DiscordUser(ctx.author)
lb = user.leaderboard(season=1)
embed = discord.Embed(description='Season 1', color=0x0080FF)
embed.set_author(
name='Leaderboard',
url='https://fancyjesse.com/projects/matches/leaderboard?season_id=1',
icon_url=self.bot.user.avatar_url,
)
lb = [
'{}. {} ({:,})'.format(i + 1, l['username'], l['total_points'])
for i, l in enumerate(lb[:10])
]
embed.add_field(
name='\u200b', value='\n'.join(lb) if lb else 'Nothing found', inline=True
)
await ctx.send(embed=embed)
@commands.command(name='leaderboard2', aliases=['top2'])
async def leaderboard_season2(self, ctx):
"""Displays the leaderboard for Season 2.
:param ctx: The invocation context.
"""
user = DiscordUser(ctx.author)
lb = user.leaderboard(season=2)
embed = discord.Embed(description='Season 2', color=0x0080FF)
embed.set_author(
name='Leaderboard',
url='https://fancyjesse.com/projects/matches/leaderboard?season_id=2',
icon_url=self.bot.user.avatar_url,
)
lb = [
'{}. {} ({:,})'.format(i + 1, l['username'], l['total_points'])
for i, l in enumerate(lb[:10])
]
embed.add_field(
name='\u200b', value='\n'.join(lb) if lb else 'Nothing found', inline=True
)
await ctx.send(embed=embed)
@commands.command(name='leaderboard3', aliases=['top', 'leaderboard', 'top3'])
async def leaderboard_season3(self, ctx):
"""Displays the leaderboard for Season 3.
:param ctx: The invocation context.
"""
user = DiscordUser(ctx.author)
lb = user.leaderboard(season=3)
embed = discord.Embed(description='Season 3', color=0x0080FF)
embed.set_author(
name='Leaderboard',
url='https://fancyjesse.com/projects/matches/leaderboard?season_id=3',
icon_url=self.bot.user.avatar_url,
)
lb = [
'{}. {} ({:,})'.format(i + 1, l['username'], l['total_points'])
for i, l in enumerate(lb[:10])
]
embed.add_field(
name='\u200b', value='\n'.join(lb) if lb else 'Nothing found', inline=True
)
await ctx.send(embed=embed)
@commands.command(name='titles', aliases=['champions', 'champs'], enabled=False)
async def current_champions(self, ctx):
"""TODO: Displays the current list of `Superstar`s with titles.
:param ctx: The invocation context.
"""
pass
@commands.command(name='rumble', aliases=['royalrumble'])
async def royalrumble_info(self, ctx):
"""Provides a quick login link to the Matches website's Royal Rumble page through DM.
.. Note:
The hyperlink can only be used once and within a short time frame before it expires.
:param ctx: The invocation context.
"""
# response = user.royalrumble_info() # TODO: check if available to enter
user = DiscordUser(ctx.author)
link = user.request_login_link()
link = link.replace('projects/matches?', 'projects/matches/royalrumble?')
msg = 'Join the rumble here! (link expires in 5 minutes)\n<{}>'.format(link)
await ctx.author.send(embed=quickembed.general(desc=msg, user=user))
embed = quickembed.success(user=user, desc='Rumble link DMed ;)')
await ctx.send(embed=embed)
@commands.command(name='joinrumble', enabled=False)
@checks.is_registered()
async def user_join_royalrumble(self, ctx):
"""TODO: Enters the user into the current Royal Rumble event by providing them an entry number.
:param ctx: The invocation context.
"""
user = DiscordUser(ctx.author)
response = user.join_royalrumble()
if response['success']:
embed = quickembed.success(
desc='Entry Number: `{}`'.format(response['message']), user=user
)
else:
embed = quickembed.error(desc=response['message'], user=user)
await ctx.send(embed=embed)
@commands.command(name='stats3', aliases=['stats', 'bal', 'points', 'profile'])
@checks.is_registered()
async def user_stats_season3(self, ctx):
"""Displays the user's stats for Season 3.
:param ctx: The invocation context.
"""
await ctx.send(embed=DiscordUser(ctx.author).stats_embed(season=3))
@commands.command(name='stats2', aliases=['points2', 'bal2'])
@checks.is_registered()
async def user_stats_season2(self, ctx):
"""Displays the user's stats for Season 2.
:param ctx: The invocation context.
"""
await ctx.send(embed=DiscordUser(ctx.author).stats_embed(season=2))
@commands.command(name='stats1', aliases=['points1', 'bal1'])
@checks.is_registered()
async def user_stats_season1(self, ctx):
"""Displays the user's stats for Season 1.
:param ctx: The invocation context.
"""
await ctx.send(embed=DiscordUser(ctx.author).stats_embed(season=1))
@commands.command(name='bets', aliases=['mybets', 'my-bets'])
@checks.is_registered()
async def user_current_bets(self, ctx):
"""Displays the user's current bets for `Match`es.
:param ctx: The invocation context.
"""
user = DiscordUser(ctx.author)
bets = user.current_bets()
if bets:
msg = "```{}```".format(
'\n'.join(
[
'Match {}\n\t{:,} points on {}\n\t'
'Potential Winnings: {:,} ({}%)'.format(
bet['match_id'],
bet['points'],
bet['contestants'],
bet['potential_cut_points'],
bet['potential_cut_pct'] * 100,
)
for bet in bets
]
)
)
embed = quickembed.general(desc='Current Bets', user=user)
embed.add_field(name='\u200b', value=msg, inline=False)
else:
embed = quickembed.error(desc='No current bets placed', user=user)
await ctx.send(embed=embed)
@commands.command(name='match', aliases=['match-info'])
async def match_info(self, ctx, match_id=None):
"""Display info the `Match`.
:param ctx: The invocation context.
:param match_id: The `Match` id.
"""
user = DiscordUser(ctx.author)
try:
match_id = int(match_id)
except Exception as e:
match_id = None
logger.debug('match_info: {}'.format(e))
msg = 'Invalid `!match` command\n`!match [match_id]`'
await ctx.send(embed=quickembed.error(desc=msg, user=user))
if match_id:
rows = user.search_match_by_id(match_id)
if rows:
await ctx.send(embed=Match(rows[0].id).info_embed())
else:
await ctx.send(
embed=quickembed.error(
desc='Match `{}` not found'.format(match_id), user=user
)
)
@commands.command(name='matches', aliases=['open-mmatches'])
@commands.cooldown(1, 60.0, commands.BucketType.user)
async def open_matches(self, ctx):
"""Lists the available `Match`es to bet on.
.. Note:
If the number of available `Match`es exceeds 5, they will be displayed with their `short view`.
If it does not exceed 5, their full details will be displayed as individual messages.
:param ctx: The invocation context.
"""
user = DiscordUser(ctx.author)
rows = user.search_match_by_open_bets()
if len(rows) > 5:
embed = quickembed.info(desc='Short View - Use `!match [id]` for full view')
embed.set_author(name='Open Bet Matches')
for row in rows:
match = Match(row.id)
embed.add_field(
name='[Match {}]'.format(match.id),
value='{}'.format(match.info_text_short()),
inline=True,
)
await ctx.send(embed=embed)
elif len(rows) > 0:
for row in rows:
await ctx.send(embed=Match(row.id).info_embed())
else:
await ctx.send(
embed=quickembed.error(desc='No open bet matches available', user=user)
)
@commands.command(name='bet', aliases=['placebet'])
@checks.is_registered()
async def place_match_bet(self, ctx, *args):
"""Places a `Match` bet.
.. Note:
A bet requires:
* The `bet amount`
* The `match_id`
* The `team`
The user can provide them sequentially:
!bet [bet_amount] [match_id] [team]
Or they can insert a `Superstar`'s name (`Match` contestant):
!bet [bet_amount] [contestant]
The `match_id` and `team` is found by cross-referencing the `Superstar` name with open-bet `Match`
contestants.
:param ctx: The invocation context.
:param args: Must be either `[bet_amount] [match_id] [team]` or `[bet_amount] [contestant]`
"""
user = DiscordUser(ctx.author)
bet = None
match_id = None
team = None
superstar_name = None
try:
bet = int(args[0].replace(',', ''))
if len(args) == 3 and args[1].isdigit() and args[2].isdigit():
match_id = int(args[1])
team = int(args[2])
elif len(args) > 1:
superstar_name = ' '.join(args[1:])
rows = user.search_match_by_open_bets_and_superstar_name(superstar_name)
match_id = rows[0].id if rows else False # use first match found
if not match_id:
embed = quickembed.error(
desc='Unable to find an open match for contestant `{}`'.format(
superstar_name
),
user=user,
)
await ctx.send(embed=embed)
return
else:
raise
except Exception as e:
logger.debug('place_match_bet: {}'.format(e))
msg = (
'Invalid `!bet` command\n'
'`!bet [bet_amount] [contestant]`\n'
'`!bet [bet_amount] [match_id] [team]`'
)
await ctx.send(embed=quickembed.error(desc=msg, user=user))
return
match = Match(match_id)
if not team and superstar_name:
team = match.team_by_contestant(superstar_name)
response = user.validate_bet(match_id, team, bet)
if response['success']:
question_embed = quickembed.question(
desc='[Y/N] Place this bet?', user=user
)
question_embed.add_field(
name='Info', value=match.info_text_short(), inline=False
)
question_embed.add_field(
name='Betting', value='{:,}'.format(bet), inline=True
)
question_embed.add_field(
name='Betting On', value=match.teams[team]['members'], inline=True
)
await ctx.send(embed=question_embed)
confirm = await self.bot.wait_for(
'message', check=checks.confirm(ctx.author), timeout=15.0
)
confirm.content = confirm.content.upper()
if confirm.content == 'Y':
response = user.place_bet(match_id, team, bet)
if response['success']:
msg = 'Placed `{:,}` point bet on `{}`'.format(
bet, match.teams[team]['members']
)
embed = quickembed.success(desc=msg, user=user)
else:
embed = quickembed.error(desc=response['message'], user=user)
elif confirm.content == 'N':
embed = quickembed.error(desc='Bet cancelled', user=user)
else:
embed = quickembed.error(desc=response['message'], user=user)
await ctx.send(embed=embed)
@commands.command(name='rate', aliases=['rate-match', 'match-rate'])
@checks.is_registered()
async def rate_match(self, ctx, *args):
"""Adds a 0-5 star rating to a `Match`.
.. Note:
If no `match_id` is provided, the rating will be added to the most recently closed `Match`.
:param ctx: The invocation context.
:param args: Must be either `[rating]` or `[match_id] [rating]`. Rating must be between 0-5.
:return:
"""
user = DiscordUser(ctx.author)
try:
if len(args) == 1:
match_id = None
rating = float(args[0])
else:
match_id = int(args[0])
rating = float(args[1])
except Exception:
msg = (
'Invalid `!rate` command\n'
'`!rate [rating]` (rates last match)\n'
'`!rate [match_id] [rating]`'
)
embed = quickembed.error(desc=msg, user=user)
await ctx.send(embed=embed)
return
if not match_id:
rows = user.search_match_by_recent_completed()
if not rows:
msg = 'No current match set to rate'
embed = quickembed.error(desc=msg, user=user)
await ctx.send(embed=embed)
return
match_id = rows[0].id
response = user.rate_match(match_id, rating)
if response['success']:
match = Match(match_id)
stars = ''
for i in range(1, 6):
if rating >= i:
stars += '★'
else:
stars += '☆'
msg = 'Rated `Match {}` {} ({})\n{}'.format(
match_id, stars, rating, match.info_text_short()
)
embed = quickembed.success(desc=msg, user=user)
else:
msg = response['message']
embed = quickembed.error(desc=msg, user=user)
await ctx.send(embed=embed)
def setup(bot):
"""Required for cogs.
:param bot: The Discord bot.
"""
bot.add_cog(Matches(bot))