Add spades; Move utils to it's own folder outside cogs; Remove images and fonts
25
bot.py
|
@ -10,7 +10,7 @@ import aiohttp
|
|||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
from discord.ext import commands
|
||||
from cogs import utils
|
||||
import utils
|
||||
|
||||
opts = {
|
||||
'command_prefix': utils.command_prefix,
|
||||
|
@ -24,21 +24,15 @@ bot = commands.AutoShardedBot(**opts)
|
|||
logging.basicConfig(level=logging.INFO, filename='bonfire.log')
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
if not hasattr(bot, 'owner'):
|
||||
appinfo = await bot.application_info()
|
||||
bot.owner = appinfo.owner
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_command_completion(ctx):
|
||||
author = ctx.message.author
|
||||
server = ctx.message.guild
|
||||
command = ctx.command
|
||||
|
||||
command_usage = await bot.db.actual_load('command_usage', key=command.qualified_name) \
|
||||
or {'command': command.qualified_name}
|
||||
command_usage = await bot.db.actual_load(
|
||||
'command_usage', key=command.qualified_name
|
||||
) or {'command': command.qualified_name}
|
||||
|
||||
# Add one to the total usage for this command, basing it off 0 to start with (obviously)
|
||||
total_usage = command_usage.get('total_usage', 0) + 1
|
||||
|
@ -60,6 +54,15 @@ async def on_command_completion(ctx):
|
|||
# Save all the changes
|
||||
await bot.db.save('command_usage', command_usage)
|
||||
|
||||
# Now add credits to a users amount
|
||||
# user_credits = bot.db.load('credits', key=ctx.author.id, pluck='credits') or 1000
|
||||
# user_credits = int(user_credits) + 5
|
||||
# update = {
|
||||
# 'member_id': str(ctx.author.id),
|
||||
# 'credits': user_credits
|
||||
# }
|
||||
# await bot.db.save('credits', update)
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_command_error(ctx, error):
|
||||
|
@ -113,7 +116,7 @@ async def on_command_error(ctx, error):
|
|||
try:
|
||||
traceback.print_tb(error.original.__traceback__, file=f)
|
||||
print('{0.__class__.__name__}: {0}'.format(error.original), file=f)
|
||||
except:
|
||||
except Exception:
|
||||
traceback.print_tb(error.__traceback__, file=f)
|
||||
print('{0.__class__.__name__}: {0}'.format(error), file=f)
|
||||
except discord.HTTPException:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from discord.ext import commands
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
import discord
|
||||
import asyncio
|
||||
|
|
|
@ -5,8 +5,7 @@ import traceback
|
|||
import re
|
||||
|
||||
from discord.ext import commands
|
||||
from . import utils
|
||||
|
||||
import utils
|
||||
|
||||
tzmap = {
|
||||
'us-central': pendulum.timezone('US/Central'),
|
||||
|
|
|
@ -1,24 +1,10 @@
|
|||
from . import utils
|
||||
import utils
|
||||
|
||||
from discord.ext import commands
|
||||
|
||||
import asyncio
|
||||
import math
|
||||
|
||||
face_map = {
|
||||
'S': 'spades',
|
||||
'D': 'diamonds',
|
||||
'C': 'clubs',
|
||||
'H': 'hearts'
|
||||
}
|
||||
|
||||
card_map = {
|
||||
'A': 'Ace',
|
||||
'K': 'King',
|
||||
'Q': 'Queen',
|
||||
'J': 'Jack'
|
||||
}
|
||||
|
||||
|
||||
class Blackjack:
|
||||
"""Pretty self-explanatory"""
|
||||
|
@ -151,16 +137,17 @@ class Player:
|
|||
|
||||
for card in self.hand:
|
||||
# Order is suit, face...so we want the second value
|
||||
face = card[1]
|
||||
value = card.value.value
|
||||
face = card.value.name
|
||||
|
||||
if face in ['Q', 'K', 'J']:
|
||||
if face in ['queen', 'king', 'jack']:
|
||||
for index, t in enumerate(total):
|
||||
total[index] += 10
|
||||
elif face == 'A':
|
||||
elif face == 'ace':
|
||||
total = FOIL(total, [1, 11])
|
||||
else:
|
||||
for index, t in enumerate(total):
|
||||
total[index] += int(face)
|
||||
total[index] += int(value)
|
||||
|
||||
# If we have more than one possible total (there is at least one ace) then we do not care about one if it is
|
||||
# over 21
|
||||
|
@ -182,12 +169,7 @@ class Player:
|
|||
def __str__(self):
|
||||
# We only care about our hand, for printing wise
|
||||
fmt = "Hand:\n"
|
||||
fmt += "\n".join(
|
||||
"{} of {}".format(
|
||||
card_map.get(card[1], card[1]),
|
||||
face_map.get(card[0], card[0]))
|
||||
for card in self.hand
|
||||
)
|
||||
fmt += "\n".join(str(card) for card in self.hand)
|
||||
fmt += "\n(Total: {})".format(self.count)
|
||||
return fmt
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from .utils import checks
|
||||
from utils import checks
|
||||
|
||||
import random
|
||||
import re
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .utils import config
|
||||
from utils import config
|
||||
import aiohttp
|
||||
import logging
|
||||
import json
|
||||
|
|
|
@ -2,7 +2,7 @@ from discord.ext import commands
|
|||
from discord.ext.commands.cooldowns import BucketType
|
||||
import discord
|
||||
|
||||
from .utils import checks
|
||||
from utils import checks
|
||||
|
||||
import re
|
||||
import asyncio
|
||||
|
|
|
@ -3,9 +3,8 @@ import discord
|
|||
import random
|
||||
import re
|
||||
import math
|
||||
from bs4 import BeautifulSoup as bs
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
|
||||
class Images:
|
||||
|
@ -39,15 +38,15 @@ class Images:
|
|||
|
||||
EXAMPLE: !doggo
|
||||
RESULT: A beautiful picture of a dog o3o"""
|
||||
result = await utils.request('http://random.dog', attr='text')
|
||||
result = await utils.request('https://random.dog/woof.json')
|
||||
try:
|
||||
soup = bs(result, 'html.parser')
|
||||
filename = soup.img.get('src')
|
||||
except (TypeError, AttributeError):
|
||||
url = result.get("url")
|
||||
filename = re.match("https:\/\/random.dog\/(.*)", url).group(1)
|
||||
except AttributeError:
|
||||
await ctx.send("I couldn't connect! Sorry no dogs right now ;w;")
|
||||
return
|
||||
|
||||
image = await utils.download_image("http://random.dog/{}".format(filename))
|
||||
image = await utils.download_image(url)
|
||||
f = discord.File(image, filename=filename)
|
||||
await ctx.send(file=f)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import rethinkdb as r
|
|||
from discord.ext import commands
|
||||
from discord.ext.commands.cooldowns import BucketType
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
import discord
|
||||
import random
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from discord.ext import commands
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
from bs4 import BeautifulSoup as bs
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
import random
|
||||
import re
|
||||
|
@ -115,7 +115,8 @@ class Miscallaneous:
|
|||
chunks[len(chunks) - 1] += tmp
|
||||
|
||||
if utils.dev_server:
|
||||
tmp = "\n\nIf I'm having issues, then please visit the dev server and ask for help. {}".format(utils.dev_server)
|
||||
tmp = "\n\nIf I'm having issues, then please visit the dev server and ask for help. {}".format(
|
||||
utils.dev_server)
|
||||
if len(chunks[len(chunks) - 1] + tmp) > 2000:
|
||||
chunks.append(tmp)
|
||||
else:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from discord.ext import commands
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
import discord
|
||||
import asyncio
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from . import utils
|
||||
import utils
|
||||
|
||||
from discord.ext import commands
|
||||
import discord
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from . import utils
|
||||
import utils
|
||||
|
||||
from discord.ext import commands
|
||||
import discord
|
||||
import logging
|
||||
|
||||
BASE_URL = "https://api.owapi.net/api/v3/u/"
|
||||
# This is a list of the possible things that we may want to retrieve from the stats
|
||||
|
@ -12,6 +13,8 @@ check_g_stats = ["eliminations", "deaths", 'kpd', 'wins', 'losses', 'time_played
|
|||
'cards', 'damage_done', 'healing_done', 'multikills']
|
||||
check_o_stats = ['wins']
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
class Overwatch:
|
||||
"""Class for viewing Overwatch stats"""
|
||||
|
@ -52,6 +55,8 @@ class Overwatch:
|
|||
await ctx.send("I couldn't connect to overwatch at the moment!")
|
||||
return
|
||||
|
||||
log.info(data)
|
||||
|
||||
region = [x for x in data.keys() if data[x] is not None and x in ['us', 'any', 'kr', 'eu']][0]
|
||||
stats = data[region]['stats']['quickplay']
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
from discord.ext import commands
|
||||
|
||||
from . import utils
|
||||
|
||||
import asyncio
|
||||
import discord
|
||||
import inspect
|
||||
|
@ -26,6 +24,9 @@ class Owner:
|
|||
self._last_result = None
|
||||
self.sessions = set()
|
||||
|
||||
async def __local_check(self, ctx):
|
||||
return await self.bot.is_owner(ctx.author)
|
||||
|
||||
@staticmethod
|
||||
def cleanup_code(content):
|
||||
"""Automatically removes code blocks from the code."""
|
||||
|
@ -36,62 +37,7 @@ class Owner:
|
|||
# remove `foo`
|
||||
return content.strip('` \n')
|
||||
|
||||
async def on_guild_join(self, guild):
|
||||
# I don't want this for now
|
||||
return
|
||||
# Create our embed that we'll use for the information
|
||||
embed = discord.Embed(title="Joined guild {}".format(guild.name), description="Created on: {}".format(guild.created_at.date()))
|
||||
|
||||
# Make sure we only set the icon url if it has been set
|
||||
if guild.icon_url != "":
|
||||
embed.set_thumbnail(url=guild.icon_url)
|
||||
|
||||
# Add our fields, these are self-explanatory
|
||||
embed.add_field(name='Region', value=str(guild.region))
|
||||
embed.add_field(name='Total Emojis', value=len(guild.emojis))
|
||||
|
||||
# Get the amount of online members
|
||||
online_members = [m for m in guild.members if str(m.status) == 'online']
|
||||
embed.add_field(name='Total members', value='{}/{}'.format(len(online_members), guild.member_count))
|
||||
embed.add_field(name='Roles', value=len(guild.roles))
|
||||
|
||||
# Split channels into voice and text channels
|
||||
voice_channels = [c for c in guild.channels if type(c) is discord.VoiceChannel]
|
||||
text_channels = [c for c in guild.channels if type(c) is discord.TextChannel]
|
||||
embed.add_field(name='Channels', value='{} text, {} voice'.format(len(text_channels), len(voice_channels)))
|
||||
embed.add_field(name='Owner', value=guild.owner.display_name)
|
||||
|
||||
await self.bot.owner.send(embed=embed)
|
||||
|
||||
async def on_guild_remove(self, guild):
|
||||
# I don't want this for now
|
||||
return
|
||||
# Create our embed that we'll use for the information
|
||||
embed = discord.Embed(title="Left guild {}".format(guild.name), description="Created on: {}".format(guild.created_at.date()))
|
||||
|
||||
# Make sure we only set the icon url if it has been set
|
||||
if guild.icon_url != "":
|
||||
embed.set_thumbnail(url=guild.icon_url)
|
||||
|
||||
# Add our fields, these are self-explanatory
|
||||
embed.add_field(name='Region', value=str(guild.region))
|
||||
embed.add_field(name='Total Emojis', value=len(guild.emojis))
|
||||
|
||||
# Get the amount of online members
|
||||
online_members = [m for m in guild.members if str(m.status) == 'online']
|
||||
embed.add_field(name='Total members', value='{}/{}'.format(len(online_members), guild.member_count))
|
||||
embed.add_field(name='Roles', value=len(guild.roles))
|
||||
|
||||
# Split channels into voice and text channels
|
||||
voice_channels = [c for c in guild.channels if type(c) is discord.VoiceChannel]
|
||||
text_channels = [c for c in guild.channels if type(c) is discord.TextChannel]
|
||||
embed.add_field(name='Channels', value='{} text, {} voice'.format(len(text_channels), len(voice_channels)))
|
||||
embed.add_field(name='Owner', value=guild.owner.display_name)
|
||||
|
||||
await self.bot.owner.send(embed=embed)
|
||||
|
||||
@commands.command(hidden=True)
|
||||
@utils.can_run(ownership=True)
|
||||
async def repl(self, ctx):
|
||||
msg = ctx.message
|
||||
|
||||
|
@ -119,6 +65,8 @@ class Owner:
|
|||
m.channel.id == msg.channel.id and \
|
||||
m.content.startswith('`')
|
||||
|
||||
code = None
|
||||
|
||||
while True:
|
||||
try:
|
||||
response = await self.bot.wait_for('message', check=check, timeout=10.0 * 60.0)
|
||||
|
@ -161,7 +109,7 @@ class Owner:
|
|||
result = executor(code, variables)
|
||||
if inspect.isawaitable(result):
|
||||
result = await result
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
value = stdout.getvalue()
|
||||
fmt = '```py\n{}{}\n```'.format(value, traceback.format_exc())
|
||||
else:
|
||||
|
@ -184,7 +132,6 @@ class Owner:
|
|||
await ctx.send('Unexpected error: `{}`'.format(e))
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(ownership=True)
|
||||
async def sendtochannel(self, ctx, cid: int, *, message):
|
||||
"""Sends a message to a provided channel, by ID"""
|
||||
channel = self.bot.get_channel(cid)
|
||||
|
@ -195,7 +142,6 @@ class Owner:
|
|||
pass
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(ownership=True)
|
||||
async def debug(self, ctx, *, body: str):
|
||||
env = {
|
||||
'bot': self.bot,
|
||||
|
@ -246,7 +192,6 @@ class Owner:
|
|||
await ctx.send("Content too large for me to print!")
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(ownership=True)
|
||||
async def bash(self, ctx, *, cmd: str):
|
||||
"""Runs a bash command"""
|
||||
output = subprocess.check_output("{}; exit 0".format(cmd), stderr=subprocess.STDOUT, shell=True)
|
||||
|
@ -256,7 +201,6 @@ class Owner:
|
|||
await ctx.send("No output for `{}`".format(cmd))
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(ownership=True)
|
||||
async def shutdown(self, ctx):
|
||||
"""Shuts the bot down"""
|
||||
fmt = 'Shutting down, I will miss you {0.author.name}'
|
||||
|
@ -265,21 +209,18 @@ class Owner:
|
|||
await self.bot.close()
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(ownership=True)
|
||||
async def name(self, ctx, new_nick: str):
|
||||
"""Changes the bot's name"""
|
||||
await self.bot.user.edit(username=new_nick)
|
||||
await ctx.send('Changed username to ' + new_nick)
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(ownership=True)
|
||||
async def status(self, ctx, *, status: str):
|
||||
"""Changes the bot's 'playing' status"""
|
||||
await self.bot.change_presence(activity=discord.Game(name=status, type=0))
|
||||
await ctx.send("Just changed my status to '{}'!".format(status))
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(ownership=True)
|
||||
async def load(self, ctx, *, module: str):
|
||||
"""Loads a module"""
|
||||
|
||||
|
@ -297,7 +238,6 @@ class Owner:
|
|||
await ctx.send(fmt.format(type(error).__name__, error))
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(ownership=True)
|
||||
async def unload(self, ctx, *, module: str):
|
||||
"""Unloads a module"""
|
||||
|
||||
|
@ -310,7 +250,6 @@ class Owner:
|
|||
await ctx.send("I have just unloaded the {} module".format(module))
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(ownership=True)
|
||||
async def reload(self, ctx, *, module: str):
|
||||
"""Reloads a module"""
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import traceback
|
|||
|
||||
from discord.ext import commands
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
BASE_URL = 'https://api.picarto.tv/v1'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from discord.ext import commands
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
|
||||
def to_keycap(c):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from discord.ext import commands
|
||||
import discord
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
import random
|
||||
import pendulum
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from discord.ext import commands
|
||||
import discord
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
import re
|
||||
import asyncio
|
||||
|
|
|
@ -5,7 +5,7 @@ import asyncio
|
|||
|
||||
from discord.ext import commands
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
|
||||
class Roulette:
|
||||
|
|
483
cogs/spades.py
Normal file
|
@ -0,0 +1,483 @@
|
|||
import asyncio
|
||||
import discord
|
||||
|
||||
import utils
|
||||
from utils import Deck, Suit, Face
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
card_map = {
|
||||
"2": "two",
|
||||
"3": "three",
|
||||
"4": "four",
|
||||
"5": "five",
|
||||
"6": "six",
|
||||
"7": "seven",
|
||||
"8": "eight",
|
||||
"9": "nine",
|
||||
"10": "ten"
|
||||
}
|
||||
|
||||
|
||||
class Player:
|
||||
def __init__(self, member, game):
|
||||
self.discord_member = member
|
||||
self.game = game
|
||||
self.channel = member.dm_channel
|
||||
if self.channel is None:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(self.set_channel())
|
||||
self.hand_message = None
|
||||
self.table_message = None
|
||||
self.hand = Deck(prefill=False, spades_high=True)
|
||||
self.bid = 0
|
||||
self.points = 0
|
||||
self.tricks = 0
|
||||
|
||||
self.played_card = None
|
||||
|
||||
self._messages_to_clean = []
|
||||
|
||||
@property
|
||||
def bid_num(self):
|
||||
if self.bid == "moon":
|
||||
return 13
|
||||
elif self.bid == "nil":
|
||||
return 0
|
||||
return self.bid
|
||||
|
||||
async def send_message(self, content=None, embed=None, delete=True):
|
||||
"""A convenience method to send the message to the player, then add it to the list of messages to delete"""
|
||||
_msg = await self.discord_member.send(content, embed=embed)
|
||||
if delete:
|
||||
self._messages_to_clean.append(_msg)
|
||||
return _msg
|
||||
|
||||
async def set_channel(self):
|
||||
self.channel = await self.discord_member.create_dm()
|
||||
|
||||
async def show_hand(self):
|
||||
embed = discord.Embed(title="Hand")
|
||||
diamonds = []
|
||||
hearts = []
|
||||
clubs = []
|
||||
spades = []
|
||||
|
||||
for card in sorted(self.hand):
|
||||
if card.suit == Suit.diamonds:
|
||||
diamonds.append(str(card.face_short))
|
||||
if card.suit == Suit.hearts:
|
||||
hearts.append(str(card.face_short))
|
||||
if card.suit == Suit.clubs:
|
||||
clubs.append(str(card.face_short))
|
||||
if card.suit == Suit.spades:
|
||||
spades.append(str(card.face_short))
|
||||
|
||||
if diamonds:
|
||||
embed.add_field(name="Diamonds", value=", ".join(diamonds), inline=False)
|
||||
if hearts:
|
||||
embed.add_field(name="Hearts", value=", ".join(hearts), inline=False)
|
||||
if clubs:
|
||||
embed.add_field(name="Clubs", value=", ".join(clubs), inline=False)
|
||||
if spades:
|
||||
embed.add_field(name="Spades", value=", ".join(spades), inline=False)
|
||||
|
||||
if self.hand_message:
|
||||
await self.hand_message.edit(embed=embed)
|
||||
else:
|
||||
self.hand_message = await self.discord_member.send(embed=embed)
|
||||
|
||||
async def show_table(self):
|
||||
|
||||
embed = discord.Embed(title="Table")
|
||||
|
||||
if self.game.round.suit:
|
||||
embed.add_field(name="Round suit", value=self.game.round.suit.name, inline=False)
|
||||
else:
|
||||
embed.add_field(name="Round suit", value=self.game.round.suit, inline=False)
|
||||
|
||||
winning_card = self.game.round.winning_card
|
||||
if winning_card:
|
||||
embed.add_field(name="Winning card", value=str(winning_card), inline=False)
|
||||
|
||||
for num, p in enumerate(self.game.players):
|
||||
fmt = "{} ({}/{} tricks): {}".format(
|
||||
p.discord_member.display_name,
|
||||
p.tricks,
|
||||
p.bid_num,
|
||||
p.played_card
|
||||
)
|
||||
embed.add_field(name="Player {}".format(num + 1), value=fmt, inline=False)
|
||||
|
||||
if self.table_message:
|
||||
await self.table_message.edit(embed=embed)
|
||||
else:
|
||||
self.table_message = await self.discord_member.send(embed=embed)
|
||||
|
||||
async def get_bid(self):
|
||||
await self.send_message(
|
||||
content="It is your turn to bid. Please provide 1-12, nil, or moon depending on the bid you want. "
|
||||
"Please note you have 3 minutes to bid, any longer and you will be removed from the game.")
|
||||
self.bid = 0
|
||||
|
||||
def check(m):
|
||||
possible = ['nil', 'moon', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13']
|
||||
return m.channel == self.channel \
|
||||
and m.author.id == self.discord_member.id \
|
||||
and m.content.strip().lower() in possible
|
||||
|
||||
msg = await self.game.bot.wait_for('message', check=check)
|
||||
content = msg.content.strip().lower()
|
||||
if content == '0':
|
||||
self.bid = 'nil'
|
||||
elif content == '13':
|
||||
self.bid = 'moon'
|
||||
elif content.isdigit():
|
||||
self.bid = int(content)
|
||||
else:
|
||||
self.bid = content
|
||||
await self.send_message(content="Thank you for your bid! Please wait while I get the other players' bids...")
|
||||
return self.bid
|
||||
|
||||
async def play(self):
|
||||
fmt = "It is your turn to play, provide your response in the form '[value] of [face]' such as Ace of Spades. "
|
||||
fmt += "Your hand can be found above when you bid earlier."
|
||||
|
||||
await self.send_message(content=fmt)
|
||||
await self.game.bot.wait_for('message', check=self.play_check)
|
||||
await self.send_message(content="You have played...please wait for the other players")
|
||||
|
||||
async def clean_messages(self):
|
||||
for msg in self._messages_to_clean:
|
||||
await msg.delete()
|
||||
|
||||
self._messages_to_clean = []
|
||||
|
||||
def play_check(self, message):
|
||||
if message.channel != self.channel or message.author.id != message.author.id:
|
||||
return False
|
||||
if " of " not in message.content:
|
||||
return False
|
||||
try:
|
||||
parts = message.content.partition('of')
|
||||
face = parts[0].split()[-1].lower()
|
||||
suit = parts[2].split()[0].lower()
|
||||
face = card_map.get(face, face)
|
||||
|
||||
suit = getattr(Suit, suit)
|
||||
face = getattr(Face, face)
|
||||
|
||||
card = self.hand.get_card(suit, face)
|
||||
if card is not None and self.game.round.can_play(self, card):
|
||||
self.played_card = card
|
||||
self.hand.pluck(card=card)
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def score(self):
|
||||
if self.bid == 'nil':
|
||||
if self.tricks == 0:
|
||||
self.points += 100
|
||||
else:
|
||||
self.points -= 100
|
||||
elif self.bid == 'moon':
|
||||
if self.tricks == 13:
|
||||
self.points += 200
|
||||
else:
|
||||
self.points -= 200
|
||||
else:
|
||||
if self.tricks >= self.bid:
|
||||
self.points += self.bid * 10
|
||||
self.points += self.tricks - self.bid
|
||||
else:
|
||||
self.points -= self.bid * 10
|
||||
|
||||
self.bid = 0
|
||||
self.tricks = 0
|
||||
|
||||
def has_suit(self, face):
|
||||
for card in self.hand:
|
||||
if face == card.suit:
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_only_spades(self):
|
||||
for card in self.hand:
|
||||
if card.suit != Suit.spades:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Round:
|
||||
def __init__(self):
|
||||
self.spades_broken = False
|
||||
self.cards = Deck(prefill=False, spades_high=True)
|
||||
self.suit = None
|
||||
|
||||
def can_play(self, player, card):
|
||||
if self.cards.count == 0:
|
||||
if card.suit != Suit.spades:
|
||||
return True
|
||||
else:
|
||||
return self.spades_broken or player.has_only_spades()
|
||||
else:
|
||||
if card.suit == self.suit:
|
||||
return True
|
||||
else:
|
||||
return not player.has_suit(self.suit)
|
||||
|
||||
def play(self, card):
|
||||
# Set the suit
|
||||
if self.cards.count == 0:
|
||||
self.suit = card.suit
|
||||
# This will override the deck, and set it to the round's deck (this is what we want)
|
||||
card.deck = self.cards
|
||||
self.cards.insert(card)
|
||||
if card.suit == Suit.spades:
|
||||
self.spades_broken = True
|
||||
|
||||
@property
|
||||
def winning_card(self):
|
||||
cards = sorted([
|
||||
card
|
||||
for card in self.cards
|
||||
if card.suit == self.suit or card.suit == Suit.spades], reverse=True
|
||||
)
|
||||
try:
|
||||
return cards[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def reset(self):
|
||||
list(self.cards.draw(count=self.cards.count))
|
||||
self.suit = None
|
||||
|
||||
|
||||
class Game:
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.players = []
|
||||
self.deck = Deck(spades_high=True)
|
||||
self.deck.shuffle()
|
||||
self.round = Round()
|
||||
self.started = False
|
||||
self.card_count = 13
|
||||
self.score_limit = 250
|
||||
|
||||
async def start(self):
|
||||
self.started = True
|
||||
special_bids = ['nil', 'moon', 'misdeal']
|
||||
fmt = "All 4 players have joined, and your game of Spades has started!\n"
|
||||
fmt += "Rules for this game can be found here: https://www.pagat.com/boston/spades.html. "
|
||||
fmt += "Special actions/bids allowed are: `{}`\n".format(", ".join(special_bids))
|
||||
fmt += "Players are:\n" + "\n".join(p.discord_member.display_name for p in self.players)
|
||||
fmt += "\n\nPlease wait while all players bid...then the first round will begin"
|
||||
for p in self.players:
|
||||
await p.discord_member.send(fmt)
|
||||
|
||||
await self.game_task()
|
||||
|
||||
async def game_task(self):
|
||||
winner = None
|
||||
# some while loop, while no one has won yet
|
||||
while winner is None:
|
||||
await self.play_round()
|
||||
winner = self.get_winner()
|
||||
await self.new_round()
|
||||
|
||||
async def play_round(self):
|
||||
# For clarities sake, I want to send when it starts, immediately
|
||||
# then follow through with the hand and betting when it's their turn
|
||||
self.deal()
|
||||
for p in self.players:
|
||||
await p.show_hand()
|
||||
await p.show_table()
|
||||
await p.get_bid()
|
||||
|
||||
self.order_turns(self.get_highest_bidder())
|
||||
|
||||
# Bids are complete, time to start the game
|
||||
await self.clean_messages()
|
||||
|
||||
fmt = "Alright, everyone has bid, the bids are:\n{}".format(
|
||||
"\n".join("{}: {}".format(p.discord_member.display_name, p.bid) for p in self.players))
|
||||
for p in self.players:
|
||||
await p.send_message(content=fmt)
|
||||
|
||||
# Once bids are done, we can play the actual round
|
||||
for i in range(self.card_count):
|
||||
# Wait for each player to play
|
||||
for p in self.players:
|
||||
await p.play()
|
||||
self.round.play(p.played_card)
|
||||
# Update everyone's table once each person has finished
|
||||
await self.update_table()
|
||||
# Get the winner after the round, increase their tricks
|
||||
winner = self.get_round_winner()
|
||||
winner.tricks += 1
|
||||
# Order players based off the winner
|
||||
self.order_turns(winner)
|
||||
|
||||
# Reset the round
|
||||
await self.reset_round()
|
||||
fmt = "{} won with a {}".format(winner.discord_member.display_name, winner.played_card)
|
||||
for p in self.players:
|
||||
await p.send_message(content=fmt)
|
||||
|
||||
async def update_table(self):
|
||||
for p in self.players:
|
||||
await p.show_table()
|
||||
|
||||
async def clean_messages(self):
|
||||
for p in self.players:
|
||||
await p.clean_messages()
|
||||
|
||||
async def reset_round(self):
|
||||
self.round.reset()
|
||||
await self.clean_messages()
|
||||
# First loop through to set everyone's card to None
|
||||
for p in self.players:
|
||||
p.played_card = None
|
||||
# Now we can show the table correctly, since everyone's card is set correctly
|
||||
for p in self.players:
|
||||
await p.show_hand()
|
||||
await p.show_table()
|
||||
|
||||
def get_highest_bidder(self):
|
||||
highest_bid = -1
|
||||
highest_player = None
|
||||
for player in self.players:
|
||||
if player.bid_num > highest_bid:
|
||||
highest_player = player
|
||||
|
||||
return highest_player
|
||||
|
||||
def order_turns(self, player):
|
||||
index = self.players.index(player)
|
||||
self.players = self.players[index:] + self.players[:index]
|
||||
|
||||
def get_round_winner(self):
|
||||
winning_card = self.round.winning_card
|
||||
for p in self.players:
|
||||
if winning_card == p.played_card:
|
||||
return p
|
||||
|
||||
def get_winner(self):
|
||||
for p in self.players:
|
||||
if p.points >= self.score_limit:
|
||||
return p
|
||||
|
||||
async def new_round(self):
|
||||
score_msg = discord.Embed(title="Table scores")
|
||||
for p in self.players:
|
||||
p.score()
|
||||
p.played_card = None
|
||||
p.hand_message = None
|
||||
p.table_message = None
|
||||
score_msg.add_field(
|
||||
name="Player {}".format(p.discord_member.display_name),
|
||||
value="{}/{}".format(p.points, self.score_limit),
|
||||
inline=False
|
||||
)
|
||||
|
||||
# We should do this after scoring, so a separate loop is needed here for that
|
||||
for p in self.players:
|
||||
await p.send_message(embed=score_msg)
|
||||
|
||||
# Round the round's reset information (this is the one run after each round of cards...this won't do everything)
|
||||
self.round.reset()
|
||||
# This is the only extra thing needed to fully reset the round itself
|
||||
self.round.spades_broken = False
|
||||
# Set the deck back and shuffle it
|
||||
self.deck.refresh()
|
||||
self.deck.shuffle()
|
||||
# This is all we want to do here, this is just preparing for the new round...not actually starting it
|
||||
|
||||
def deal(self):
|
||||
for _ in range(self.card_count):
|
||||
for p in self.players:
|
||||
card = list(self.deck.draw())
|
||||
p.hand.insert(card)
|
||||
|
||||
def join(self, member):
|
||||
p = Player(member, self)
|
||||
self.players.append(p)
|
||||
|
||||
|
||||
class Spades:
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.pending_game = None
|
||||
self.games = []
|
||||
|
||||
def get_game(self, member):
|
||||
# Simply loop through each game's players, find the one that matches and return it
|
||||
for g, _ in self.games:
|
||||
for p in g.players:
|
||||
if member.id == p.discord_member.id:
|
||||
return g
|
||||
if self.pending_game:
|
||||
for p in self.pending_game.players:
|
||||
if member.id == p.discord_member.id:
|
||||
return self.pending_game
|
||||
|
||||
def join_game(self, author):
|
||||
# First check if there's a pending game
|
||||
if self.pending_game:
|
||||
# If so add the player to it
|
||||
self.pending_game.join(author)
|
||||
# If we've hit 4 players, we want to start the game, add it to our list of games, and wipe our pending game
|
||||
if len(self.pending_game.players) == 4:
|
||||
task = self.bot.loop.create_task(self.pending_game.start())
|
||||
self.games.append((self.pending_game, task))
|
||||
self.pending_game = None
|
||||
# If there's no pending game, start a pending game
|
||||
else:
|
||||
g = Game(self.bot)
|
||||
g.join(author)
|
||||
self.pending_game = g
|
||||
|
||||
def __unload(self):
|
||||
# Simply cancel every task
|
||||
for _, task in self.games:
|
||||
task.cancel()
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(send_messages=True)
|
||||
async def spades(self, ctx):
|
||||
"""Used to join a spades games. This can be used in servers, or in PM, however it will be handled purely via PM.
|
||||
There are no teams in this version of spades, and blind nil/moon bids are not allowed. The way this is handled
|
||||
is for each person joining, there is a pending game ready to start...once 4 people have joined the "lobby" the
|
||||
game will start.
|
||||
|
||||
EXAMPLE: !spades
|
||||
RESULT: You've joined the spades lobby!"""
|
||||
author = ctx.message.author
|
||||
game = self.get_game(author)
|
||||
if game:
|
||||
if game.started:
|
||||
await ctx.send("You are already in a game! Check your PM's if you are confused")
|
||||
else:
|
||||
await ctx.send("There are {} players in your lobby".format(len(game.players)))
|
||||
return
|
||||
# Before we add the player to the game, we need to ensure we can PM this user
|
||||
# So lets do this backwards, confirm the user has joined the game, *then* join the game
|
||||
try:
|
||||
await author.send("You have joined a spades lobby! Please wait for more people to join, "
|
||||
"before the game can start")
|
||||
if ctx.guild:
|
||||
await ctx.send("Check your PM's {}. I have sent you information about your spades lobby".format(
|
||||
author.display_name))
|
||||
self.join_game(author)
|
||||
except discord.Forbidden:
|
||||
await ctx.send("This game is ran through PM's only! "
|
||||
"Please enable your PM's on this server if you want to play!")
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Spades(bot))
|
|
@ -5,7 +5,7 @@ import traceback
|
|||
from discord.ext import commands
|
||||
from base64 import urlsafe_b64encode
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
|
||||
class Spotify:
|
||||
|
|
144
cogs/stats.py
|
@ -1,7 +1,7 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
import re
|
||||
|
||||
|
@ -108,20 +108,12 @@ class Stats:
|
|||
member_usage = command_stats['member_usage'].get(str(ctx.message.author.id), 0)
|
||||
server_usage = command_stats['server_usage'].get(str(ctx.message.guild.id), 0)
|
||||
|
||||
try:
|
||||
data = [("Command Name", cmd.qualified_name),
|
||||
("Total Usage", total_usage),
|
||||
("Your Usage", member_usage),
|
||||
("This Server's Usage", server_usage)]
|
||||
banner = await utils.create_banner(ctx.message.author, "Command Stats", data)
|
||||
await ctx.send(file=discord.File(banner, filename='banner.png'))
|
||||
except (FileNotFoundError, discord.Forbidden):
|
||||
fmt = "The command {} has been used a total of {} times\n" \
|
||||
"{} times on this server\n" \
|
||||
"It has been ran by you, {}, {} times".format(cmd.qualified_name, total_usage, server_usage,
|
||||
ctx.message.author.display_name, member_usage)
|
||||
embed = discord.Embed(title="Usage stats for {}".format(cmd.qualified_name))
|
||||
embed.add_field(name="Total usage", value=total_usage, inline=False)
|
||||
embed.add_field(name="Your usage", value=member_usage, inline=False)
|
||||
embed.add_field(name="This server's usage", value=server_usage, inline=False)
|
||||
|
||||
await ctx.send(fmt)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@command.command(name="leaderboard")
|
||||
@utils.can_run(send_messages=True)
|
||||
|
@ -135,49 +127,44 @@ class Stats:
|
|||
await ctx.message.channel.trigger_typing()
|
||||
|
||||
if re.search('(author|me)', option):
|
||||
author = ctx.message.author
|
||||
mid = str(ctx.message.author.id)
|
||||
# First lets get all the command usage
|
||||
command_stats = self.bot.db.load('command_usage')
|
||||
# Now use a dictionary comprehension to get just the command name, and usage
|
||||
# Based on the author's usage of the command
|
||||
|
||||
stats = {
|
||||
command: data["member_usage"].get(str(author.id))
|
||||
command: data["member_usage"].get(mid)
|
||||
for command, data in command_stats.items()
|
||||
if data["member_usage"].get(str(author.id), 0) > 0
|
||||
if data["member_usage"].get(mid, 0) > 0
|
||||
}
|
||||
# Now sort it by the amount of times used
|
||||
sorted_stats = sorted(stats.items(), key=lambda x: x[1], reverse=True)
|
||||
sorted_stats = sorted(stats.items(), key=lambda x: x[1], reverse=True)[:5]
|
||||
embed = discord.Embed(title="Your top 5 commands", colour=ctx.author.colour)
|
||||
embed.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url)
|
||||
|
||||
# Create a string, each command on it's own line, based on the top 5 used commands
|
||||
# I'm letting it use the length of the sorted_stats[:5]
|
||||
# As this can include, for example, all 3 if there are only 3 entries
|
||||
try:
|
||||
top_5 = [(data[0], data[1]) for data in sorted_stats[:5]]
|
||||
banner = await utils.create_banner(ctx.message.author, "Your command usage", top_5)
|
||||
await ctx.send(file=discord.File(banner, filename='banner.png'))
|
||||
except (FileNotFoundError, discord.Forbidden):
|
||||
top_5 = "\n".join("{}: {}".format(data[0], data[1]) for data in sorted_stats[:5])
|
||||
await ctx.send(
|
||||
"Your top {} most used commands are:\n```\n{}```".format(len(sorted_stats[:5]), top_5))
|
||||
for cmd, amount in sorted_stats:
|
||||
embed.add_field(name=cmd, value=amount, inline=False)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
elif re.search('server', option):
|
||||
# This is exactly the same as above, except server usage instead of member usage
|
||||
server = ctx.message.guild
|
||||
sid = str(ctx.message.guild.id)
|
||||
command_stats = self.bot.db.load('command_usage')
|
||||
stats = {
|
||||
command: data['server_usage'].get(str(server.id))
|
||||
command: data['server_usage'].get(sid)
|
||||
for command, data in command_stats.items()
|
||||
if data.get("server_usage", {}).get(str(server.id), 0) > 0
|
||||
if data.get("server_usage", {}).get(sid, 0) > 0
|
||||
}
|
||||
sorted_stats = sorted(stats.items(), key=lambda x: x[1], reverse=True)
|
||||
try:
|
||||
top_5 = [(data[0], data[1]) for data in sorted_stats[:5]]
|
||||
banner = await utils.create_banner(ctx.message.author, "Server command usage", top_5)
|
||||
await ctx.send(file=discord.File(banner, filename='banner.png'))
|
||||
except (FileNotFoundError, discord.Forbidden):
|
||||
top_5 = "\n".join("{}: {}".format(data[0], data[1]) for data in sorted_stats[:5])
|
||||
await ctx.send(
|
||||
"This server's top {} most used commands are:\n```\n{}```".format(len(sorted_stats[:5]), top_5))
|
||||
sorted_stats = sorted(stats.items(), key=lambda x: x[1], reverse=True)[:5]
|
||||
embed = discord.Embed(title="The server's top 5 commands")
|
||||
embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon_url)
|
||||
|
||||
for cmd, amount in sorted_stats:
|
||||
embed.add_field(name=cmd, value=amount, inline=False)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
else:
|
||||
await ctx.send("That is not a valid option, valid options are: `server` or `me`")
|
||||
|
||||
|
@ -197,25 +184,23 @@ class Stats:
|
|||
# Just to make this easier, just pay attention to the boops data, now that we have the right entry
|
||||
boops = boops['boops']
|
||||
|
||||
# First get a list of the ID's of all members in this server, for use in list comprehension
|
||||
server_member_ids = [str(member.id) for member in ctx.message.guild.members]
|
||||
# Then get a sorted list, based on the amount of times they've booped the member
|
||||
# Reverse needs to be true, as we want it to go from highest to lowest
|
||||
sorted_boops = sorted(boops.items(), key=lambda x: x[1], reverse=True)
|
||||
# Then override the same list, checking if the member they've booped is in this server
|
||||
sorted_boops = [x for x in sorted_boops if x[0] in server_member_ids]
|
||||
sorted_boops = sorted(
|
||||
((ctx.guild.get_member(int(member_id)), amount)
|
||||
for member_id, amount in boops.items()
|
||||
if ctx.guild.get_member(int(member_id))),
|
||||
reverse=True,
|
||||
key=lambda k: k[1]
|
||||
)
|
||||
|
||||
# Since this is sorted, we just need to get the following information on the first user in the list
|
||||
try:
|
||||
most_id, most_boops = sorted_boops[0]
|
||||
member, most_boops = sorted_boops[0]
|
||||
except IndexError:
|
||||
await ctx.send("You have not booped anyone in this server {}".format(ctx.message.author.mention))
|
||||
return
|
||||
|
||||
member = ctx.message.guild.get_member(int(most_id))
|
||||
|
||||
await ctx.send("{0} you have booped {1} the most amount of times, coming in at {2} times".format(
|
||||
ctx.message.author.mention, member.display_name, most_boops))
|
||||
else:
|
||||
await ctx.send("{0} you have booped {1} the most amount of times, coming in at {2} times".format(
|
||||
ctx.message.author.mention, member.display_name, most_boops))
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
|
@ -235,23 +220,21 @@ class Stats:
|
|||
# Just to make this easier, just pay attention to the boops data, now that we have the right entry
|
||||
boops = boops['boops']
|
||||
|
||||
# Same concept as the mostboops method
|
||||
server_member_ids = [member.id for member in ctx.message.guild.members]
|
||||
booped_members = {int(m_id): amt for m_id, amt in boops.items() if int(m_id) in server_member_ids}
|
||||
sorted_booped_members = sorted(booped_members.items(), key=lambda k: k[1], reverse=True)
|
||||
# Now we only want the first 10 members, so splice this list
|
||||
sorted_booped_members = sorted_booped_members[:10]
|
||||
|
||||
try:
|
||||
output = [("{0.display_name}".format(ctx.message.guild.get_member(m_id)), amt)
|
||||
for m_id, amt in sorted_booped_members]
|
||||
banner = await utils.create_banner(ctx.message.author, "Your booped victims", output)
|
||||
await ctx.send(file=discord.File(banner, filename='banner.png'))
|
||||
except (FileNotFoundError, discord.Forbidden):
|
||||
output = "\n".join(
|
||||
"{0.display_name}: {1} times".format(ctx.message.guild.get_member(m_id), amt) for
|
||||
m_id, amt in sorted_booped_members)
|
||||
await ctx.send("You have booped:```\n{}```".format(output))
|
||||
sorted_boops = sorted(
|
||||
((ctx.guild.get_member(int(member_id)), amount)
|
||||
for member_id, amount in boops.items()
|
||||
if ctx.guild.get_member(int(member_id))),
|
||||
reverse=True,
|
||||
key=lambda k: k[1]
|
||||
)
|
||||
if sorted_boops:
|
||||
embed = discord.Embed(title="Your booped victims", colour=ctx.author.colour)
|
||||
embed.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url)
|
||||
for member, amount in sorted_boops:
|
||||
embed.add_field(name=member.display_name, value=amount)
|
||||
await ctx.send(embed=embed)
|
||||
else:
|
||||
await ctx.send("You haven't booped anyone in this server!")
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
|
@ -307,16 +290,15 @@ class Stats:
|
|||
overall_rank = "{}/{}".format(*self.bot.br.get_rank(member))
|
||||
rating = self.bot.br.get_rating(member)
|
||||
record = self.bot.br.get_record(member)
|
||||
try:
|
||||
# Create our banner
|
||||
title = 'Stats for {}'.format(member.display_name)
|
||||
fmt = [('Record', record), ('Server Rank', server_rank), ('Overall Rank', overall_rank), ('Rating', rating)]
|
||||
banner = await utils.create_banner(member, title, fmt)
|
||||
await ctx.send(file=discord.File(banner, filename='banner.png'))
|
||||
except (FileNotFoundError, discord.Forbidden):
|
||||
fmt = 'Stats for {}:\n\tRecord: {}\n\tServer Rank: {}\n\tOverall Rank: {}\n\tRating: {}'
|
||||
fmt = fmt.format(member.display_name, record, server_rank, overall_rank, rating)
|
||||
await ctx.send('```\n{}```'.format(fmt))
|
||||
|
||||
embed = discord.Embed(title="Battling stats for {}".format(ctx.author.display_name), colour=ctx.author.colour)
|
||||
embed.set_author(name=str(member), icon_url=member.avatar_url)
|
||||
embed.add_field(name="Record", value=record, inline=False)
|
||||
embed.add_field(name="Server Rank", value=server_rank, inline=False)
|
||||
embed.add_field(name="Overall Rank", value=overall_rank, inline=False)
|
||||
embed.add_field(name="Rating", value=rating, inline=False)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from discord.ext import commands
|
||||
import discord
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
import asyncio
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from discord.ext import commands
|
||||
import discord
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
import re
|
||||
import random
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from discord.ext import commands
|
||||
|
||||
from . import utils
|
||||
import utils
|
||||
|
||||
import discord
|
||||
|
||||
|
@ -10,7 +10,6 @@ class Tutorial:
|
|||
self.bot = bot
|
||||
|
||||
@commands.command()
|
||||
@utils.can_run(ownership=True)
|
||||
# @utils.can_run(send_messages=True)
|
||||
async def tutorial(self, ctx, *, cmd_or_cog = None):
|
||||
# The message we'll use to send
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
import itertools
|
||||
import random
|
||||
from functools import cmp_to_key
|
||||
|
||||
suits = ['S', 'C', 'H', 'D']
|
||||
faces = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
|
||||
|
||||
|
||||
class Deck:
|
||||
def __init__(self, prefill=True):
|
||||
# itertools.product creates us a tuple based on every output of our faces and suits
|
||||
# This is EXACTLY what a deck of normal playing cards is, so it's perfect
|
||||
if prefill:
|
||||
self.deck = list(itertools.product(suits, faces))
|
||||
else:
|
||||
self.deck = []
|
||||
|
||||
def __iter__(self):
|
||||
for card in self.deck:
|
||||
yield card
|
||||
|
||||
def refresh(self):
|
||||
"""A method that 'restarts' the deck, filling it back with 52 cards"""
|
||||
self.deck = list(itertools.product(suits, faces))
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
"""A property to provide how many cards are currently in the deck"""
|
||||
return len(self.deck)
|
||||
|
||||
@property
|
||||
def empty(self):
|
||||
"""A property to determine whether or not the deck has cards in it"""
|
||||
return len(self.deck) == 0
|
||||
|
||||
def draw(self, count=1):
|
||||
"""Generator to draw from the deck"""
|
||||
try:
|
||||
for i in range(count):
|
||||
yield self.deck.pop()
|
||||
except IndexError:
|
||||
yield None
|
||||
|
||||
def insert(self, cards):
|
||||
"""Adds the provided cards to the end of the deck"""
|
||||
self.deck.extend(cards)
|
||||
|
||||
def pluck(self, card):
|
||||
"""Pulls the provided card from the deck"""
|
||||
return self.deck.pop(self.deck.index(card))
|
||||
|
||||
def shuffle(self):
|
||||
"""Shuffles the deck in place"""
|
||||
random.SystemRandom().shuffle(self.deck)
|
||||
|
||||
def sorted_deck(self):
|
||||
"""Provides a sorted representation of the current deck"""
|
||||
# The idea behind this one is for being useful in hands...there's no reason we need to sort in place
|
||||
# So all we're going to do here is compare how we want, and return the sorted representation
|
||||
def compare(first, second):
|
||||
if suits.index(first[0]) < suits.index(second[0]):
|
||||
return -1
|
||||
elif suits.index(first[0]) > suits.index(second[0]):
|
||||
return 1
|
||||
else:
|
||||
if faces.index(first[1]) < faces.index(second[1]):
|
||||
return -1
|
||||
elif faces.index(first[1]) > faces.index(second[1]):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
return sorted(self.deck, key=cmp_to_key(compare))
|
|
@ -1,19 +1,14 @@
|
|||
bot_token: 'token'
|
||||
owner_id: ['12345']
|
||||
description: 'Bot Description Here'
|
||||
command_prefix: '!'
|
||||
default_status: '!help for a list of commands'
|
||||
discord_bots_key: 'key'
|
||||
carbon_key: 'key'
|
||||
twitch_key: 'key'
|
||||
youtube_key: 'key'
|
||||
osu_key: 'key'
|
||||
dev_server: 'https://discord.gg/123456'
|
||||
description: Description
|
||||
command_prefix: ['?', '!']
|
||||
default_status: 'Status'
|
||||
dev_server: 'https://discord.gg/f6uzJEj'
|
||||
user_agent: 'Bonfire/4.0.3 (https://github.com/Phxntxm/Bonfire)'
|
||||
|
||||
user_agent: 'User-Agent/1.0.0 (Comment like link to site)'
|
||||
|
||||
db_host: 'localhost'
|
||||
db_name: 'Bot_Name'
|
||||
db_port: 28015
|
||||
db_user: 'admin'
|
||||
db_pass: 'password'
|
||||
youtube_key: 'AIzaSyDZKkVdgvCspswxJS5eK7bYtoSDME_dB_Y'
|
||||
osu_key: '0e3301a760a3f834dc9421e97177f9c38ad46169'
|
||||
patreon_key: 'npI5yo6eSKbyeC_F3xZ8NRkASzql73q9hEucqorJhHw'
|
||||
patreon_id: '102085'
|
||||
patreon_link: 'https://www.patreon.com/Phxntxm'
|
||||
spotify_id: 646c8cd8c4f54aa0980dafb819983d40
|
||||
spotify_secret: 271dc32d794e4b40bd52bfe635183aba
|
||||
|
|
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 229 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 169 KiB |
Before Width: | Height: | Size: 127 KiB |
BIN
images/snek0.jpg
Before Width: | Height: | Size: 189 KiB |
BIN
images/snek1.jpg
Before Width: | Height: | Size: 459 KiB |
Before Width: | Height: | Size: 393 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 214 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 399 KiB |
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 50 KiB |
BIN
images/snek2.jpg
Before Width: | Height: | Size: 233 KiB |
BIN
images/snek3.jpg
Before Width: | Height: | Size: 15 KiB |
BIN
images/snek4.jpg
Before Width: | Height: | Size: 73 KiB |
BIN
images/snek5.jpg
Before Width: | Height: | Size: 373 KiB |
BIN
images/snek6.jpg
Before Width: | Height: | Size: 227 KiB |
BIN
images/snek7.jpg
Before Width: | Height: | Size: 296 KiB |
BIN
images/snek8.jpg
Before Width: | Height: | Size: 144 KiB |
BIN
images/snek9.jpg
Before Width: | Height: | Size: 91 KiB |
|
@ -1,9 +1,8 @@
|
|||
aiohttp
|
||||
Pillow==4.2.0
|
||||
rethinkdb
|
||||
ruamel.yaml
|
||||
pyyaml
|
||||
psutil
|
||||
pendulum
|
||||
beautifulsoup4
|
||||
-e git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py[voice]
|
||||
-e git+https://github.com/khazhyk/osuapi.git#egg=osuapi
|
||||
osuapi
|
||||
-e git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py
|
|
@ -1,7 +1,6 @@
|
|||
from .cards import Deck
|
||||
from .cards import Deck, Face, Suit
|
||||
from .checks import can_run, db_check
|
||||
from .config import *
|
||||
from .utilities import *
|
||||
from .images import create_banner
|
||||
from .paginator import Pages, CannotPaginate, HelpPaginator
|
||||
from .database import DB
|
188
utils/cards.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
import random
|
||||
|
||||
from enum import IntEnum, auto
|
||||
|
||||
|
||||
class Suit(IntEnum):
|
||||
clubs = auto()
|
||||
hearts = auto()
|
||||
diamonds = auto()
|
||||
spades = auto()
|
||||
|
||||
|
||||
class Face(IntEnum):
|
||||
ace = auto()
|
||||
two = auto()
|
||||
three = auto()
|
||||
four = auto()
|
||||
five = auto()
|
||||
six = auto()
|
||||
seven = auto()
|
||||
eight = auto()
|
||||
nine = auto()
|
||||
ten = auto()
|
||||
jack = auto()
|
||||
queen = auto()
|
||||
king = auto()
|
||||
|
||||
|
||||
class Deck:
|
||||
def __init__(self, prefill=True, ace_high=True, spades_high=False):
|
||||
self.deck = []
|
||||
if prefill:
|
||||
self.refresh()
|
||||
|
||||
self.ace_high = ace_high
|
||||
self.spades_high = spades_high
|
||||
|
||||
def __iter__(self):
|
||||
for card in self.deck:
|
||||
yield card
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.deck[key]
|
||||
|
||||
def refresh(self):
|
||||
"""A method that 'restarts' the deck, filling it back with 52 cards"""
|
||||
self.deck = []
|
||||
|
||||
for _suit in Suit:
|
||||
for _face in Face:
|
||||
self.insert(Card(_suit, _face, self))
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
"""A property to provide how many cards are currently in the deck"""
|
||||
return len(self.deck)
|
||||
|
||||
@property
|
||||
def empty(self):
|
||||
"""A property to determine whether or not the deck has cards in it"""
|
||||
return len(self.deck) == 0
|
||||
|
||||
def draw(self, count=1):
|
||||
"""Generator to draw from the deck"""
|
||||
try:
|
||||
for i in range(count):
|
||||
yield self.deck.pop()
|
||||
except IndexError:
|
||||
yield None
|
||||
|
||||
def insert(self, cards):
|
||||
"""Adds the provided cards to the end of the deck"""
|
||||
try:
|
||||
self.deck.extend(cards)
|
||||
for card in cards:
|
||||
card._deck = self
|
||||
except TypeError:
|
||||
self.deck.append(cards)
|
||||
cards._deck = self
|
||||
|
||||
def index(self, card):
|
||||
"""Returns the index of the card provided (-1 if card is not in the deck)"""
|
||||
return self.deck.index(card)
|
||||
|
||||
def pluck(self, index=None, card=None):
|
||||
"""Pulls the provided card from the deck"""
|
||||
if index:
|
||||
return self.deck.pop(index)
|
||||
elif card:
|
||||
return self.deck.pop(self.index(card))
|
||||
|
||||
def shuffle(self):
|
||||
"""Shuffles the deck in place"""
|
||||
random.SystemRandom().shuffle(self.deck)
|
||||
|
||||
def get_card(self, suit, face):
|
||||
"""Returns the provided card in the deck"""
|
||||
for card in self.deck:
|
||||
if card.suit == suit and card.value == face:
|
||||
return card
|
||||
|
||||
|
||||
class Card:
|
||||
"""The class that holds all the details for a card in the deck"""
|
||||
|
||||
def __init__(self, suit, value, deck):
|
||||
self._suit = suit
|
||||
self._value = value
|
||||
self._deck = deck
|
||||
|
||||
@property
|
||||
def suit(self):
|
||||
"""The suit (club, diamond, heart, spade)"""
|
||||
return self._suit
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""The face value (2-10, J, Q, K, A)"""
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def face_short(self):
|
||||
"""The first 'letter' of the face, (2-10 will be the numbers)"""
|
||||
if self.value == Face.ace or \
|
||||
self.value == Face.jack or \
|
||||
self.value == Face.queen or \
|
||||
self.value == Face.king:
|
||||
return self.value.name[0]
|
||||
else:
|
||||
return self.value.value
|
||||
|
||||
def __hash__(self):
|
||||
return self.value.value + len(Face) * (self.suit.value - 1)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Card):
|
||||
return False
|
||||
return self.suit == other.suit and self.value == other.value
|
||||
|
||||
def __ne__(self, other):
|
||||
if not isinstance(other, Card):
|
||||
return True
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __str__(self):
|
||||
return "%s of %s" % (self.value.name, self.suit.name)
|
||||
|
||||
def __lt__(self, other):
|
||||
if self._deck.spades_high:
|
||||
if other.suit == Suit.spades and self.suit != Suit.spades:
|
||||
return True
|
||||
if self.suit == Suit.spades and other.suit != Suit.spades:
|
||||
return False
|
||||
|
||||
self_value = self.value.value
|
||||
other_value = other.value.value
|
||||
|
||||
if self._deck.ace_high:
|
||||
if self.value == Face.ace:
|
||||
self_value = 14
|
||||
if other.value == Face.ace:
|
||||
other_value = 14
|
||||
|
||||
return self_value < other_value
|
||||
|
||||
def __gt__(self, other):
|
||||
if self._deck.spades_high:
|
||||
if other.suit == Suit.spades and self.suit != Suit.spades:
|
||||
return False
|
||||
if self.suit == Suit.spades and other.suit != Suit.spades:
|
||||
return True
|
||||
|
||||
self_value = self.value.value
|
||||
other_value = other.value.value
|
||||
|
||||
if self._deck.ace_high:
|
||||
if self.value == Face.ace:
|
||||
self_value = 14
|
||||
if other.value == Face.ace:
|
||||
other_value = 14
|
||||
|
||||
return self_value > other_value
|
||||
|
||||
def __le__(self, other):
|
||||
return self.__eq__(other) or self.__lt__(other)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.__eq__(other) or self.__gt__(other)
|
|
@ -64,12 +64,6 @@ async def db_check():
|
|||
print("Done checking tables!")
|
||||
|
||||
|
||||
def is_owner(ctx):
|
||||
if not hasattr(ctx.bot, "owner"):
|
||||
return False
|
||||
return ctx.bot.owner.id == ctx.message.author.id
|
||||
|
||||
|
||||
def should_ignore(ctx):
|
||||
if ctx.message.guild is None:
|
||||
return False
|
||||
|
@ -162,8 +156,6 @@ def has_perms(ctx, **perms):
|
|||
# Return true if this is a private channel, we'll handle that in the registering of the command
|
||||
if type(ctx.message.channel) is discord.DMChannel:
|
||||
return True
|
||||
# Just get rid of this if it exists
|
||||
perms.pop("ownership", None)
|
||||
|
||||
# Get the member permissions so that we can compare
|
||||
guild_perms = ctx.message.author.guild_permissions
|
||||
|
@ -188,9 +180,6 @@ def has_perms(ctx, **perms):
|
|||
|
||||
def can_run(**kwargs):
|
||||
async def predicate(ctx):
|
||||
# First check if the command requires ownership of the bot
|
||||
if kwargs.get("ownership", False) and not is_owner(ctx):
|
||||
return False
|
||||
# Next check if it requires any certain permissions
|
||||
if kwargs and not has_perms(ctx, **kwargs):
|
||||
return False
|
|
@ -1,4 +1,4 @@
|
|||
import ruamel.yaml as yaml
|
||||
import yaml
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
|
@ -7,7 +7,7 @@ global_config = {}
|
|||
# Ensure that the required config.yml file actually exists
|
||||
try:
|
||||
with open("config.yml", "r") as f:
|
||||
global_config = yaml.safe_load(f)
|
||||
global_config = yaml.load(f)
|
||||
global_config = {k: v for k, v in global_config.items() if v}
|
||||
except FileNotFoundError:
|
||||
print("You have no config file setup! Please use config.yml.sample to setup a valid config file")
|
|
@ -307,7 +307,6 @@ async def _can_run(cmd, ctx):
|
|||
|
||||
def _command_signature(cmd):
|
||||
# this is modified from discord.py source
|
||||
# which I wrote myself lmao
|
||||
|
||||
result = [cmd.qualified_name]
|
||||
if cmd.usage:
|