Merge branch 'master' of https://github.com/Phxntxm/Bonfire
This commit is contained in:
commit
84baef2125
12
README.md
12
README.md
|
@ -5,7 +5,7 @@ This is for a Discord bot using the discord.py wrapper made for fun, used in a c
|
|||
If you'd like to add this bot to one of your own servers, please visit the following URL:
|
||||
https://discordapp.com/oauth2/authorize?client_id=183748889814237186&scope=bot&permissions=0
|
||||
|
||||
This requires the discord.py library, as well as all of it's dependencies.
|
||||
This requires the discord.py library, as well as all of its dependencies.
|
||||
https://github.com/Rapptz/discord.py
|
||||
|
||||
To save the data for the bot, rethinkdb is what is used:
|
||||
|
@ -20,6 +20,14 @@ py -3 -m pip install discord.py[voice] lxml fuzzywuzzy youtube_dl rethinkdb ruam
|
|||
|
||||
Note: ATM of writing this, Pillow 3.4.2 (the stable version...good job Pillow?) is broken, do not use pip's default to install this. This is why we're using Pillow==3.4.1 above, and not just Pillow
|
||||
|
||||
The joke command requires the fortune-mod package, which are installable on Linux and other Unix-based distros.
|
||||
|
||||
Debian, Ubuntu, etc.:
|
||||
```
|
||||
sudo apt install fortune-mod
|
||||
```
|
||||
If you're on Red Hat, Fedora, etc., replace ``apt`` with ``yum``. If you're on another Linux distro, I trust you know what package manager to use. If you're on Windows, you might want to check out [this guide](http://superuser.com/questions/683162/bsd-fortune-for-windows-command-prompt-or-dos), but you're on your own.
|
||||
|
||||
The only required file to modify would be the config.yml.sample file. The entries are as follows:
|
||||
|
||||
- bot_token: The token that can be retrieved from the [bot's application page](https://discordapp.com/developers/applications/me)
|
||||
|
@ -36,6 +44,6 @@ The only required file to modify would be the config.yml.sample file. The entrie
|
|||
- da_secret: The deviant art Secret, given with the da_id above
|
||||
- shard_count: This is the number of shards the bot is split over. 1 needs to be used if the bot is not being sharded
|
||||
- shard_id: This will be the ID of the shard in particular, 0 if sharding is not used
|
||||
- extensions: This is a list of the extensions loaded into the bot (check the cogs folder for the extensions available). The disabled playlist is a special entry....read that file for what it's purpose is....most likely you will not need it. Entries in this list need to be separated by ", " like in the example.
|
||||
- extensions: This is a list of the extensions loaded into the bot (check the cogs folder for the extensions available). The disabled playlist is a special entry....read that file for what its purpose is....most likely you will not need it. Entries in this list need to be separated by ", " like in the example.
|
||||
- db_*: This is the information for the rethinkdb database. The cert is the certificate used for driver connections
|
||||
|
||||
|
|
24
bot.py
24
bot.py
|
@ -10,13 +10,13 @@ import aiohttp
|
|||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
from discord.ext import commands
|
||||
from cogs.utils import config
|
||||
from cogs import utils
|
||||
|
||||
opts = {'command_prefix': config.command_prefix,
|
||||
'description': config.bot_description,
|
||||
opts = {'command_prefix': utils.command_prefix,
|
||||
'description': utils.bot_description,
|
||||
'pm_help': None,
|
||||
'shard_count': config.shard_count,
|
||||
'shard_id': config.shard_id,
|
||||
'shard_count': utils.shard_count,
|
||||
'shard_id': utils.shard_id,
|
||||
'command_not_found': ''}
|
||||
|
||||
bot = commands.Bot(**opts)
|
||||
|
@ -26,11 +26,11 @@ logging.basicConfig(level=logging.WARNING, filename='bonfire.log')
|
|||
@bot.event
|
||||
async def on_ready():
|
||||
# Change the status upon connection to the default status
|
||||
await bot.change_presence(game=discord.Game(name=config.default_status, type=0))
|
||||
await bot.change_presence(game=discord.Game(name=utils.default_status, type=0))
|
||||
|
||||
if not hasattr(bot, 'uptime'):
|
||||
bot.uptime = pendulum.utcnow()
|
||||
|
||||
await utils.db_check()
|
||||
|
||||
@bot.event
|
||||
async def on_message(message):
|
||||
|
@ -51,7 +51,7 @@ async def process_command(ctx):
|
|||
command = ctx.command
|
||||
|
||||
r_filter = {'command': command.qualified_name}
|
||||
command_usage = await config.get_content('command_usage', r_filter)
|
||||
command_usage = await utils.get_content('command_usage', r_filter)
|
||||
if command_usage is None:
|
||||
command_usage = {'command': command.qualified_name}
|
||||
else:
|
||||
|
@ -74,8 +74,8 @@ async def process_command(ctx):
|
|||
command_usage['server_usage'] = total_server_usage
|
||||
|
||||
# Save all the changes
|
||||
if not await config.update_content('command_usage', command_usage, r_filter):
|
||||
await config.add_content('command_usage', command_usage, r_filter)
|
||||
if not await utils.update_content('command_usage', command_usage, r_filter):
|
||||
await utils.add_content('command_usage', command_usage, r_filter)
|
||||
|
||||
|
||||
@bot.event
|
||||
|
@ -126,6 +126,6 @@ async def on_command_error(error, ctx):
|
|||
if __name__ == '__main__':
|
||||
bot.remove_command('help')
|
||||
|
||||
for e in config.extensions:
|
||||
for e in utils.extensions:
|
||||
bot.load_extension(e)
|
||||
bot.run(config.bot_token)
|
||||
bot.run(utils.bot_token)
|
||||
|
|
|
@ -207,6 +207,12 @@ class Game:
|
|||
|
||||
# Lets create our main deck, and shuffle it
|
||||
self.deck = utils.Deck()
|
||||
# So apparently, it is possible, with 10 players and nearly everyone/everyone busting
|
||||
# To actually deplete the deck, and cause it to return None, and mess up later
|
||||
# Due to this, lets make put 2 decks in here
|
||||
_deck2 = utils.Deck()
|
||||
self.deck.insert(list(_deck2.draw(52)))
|
||||
del _deck2
|
||||
self.deck.shuffle()
|
||||
# The dealer
|
||||
self.dealer = Player('Dealer')
|
||||
|
@ -282,8 +288,8 @@ class Game:
|
|||
fmt = "You got a blackjack {0.member.mention}!\n\n{0}".format(player)
|
||||
|
||||
await self.bot.send_message(self.channel, fmt)
|
||||
# Loop through each player (as long as their status is playing)
|
||||
for entry in [p for p in self.players if p['status'] == 'playing']:
|
||||
# Loop through each player (as long as their status is playing) and they have bet chips
|
||||
for entry in [p for p in self.players if p['status'] == 'playing' and hasattr(p['player'], 'bet')]:
|
||||
player = entry['player']
|
||||
|
||||
# Let them know it's their turn to play
|
||||
|
|
|
@ -71,7 +71,7 @@ class Core:
|
|||
entries = sorted(utils.get_all_commands(self.bot))
|
||||
try:
|
||||
pages = utils.Pages(self.bot, message=ctx.message, entries=entries)
|
||||
await pages.paginate()
|
||||
await pages.paginate(start_page=page)
|
||||
except utils.CannotPaginate as e:
|
||||
await self.bot.say(str(e))
|
||||
else:
|
||||
|
|
|
@ -79,6 +79,8 @@ class Deviantart:
|
|||
data = await utils.request(self.base_url, payload=params)
|
||||
if data is None:
|
||||
continue
|
||||
elif not data['results']:
|
||||
continue
|
||||
|
||||
result = data['results'][0]
|
||||
cache[da_name] = result
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from discord.ext import commands
|
||||
from discord.ext.commands.cooldowns import BucketType
|
||||
from .utils import config
|
||||
from .utils import checks
|
||||
from .utils import utilities
|
||||
|
||||
from . import utils
|
||||
|
||||
import discord
|
||||
import random
|
||||
|
||||
|
@ -109,7 +109,7 @@ class Interaction:
|
|||
not p2 == player_id and not p1 == player_id}
|
||||
|
||||
@commands.command(pass_context=True, no_pm=True)
|
||||
@checks.custom_perms(send_messages=True)
|
||||
@utils.custom_perms(send_messages=True)
|
||||
async def hug(self, ctx, user: discord.Member = None):
|
||||
"""Makes me hug a person!
|
||||
|
||||
|
@ -122,7 +122,7 @@ class Interaction:
|
|||
await self.bot.say(fmt.format(user.display_name))
|
||||
|
||||
@commands.command(pass_context=True, no_pm=True)
|
||||
@checks.custom_perms(send_messages=True)
|
||||
@utils.custom_perms(send_messages=True)
|
||||
async def avatar(self, ctx, member: discord.Member = None):
|
||||
"""Provides an image for the provided person's avatar (yours if no other member is provided)
|
||||
|
||||
|
@ -134,7 +134,7 @@ class Interaction:
|
|||
|
||||
url = member.avatar_url
|
||||
if ctx.message.server.me.permissions_in(ctx.message.channel).attach_files:
|
||||
file = await utilities.download_image(url)
|
||||
file = await utils.download_image(url)
|
||||
if file is None:
|
||||
await self.bot.say(url)
|
||||
else:
|
||||
|
@ -149,7 +149,7 @@ class Interaction:
|
|||
|
||||
@commands.group(pass_context=True, no_pm=True, invoke_without_command=True)
|
||||
@commands.cooldown(1, 180, BucketType.user)
|
||||
@checks.custom_perms(send_messages=True)
|
||||
@utils.custom_perms(send_messages=True)
|
||||
async def battle(self, ctx, player2: discord.Member):
|
||||
"""Challenges the mentioned user to a battle
|
||||
|
||||
|
@ -180,7 +180,7 @@ class Interaction:
|
|||
await self.bot.say(fmt.format(ctx, player2))
|
||||
|
||||
@commands.command(pass_context=True, no_pm=True)
|
||||
@checks.custom_perms(send_messages=True)
|
||||
@utils.custom_perms(send_messages=True)
|
||||
async def accept(self, ctx):
|
||||
"""Accepts the battle challenge
|
||||
|
||||
|
@ -206,13 +206,13 @@ class Interaction:
|
|||
# All we need to do is change what order the challengers are printed/added as a paramater
|
||||
if random.SystemRandom().randint(0, 1):
|
||||
await self.bot.say(fmt.format(battleP1.mention, battleP2.mention))
|
||||
await utilities.update_records('battle_records', battleP1, battleP2)
|
||||
await utils.update_records('battle_records', battleP1, battleP2)
|
||||
else:
|
||||
await self.bot.say(fmt.format(battleP2.mention, battleP1.mention))
|
||||
await utilities.update_records('battle_records', battleP2, battleP1)
|
||||
await utils.update_records('battle_records', battleP2, battleP1)
|
||||
|
||||
@commands.command(pass_context=True, no_pm=True)
|
||||
@checks.custom_perms(send_messages=True)
|
||||
@utils.custom_perms(send_messages=True)
|
||||
async def decline(self, ctx):
|
||||
"""Declines the battle challenge
|
||||
|
||||
|
@ -235,7 +235,7 @@ class Interaction:
|
|||
|
||||
@commands.command(pass_context=True, no_pm=True)
|
||||
@commands.cooldown(1, 180, BucketType.user)
|
||||
@checks.custom_perms(send_messages=True)
|
||||
@utils.custom_perms(send_messages=True)
|
||||
async def boop(self, ctx, boopee: discord.Member = None, *, message = ""):
|
||||
"""Boops the mentioned person
|
||||
|
||||
|
@ -259,19 +259,19 @@ class Interaction:
|
|||
return
|
||||
|
||||
r_filter = {'member_id': booper.id}
|
||||
boops = await config.get_content('boops', r_filter)
|
||||
boops = await utils.get_content('boops', r_filter)
|
||||
if boops is not None:
|
||||
boops = boops[0]['boops']
|
||||
# If the booper has never booped the member provided, assure it's 0
|
||||
amount = boops.get(boopee.id, 0) + 1
|
||||
boops[boopee.id] = amount
|
||||
|
||||
await config.update_content('boops', {'boops': boops}, r_filter)
|
||||
await utils.update_content('boops', {'boops': boops}, r_filter)
|
||||
else:
|
||||
entry = {'member_id': booper.id,
|
||||
'boops': {boopee.id: 1}}
|
||||
|
||||
await config.add_content('boops', entry, r_filter)
|
||||
await utils.add_content('boops', entry, r_filter)
|
||||
amount = 1
|
||||
|
||||
fmt = "{0.mention} has just booped {1.mention}{3}! That's {2} times now!"
|
||||
|
|
|
@ -233,7 +233,7 @@ class Mod:
|
|||
# If we don't find custom permissions, get the required permission for a command
|
||||
# based on what we set in utils.custom_perms, if custom_perms isn't found, we'll get an IndexError
|
||||
try:
|
||||
custom_perms = [func for func in cmd.utils if "custom_perms" in func.__qualname__][0]
|
||||
custom_perms = [func for func in cmd.checks if "custom_perms" in func.__qualname__][0]
|
||||
except IndexError:
|
||||
# Loop through and check if there is a check called is_owner
|
||||
# If we loop through and don't find one, this means that the only other choice is to be
|
||||
|
|
|
@ -12,6 +12,7 @@ import re
|
|||
import os
|
||||
import glob
|
||||
import socket
|
||||
import inspect
|
||||
|
||||
if not discord.opus.is_loaded():
|
||||
discord.opus.load_opus('/usr/lib64/libopus.so.0')
|
||||
|
@ -320,6 +321,34 @@ class Music:
|
|||
pass
|
||||
await self.bot.delete_message(message)
|
||||
|
||||
@commands.command(pass_context=True)
|
||||
@commands.check(utils.is_owner)
|
||||
async def vdebug(self, ctx, *, code : str):
|
||||
"""Evaluates code."""
|
||||
code = code.strip('` ')
|
||||
python = '```py\n{}\n```'
|
||||
result = None
|
||||
|
||||
env = {
|
||||
'bot': self.bot,
|
||||
'ctx': ctx,
|
||||
'message': ctx.message,
|
||||
'server': ctx.message.server,
|
||||
'channel': ctx.message.channel,
|
||||
'author': ctx.message.author
|
||||
}
|
||||
|
||||
env.update(globals())
|
||||
|
||||
try:
|
||||
result = eval(code, env)
|
||||
if inspect.isawaitable(result):
|
||||
result = await result
|
||||
except Exception as e:
|
||||
await self.bot.say(python.format(type(e).__name__ + ': ' + str(e)))
|
||||
return
|
||||
|
||||
await self.bot.say(python.format(result))
|
||||
|
||||
@commands.command(pass_context=True, no_pm=True)
|
||||
@utils.custom_perms(send_messages=True)
|
||||
|
@ -355,7 +384,8 @@ class Music:
|
|||
except discord.InvalidArgument:
|
||||
await self.bot.say('This is not a voice channel...')
|
||||
except (asyncio.TimeoutError, discord.ConnectionClosed):
|
||||
await self.bot.say("I failed to connect! This can sometimes be caused by your server region being far away."
|
||||
await self.bot.say("I failed to connect! This usually happens if I don't have permission to join the"
|
||||
" channel, but can sometimes be caused by your server region being far away."
|
||||
" Otherwise this is an issue on Discord's end, causing the connect to timeout!")
|
||||
await self.remove_voice_client(channel.server)
|
||||
else:
|
||||
|
@ -376,7 +406,8 @@ class Music:
|
|||
try:
|
||||
success = await self.create_voice_client(summoned_channel)
|
||||
except (asyncio.TimeoutError, discord.ConnectionClosed):
|
||||
await self.bot.say("I failed to connect! This can sometimes be caused by your server region being far away."
|
||||
await self.bot.say("I failed to connect! This usually happens if I don't have permission to join the"
|
||||
" channel, but can sometimes be caused by your server region being far away."
|
||||
" Otherwise this is an issue on Discord's end, causing the connect to timeout!")
|
||||
await self.remove_voice_client(summoned_channel.server)
|
||||
return False
|
||||
|
@ -525,8 +556,9 @@ class Music:
|
|||
|
||||
# Stop playing whatever song is playing.
|
||||
if state.is_playing():
|
||||
player = state.player
|
||||
player.stop()
|
||||
state.player.stop()
|
||||
|
||||
state.songs.clear()
|
||||
|
||||
# This will stop cancel the audio event we're using to loop through the queue
|
||||
# Then erase the voice_state entirely, and disconnect from the channel
|
||||
|
|
|
@ -10,9 +10,6 @@ import aiohttp
|
|||
import pendulum
|
||||
import asyncio
|
||||
|
||||
getter = re.compile(r'`(?!`)(.*?)`')
|
||||
multi = re.compile(r'```(.*?)```', re.DOTALL)
|
||||
|
||||
|
||||
class Owner:
|
||||
"""Commands that can only be used by Phantom, bot management commands"""
|
||||
|
@ -35,32 +32,32 @@ class Owner:
|
|||
|
||||
@commands.command(pass_context=True)
|
||||
@commands.check(utils.is_owner)
|
||||
async def debug(self, ctx):
|
||||
"""Executes code"""
|
||||
# Eval and exec have different useful purposes, so use both
|
||||
async def debug(self, ctx, *, code : str):
|
||||
"""Evaluates code."""
|
||||
code = code.strip('` ')
|
||||
python = '```py\n{}\n```'
|
||||
result = None
|
||||
|
||||
env = {
|
||||
'bot': self.bot,
|
||||
'ctx': ctx,
|
||||
'message': ctx.message,
|
||||
'server': ctx.message.server,
|
||||
'channel': ctx.message.channel,
|
||||
'author': ctx.message.author
|
||||
}
|
||||
|
||||
env.update(globals())
|
||||
|
||||
try:
|
||||
result = eval(code, env)
|
||||
if inspect.isawaitable(result):
|
||||
result = await result
|
||||
except Exception as e:
|
||||
await self.bot.say(python.format(type(e).__name__ + ': ' + str(e)))
|
||||
return
|
||||
|
||||
# `Get all content in this format`
|
||||
match_single = getter.findall(ctx.message.content)
|
||||
# ```\nGet all content in this format```
|
||||
match_multi = multi.findall(ctx.message.content)
|
||||
|
||||
if match_single:
|
||||
result = eval(match_single[0])
|
||||
|
||||
# In case the result needs to be awaited, handle that
|
||||
if inspect.isawaitable(result):
|
||||
result = await result
|
||||
await self.bot.say("```\n{0}```".format(result))
|
||||
elif match_multi:
|
||||
# Internal method to send the message to the channel, of whatever is passed
|
||||
def r(v):
|
||||
self.bot.loop.create_task(self.bot.say("```\n{}```".format(v)))
|
||||
|
||||
exec(match_multi[0])
|
||||
except Exception as error:
|
||||
fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```'
|
||||
await self.bot.say(fmt.format(type(error).__name__, error))
|
||||
await self.bot.say(python.format(result))
|
||||
|
||||
@commands.command(pass_context=True)
|
||||
@commands.check(utils.is_owner)
|
||||
|
|
|
@ -97,7 +97,7 @@ class Picarto:
|
|||
server_alerts = await utils.get_content('server_alerts', {'server_id': server_id})
|
||||
try:
|
||||
channel_id = server_alerts[0]['channel_id']
|
||||
except IndexError:
|
||||
except (IndexError, TypeError):
|
||||
channel_id = server_id
|
||||
channel = self.bot.get_channel(channel_id)
|
||||
# Get the member that has just gone live
|
||||
|
|
|
@ -89,7 +89,7 @@ class Twitch:
|
|||
continue
|
||||
server_alerts = await utils.get_content('server_alerts', {'server_id': server_id})
|
||||
channel_id = server_id
|
||||
if len(server_alerts) > 0:
|
||||
if server_alerts is not None and len(server_alerts) > 0:
|
||||
channel_id = server_alerts[0].get('channel_id')
|
||||
channel = self.bot.get_channel(channel_id)
|
||||
# Get the member that has just gone live
|
||||
|
@ -210,6 +210,7 @@ class Twitch:
|
|||
await self.bot.say("I am already set to notify in this server...")
|
||||
else:
|
||||
await utils.update_content('twitch', {'servers': r.row['servers'].append(ctx.message.server.id)}, r_filter)
|
||||
await self.bot.say("This server will now be notified if you go live")
|
||||
|
||||
@notify.command(name='on', aliases=['start,yes'], pass_context=True, no_pm=True)
|
||||
@utils.custom_perms(send_messages=True)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from .cards import Deck
|
||||
from .checks import is_owner, custom_perms, is_pm
|
||||
from .checks import is_owner, custom_perms, is_pm, db_check
|
||||
from .config import *
|
||||
from .utilities import *
|
||||
from .images import create_banner
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import asyncio
|
||||
import rethinkdb as r
|
||||
|
||||
from discord.ext import commands
|
||||
import discord
|
||||
|
@ -6,6 +7,46 @@ from . import config
|
|||
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
# The list of tables needed for the database
|
||||
table_list = ['battle_records', 'battling', 'boops', 'bot_data', 'command_usage', 'custom_permissions',
|
||||
'deviantart', 'motd', 'nsfw_channels', 'overwatch', 'picarto', 'prefixes', 'raffles',
|
||||
'rules', 'server_alerts', 'strawpolls', 'tags', 'tictactoe', 'twitch', 'user_notifications']
|
||||
|
||||
|
||||
async def db_check():
|
||||
"""Used to check if the required database/tables are setup"""
|
||||
db_opts = config.db_opts
|
||||
|
||||
r.set_loop_type('asyncio')
|
||||
# First try to connect, and see if the correct information was provided
|
||||
try:
|
||||
conn = await r.connect(**db_opts)
|
||||
except r.errors.ReqlDriverError:
|
||||
print("Cannot connect to the RethinkDB instance with the following information: {}".format(db_opts))
|
||||
|
||||
print("The RethinkDB instance you have setup may be down, otherwise please ensure you setup a"\
|
||||
" RethinkDB instance, and you have provided the correct database information in config.yml")
|
||||
quit()
|
||||
|
||||
# Get the current databases and check if the one we need is there
|
||||
dbs = await r.db_list().run(conn)
|
||||
if db_opts['db'] not in dbs:
|
||||
# If not, we want to create it
|
||||
print('Couldn\'t find database {}...creating now'.format(db_opts['db']))
|
||||
await r.db_create(db_opts['db']).run(conn)
|
||||
# Then add all the tables
|
||||
for table in table_list:
|
||||
print("Creating table {}...".format(table))
|
||||
await r.table_create(table).run(conn)
|
||||
print("Done!")
|
||||
else:
|
||||
# Otherwise, if the database is setup, make sure all the required tables are there
|
||||
tables = await r.table_list().run(conn)
|
||||
for table in table_list:
|
||||
if table not in tables:
|
||||
print("Creating table {}...".format(table))
|
||||
await r.table_create(table).run(conn)
|
||||
print("Done checking tables!")
|
||||
|
||||
def is_owner(ctx):
|
||||
return ctx.message.author.id in config.owner_ids
|
||||
|
|
|
@ -58,6 +58,8 @@ steam_key = global_config.get("steam_key", "")
|
|||
youtube_key = global_config.get("youtube_key", "")
|
||||
# The key for Osu API calls
|
||||
osu_key = global_config.get('osu_key', '')
|
||||
# The key for League of Legends API calls
|
||||
lol_key = global_config.get('lol_key', '')
|
||||
# The keys needed for deviant art calls
|
||||
da_id = global_config.get("da_id", "")
|
||||
da_secret = global_config.get("da_secret", "")
|
||||
|
|
|
@ -169,9 +169,9 @@ class Pages:
|
|||
return True
|
||||
return False
|
||||
|
||||
async def paginate(self):
|
||||
async def paginate(self, start_page=1):
|
||||
"""Actually paginate the entries and run the interactive loop if necessary."""
|
||||
await self.show_page(1, first=True)
|
||||
await self.show_page(start_page, first=True)
|
||||
|
||||
while self.paginating:
|
||||
react = await self.bot.wait_for_reaction(message=self.message, check=self.react_check, timeout=120.0)
|
||||
|
|
Loading…
Reference in a new issue