1
0
Fork 0
mirror of synced 2024-05-03 12:12:31 +12:00

Add spades; Move utils to it's own folder outside cogs; Remove images and fonts

This commit is contained in:
phxntxm 2018-10-29 20:00:37 -05:00
parent 74f474b9a7
commit 29a14d2e3b
82 changed files with 812 additions and 324 deletions

25
bot.py
View file

@ -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:

View file

@ -1,6 +1,6 @@
from discord.ext import commands
from . import utils
import utils
import discord
import asyncio

View file

@ -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'),

View file

@ -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

View file

@ -1,6 +1,6 @@
import discord
from discord.ext import commands
from .utils import checks
from utils import checks
import random
import re

View file

@ -1,4 +1,4 @@
from .utils import config
from utils import config
import aiohttp
import logging
import json

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -1,6 +1,6 @@
from discord.ext import commands
from . import utils
import utils
from bs4 import BeautifulSoup as bs

View file

@ -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:

View file

@ -1,6 +1,6 @@
from discord.ext import commands
from . import utils
import utils
import discord
import asyncio

View file

@ -1,4 +1,4 @@
from . import utils
import utils
from discord.ext import commands
import discord

View file

@ -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']

View file

@ -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"""

View file

@ -5,7 +5,7 @@ import traceback
from discord.ext import commands
from . import utils
import utils
BASE_URL = 'https://api.picarto.tv/v1'

View file

@ -1,5 +1,5 @@
from discord.ext import commands
from . import utils
import utils
def to_keycap(c):

View file

@ -1,7 +1,7 @@
from discord.ext import commands
import discord
from . import utils
import utils
import random
import pendulum

View file

@ -1,7 +1,7 @@
from discord.ext import commands
import discord
from . import utils
import utils
import re
import asyncio

View file

@ -5,7 +5,7 @@ import asyncio
from discord.ext import commands
from . import utils
import utils
class Roulette:

483
cogs/spades.py Normal file
View 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))

View file

@ -5,7 +5,7 @@ import traceback
from discord.ext import commands
from base64 import urlsafe_b64encode
from . import utils
import utils
class Spotify:

View file

@ -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):

View file

@ -1,7 +1,7 @@
from discord.ext import commands
import discord
from . import utils
import utils
import asyncio

View file

@ -1,7 +1,7 @@
from discord.ext import commands
import discord
from . import utils
import utils
import re
import random

View file

@ -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

View file

@ -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))

View file

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

View file

@ -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

View file

@ -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
View 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)

View file

@ -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

View file

@ -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")

View 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: