1
0
Fork 0
mirror of synced 2024-06-28 03:00:55 +12:00
This commit is contained in:
phxntxm 2017-03-04 01:48:54 -06:00
commit 84baef2125
16 changed files with 159 additions and 69 deletions

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

1
riot.txt Normal file
View file

@ -0,0 +1 @@
fe04e897-b34e-4bf5-b79e-ccd5664bbd54