1
0
Fork 0
mirror of synced 2024-06-27 18:50:35 +12:00
This commit is contained in:
phxntxm 2017-02-08 21:49:09 -06:00
commit 4b870fcc0d
10 changed files with 245 additions and 64 deletions

2
bot.py
View file

@ -66,7 +66,7 @@ async def process_command(ctx):
command_usage['member_usage'] = total_member_usage
# Add one to the server's usage for this command
if message.server is not None:
if ctx.message.server is not None:
total_server_usage = command_usage.get('server_usage', {})
server_usage = total_server_usage.get(server.id, 0) + 1
total_server_usage[server.id] = server_usage

View file

@ -172,13 +172,13 @@ class Core:
cmd = None
page = 1
perms = self.bot.permissions_in(ctx.message.channel)
if not (perms.embed_links and perms.add_reactions):
fmt = "I need the permissions `embed_links` and `add_reactions` to send my help message! " \
" Otherwise you can use this link to view available commands {}".format(
"http://bonfirebot.readthedocs.io/en/latest/")
await self.bot.say(fmt)
if ctx.message.server:
perms = ctx.message.server.me.permissions_in(ctx.message.channel)
if not (perms.embed_links and perms.add_reactions):
fmt = "I need the permissions `embed_links` and `add_reactions` to send my help message! " \
" Otherwise you can use this link to view available commands {}".format(
"http://bonfirebot.readthedocs.io/en/latest/")
await self.bot.say(fmt)
if message is not None:
# If something is provided, it can either be the page number or a command

View file

@ -55,7 +55,7 @@ class Music:
This command automatically searches as well from YouTube.
The list of supported sites can be found here:
https://rg3.github.io/youtube-dl/supportedsites.html
EXAMPLE: !play Song by Band
RESULT: Song by Band will be queued to play!
"""
@ -110,7 +110,12 @@ class Music:
@commands.command(pass_context=True, no_pm=True, enabled=False)
@checks.custom_perms(send_messages=True)
async def queue(self, ctx):
"""Provides a printout of the songs that are in the queue
"""Provides a printout of the songs that are in the queue.
\N{LEFTWARDS BLACK ARROW}: Goes to the previous page
\N{BLACK RIGHTWARDS ARROW}: Goes to the next page
\N{DOWNWARDS BLACK ARROW}: Moves the current song showing back in the queue
\N{UPWARDS BLACK ARROW}: Moves the current song showing up in the queue
\N{CROSS MARK}: Removes the current song showing from the queue
EXAMPLE: !queue
RESULT: A list of shitty songs you probably don't wanna listen to"""

View file

@ -138,7 +138,11 @@ class Interaction:
if file is None:
await self.bot.say(url)
else:
await self.bot.upload(file, filename='avatar.jpg')
if '.gif' in url:
filename = 'avatar.gif'
else:
filename = 'avatar.webp'
await self.bot.upload(file, filename=filename)
else:
await self.bot.say(url)

View file

@ -9,6 +9,8 @@ import time
import asyncio
import re
import os
import glob
import socket
if not discord.opus.is_loaded():
discord.opus.load_opus('/usr/lib64/libopus.so.0')
@ -33,6 +35,7 @@ class VoiceState:
}
self.volume = 50
self.downloader = download
self.file_names = []
def is_playing(self):
# If our VoiceClient or current VoiceEntry do not exist, then we are not playing a song
@ -58,8 +61,6 @@ class VoiceState:
self.player.stop()
def toggle_next(self):
# Delete the old file (screw caching, when on 5000+ Guilds this takes up too much space)
os.remove(self.current.filename)
# Set the Event so that the next song in the queue can be played
self.bot.loop.call_soon_threadsafe(self.play_next_song.set)
@ -74,6 +75,7 @@ class VoiceState:
# Make sure we find a song
while self.current is None:
self.clear_audio_files()
await asyncio.sleep(1)
self.current = await self.songs.get_next_entry()
@ -82,6 +84,10 @@ class VoiceState:
print("Downloading...")
await asyncio.sleep(1)
# Now add this file to our list of filenames, so that it can be deleted later
if self.current.filename not in self.file_names:
self.file_names.append(self.current.filename)
# Create the player object
self.current.player = self.voice.create_ffmpeg_player(
self.current.filename,
@ -100,6 +106,11 @@ class VoiceState:
# Wait till the Event has been set, before doing our task again
await self.play_next_song.wait()
def clear_audio_files(self):
"""Deletes all the audio files this guild has created"""
for f in self.file_names:
os.remove(f)
self.file_names = []
class Music:
"""Voice related commands.
@ -112,6 +123,12 @@ class Music:
down = Downloader(download_folder='audio_tmp')
self.downloader = down
self.bot.downloader = down
self.clear_audio_tmp()
def clear_audio_tmp(self):
files = glob.glob('audio_tmp/*')
for f in files:
os.remove(f)
def get_voice_state(self, server):
state = self.voice_states.get(server.id)
@ -132,17 +149,22 @@ class Music:
server = channel.server
state = self.get_voice_state(server)
voice = self.bot.voice_client_in(server)
# Attempt 3 times
for i in range(3):
try:
if voice is None:
state.voice = await self.bot.join_voice_channel(channel)
return True
elif voice.channel == channel:
state.voice = voice
return True
else:
await voice.disconnect()
state.voice = await self.bot.join_voice_channel(channel)
return True
except (discord.ClientException, socket.gaierror):
continue
if voice is None:
state.voice = await self.bot.join_voice_channel(channel)
return True
elif voice.channel == channel:
state.voice = voice
return True
else:
await voice.disconnect()
state.voice = await self.bot.join_voice_channel(channel)
return True
async def remove_voice_client(self, server):
"""Removes any voice clients from a server
@ -171,11 +193,126 @@ class Music:
if state.voice is None:
return
voice_channel = state.voice.channel
if voice_channel != before.voice.voice_channel and voice_channel != after.voice.voice_channel:
return
num_members = len(voice_channel.voice_members)
state.required_skips = math.ceil((num_members + 1) / 3)
async def queue_embed_task(self, state, channel, author):
index = 0
message = None
fmt = None
# Our check to ensure the only one who reacts is the bot
def check(reaction, user):
return user == author
possible_reactions = ['\u27A1', '\u2B05', '\u2b06', '\u2b07', '\u274c']
while True:
# Get the current queue (It might change while we're doing this)
# So do this in the while loop
queue = state.songs.entries
count = len(queue)
# This means the last song was removed
if count == 0:
await self.bot.send_message(channel, "Nothing currently in the queue")
break
# Get the current entry
entry = queue[index]
# Get the entry's embed
embed = entry.to_embed()
# Set the embed's title to indicate the amount of things in the queue
count = len(queue)
embed.title = "Current Queue [{}/{}]".format(index+1, count)
# Now we need to send the embed, so check if the message is already set
# If not, then we need to send a new one (i.e. this is the first time called)
if message:
message = await self.bot.edit_message(message, fmt, embed=embed)
# There's only one reaction we want to make sure we remove in the circumstances
# If the member doesn't have kick_members permissions, and isn't the requester
# Then they can't remove the song, otherwise they can
if not author.server_permissions.kick_members and author != entry.requester:
try:
await self.bot.remove_reaction(message, '\u274c', channel.server.me)
except:
pass
elif not author.server_permissions.kick_members and author == entry.requester:
try:
await self.bot.add_reaction(message, '\u274c')
except:
pass
else:
message = await self.bot.send_message(channel, embed=embed)
await self.bot.add_reaction(message, '\N{LEFTWARDS BLACK ARROW}')
await self.bot.add_reaction(message, '\N{BLACK RIGHTWARDS ARROW}')
# The moderation tools that can be used
if author.server_permissions.kick_members:
await self.bot.add_reaction(message, '\N{DOWNWARDS BLACK ARROW}')
await self.bot.add_reaction(message, '\N{UPWARDS BLACK ARROW}')
await self.bot.add_reaction(message, '\N{CROSS MARK}')
elif author == entry.requester:
await self.bot.add_reaction(message, '\N{CROSS MARK}')
# Reset the fmt message
fmt = None
# Now we wait for the next reaction
res = await self.bot.wait_for_reaction(possible_reactions, message=message, check=check, timeout=180)
if res is None:
break
else:
reaction, user = res
# Now we can prepare for the next embed to be sent
# If right is clicked
if '\u27A1' in reaction.emoji:
index += 1
if index >= count:
index = 0
# If left is clicked
elif '\u2B05' in reaction.emoji:
index -= 1
if index < 0:
index = count - 1
# If up is clicked
elif '\u2b06' in reaction.emoji:
# A second check just to make sure, as well as ensuring index is higher than 0
if author.server_permissions.kick_members and index > 0:
if entry != queue[index]:
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
else:
# Remove the current entry
del queue[index]
# Add it one position higher
queue.insert(index - 1, entry)
# Lets move the index to look at the new place of the entry
index -= 1
# If down is clicked
elif '\u2b07' in reaction.emoji:
# A second check just to make sure, as well as ensuring index is lower than last
if author.server_permissions.kick_members and index < (count - 1):
if entry != queue[index]:
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
else:
# Remove the current entry
del queue[index]
# Add it one position lower
queue.insert(index + 1, entry)
# Lets move the index to look at the new place of the entry
index += 1
# If x is clicked
elif '\u274c' in reaction.emoji:
# A second check just to make sure
if author.server_permissions.kick_members or author == entry.requester:
if entry != queue[index]:
fmt = "`Error: Position of this entry has changed, cannot complete your action`"
else:
# Simply remove the entry in place
del queue[index]
# This is the only check we need to make, to ensure index is now not more than last
new_count = count - 1
if index >= new_count:
index = new_count - 1
try:
await self.bot.remove_reaction(message, reaction.emoji, user)
except discord.Forbidden:
pass
await self.bot.delete_message(message)
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
async def progress(self, ctx):
@ -281,6 +418,10 @@ class Music:
await self.bot.say("You are not currently in the channel; please join before trying to request a song.")
return
# Set the number of required skips to start
num_members = len(my_channel.voice_members)
state.required_skips = math.ceil((num_members + 1) / 3)
# Create the player, and check if this was successful
# Here all we want is to get the information of the player
song = re.sub('[<>\[\]]', '', song)
@ -289,8 +430,8 @@ class Music:
_entry, position = await state.songs.add_entry(song, ctx.message.author)
except WrongEntryTypeError:
# This means that a song was attempted to be searched, instead of a link provided
info = await self.downloader.extract_info(self.bot.loop, song, download=False, process=True)
try:
info = await self.downloader.extract_info(self.bot.loop, song, download=False, process=True)
song = info.get('entries', [])[0]['webpage_url']
except IndexError:
await self.bot.send_message(ctx.message.channel, "No results found for {}!".format(song))
@ -310,6 +451,7 @@ class Music:
fmt = "Sorry but I couldn't download that! Either you provided a playlist, a streamed link, or " \
"a page that is not supported to download."
await self.bot.send_message(ctx.message.channel, fmt)
return
except ExtractionError as e:
# This gets the youtube_dl error, instead of our error raised
error = str(e).split("\n\n")[1]
@ -376,8 +518,9 @@ class Music:
# Then erase the voice_state entirely, and disconnect from the channel
try:
state.audio_player.cancel()
state.clear_audio_files()
await self.remove_voice_client(ctx.message.server)
del self.voice_states[server.id]
await state.voice.disconnect()
except:
pass
@ -433,10 +576,9 @@ class Music:
# Asyncio provides no non-private way to access the queue, so we have to use _queue
queue = state.songs.entries
if len(queue) == 0:
fmt = "Nothing currently in the queue"
await self.bot.say("Nothing currently in the queue")
else:
fmt = "\n\n".join(str(x) for x in queue)
await self.bot.say("Current songs in the queue:```\n{}```".format(fmt))
self.bot.loop.create_task(self.queue_embed_task(state, ctx.message.channel, ctx.message.author))
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@ -498,8 +640,23 @@ class Music:
if not state.is_playing():
await self.bot.say('Not playing anything.')
else:
# Create the embed object we'll use
embed = discord.Embed()
# Fill in the simple things
embed.add_field(name='Title', value=state.current.title, inline=False)
embed.add_field(name='Requester', value=state.current.requester.display_name, inline=False)
# Get the amount of current skips, and display how many have been skipped/how many required
skip_count = len(state.skip_votes)
await self.bot.say('Now playing {} [skips: {}/{}]'.format(state.current, skip_count, state.required_skips))
embed.add_field(name='Skip Count', value='{}/{}'.format(skip_count, state.required_skips), inline=False)
# Get the current progress and display this
progress = state.current.progress
length = state.current.length
progress = divmod(round(progress, 0), 60)
length = divmod(round(length, 0), 60)
fmt = "{0[0]}m {0[1]}s/{1[0]}m {1[1]}s".format(progress, length)
embed.add_field(name='Progress', value=fmt,inline=False)
# And send the embed
await self.bot.say(embed=embed)
def setup(bot):

View file

@ -7,8 +7,8 @@ import traceback
import logging
from discord.ext import commands
from .utils import config
from .utils import checks
from . import utils
log = logging.getLogger()
base_url = 'https://ptvappapi.picarto.tv'
@ -24,7 +24,7 @@ async def online_users():
# In place of requesting for /channel and checking if that is online currently, for each channel
# This method is in place to just return all online_users
url = '{}/online/all?key={}'.format(base_url, key)
with aiohttp.ClientSession(headers={"User-Agent": config.user_agent}) as s:
with aiohttp.ClientSession(headers={"User-Agent": utils.user_agent}) as s:
async with s.get(url) as response:
return await response.json()
except:
@ -43,7 +43,7 @@ def check_online(online_channels, channel):
class Picarto:
def __init__(self, bot):
self.bot = bot
self.headers = {"User-Agent": config.user_agent}
self.headers = {"User-Agent": utils.user_agent}
self.session = aiohttp.ClientSession()
async def check_channels(self):
@ -52,7 +52,7 @@ class Picarto:
try:
while not self.bot.is_closed:
r_filter = {'notifications_on': 1}
picarto = await config.get_content('picarto', r_filter)
picarto = await utils.get_content('picarto', r_filter)
# Get all online users before looping, so that only one request is needed
online_users_list = await online_users()
old_online_users = {data['member_id']: data for data in picarto if data['live']}
@ -69,7 +69,7 @@ class Picarto:
server = self.bot.get_server(server_id)
if server is None:
continue
server_alerts = await config.get_content('server_alerts', {'server_id': server_id})
server_alerts = await utils.get_content('server_alerts', {'server_id': server_id})
try:
channel_id = server_alerts[0]['channel_id']
except (IndexError, TypeError):
@ -82,7 +82,7 @@ class Picarto:
fmt = "{} has just gone live! View their stream at {}".format(member.display_name, url)
await self.bot.send_message(channel, fmt)
await config.update_content('picarto', {'live': 1}, {'member_id': m_id})
await utils.update_content('picarto', {'live': 1}, {'member_id': m_id})
for m_id, result in old_online_users.items():
# Get their url and their user based on that url
url = result['picarto_url']
@ -94,7 +94,7 @@ class Picarto:
server = self.bot.get_server(server_id)
if server is None:
continue
server_alerts = await config.get_content('server_alerts', {'server_id': server_id})
server_alerts = await utils.get_content('server_alerts', {'server_id': server_id})
try:
channel_id = server_alerts[0]['channel_id']
except IndexError:
@ -105,7 +105,7 @@ class Picarto:
fmt = "{} has just gone offline! Catch them next time they stream at {}".format(
member.display_name, url)
await self.bot.send_message(channel, fmt)
await config.update_content('picarto', {'live': 0}, {'member_id': m_id})
await utils.update_content('picarto', {'live': 0}, {'member_id': m_id})
await asyncio.sleep(30)
except Exception as e:
tb = traceback.format_exc()
@ -113,7 +113,7 @@ class Picarto:
log.error(fmt)
@commands.group(pass_context=True, invoke_without_command=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def picarto(self, ctx, member: discord.Member = None):
"""This command can be used to view Picarto stats about a certain member
@ -122,7 +122,7 @@ class Picarto:
# If member is not given, base information on the author
member = member or ctx.message.author
r_filter = {'member_id': member.id}
picarto_entry = await config.get_content('picarto', r_filter)
picarto_entry = await utils.get_content('picarto', r_filter)
if picarto_entry is None:
await self.bot.say("That user does not have a picarto url setup!")
return
@ -151,7 +151,7 @@ class Picarto:
await self.bot.say("Picarto stats for {}: ```\n{}```".format(member.display_name, fmt))
@picarto.command(name='add', pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def add_picarto_url(self, ctx, url: str):
"""Saves your user's picarto URL
@ -187,20 +187,20 @@ class Picarto:
'notifications_on': 1,
'live': 0,
'member_id': ctx.message.author.id}
if await config.add_content('picarto', entry, r_filter):
if await utils.add_content('picarto', entry, r_filter):
await self.bot.say(
"I have just saved your Picarto URL {}, this server will now be notified when you go live".format(
ctx.message.author.mention))
else:
await config.update_content('picarto', {'picarto_url': url}, r_filter)
await utils.update_content('picarto', {'picarto_url': url}, r_filter)
await self.bot.say("I have just updated your Picarto URL")
@picarto.command(name='remove', aliases=['delete'], pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def remove_picarto_url(self, ctx):
"""Removes your picarto URL"""
r_filter = {'member_id': ctx.message.author.id}
if await config.remove_content('picarto', r_filter):
if await utils.remove_content('picarto', r_filter):
await self.bot.say("I am no longer saving your picarto URL {}".format(ctx.message.author.mention))
else:
await self.bot.say(
@ -208,7 +208,7 @@ class Picarto:
ctx.message.author.mention, ctx.prefix))
@picarto.group(pass_context=True, no_pm=True, invoke_without_command=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def notify(self, ctx):
"""This can be used to turn picarto notifications on or off
Call this command by itself, to add this server to the list of servers to be notified
@ -216,7 +216,7 @@ class Picarto:
EXAMPLE: !picarto notify
RESULT: This server will now be notified of you going live"""
r_filter = {'member_id': ctx.message.author.id}
result = await config.get_content('picarto', r_filter)
result = await utils.get_content('picarto', r_filter)
# Check if this user is saved at all
if result is None:
await self.bot.say(
@ -226,30 +226,30 @@ class Picarto:
elif ctx.message.server.id in result[0]['servers']:
await self.bot.say("I am already set to notify in this server...")
else:
await config.update_content('picarto', {'servers': r.row['servers'].append(ctx.message.server.id)},
await utils.update_content('picarto', {'servers': r.row['servers'].append(ctx.message.server.id)},
r_filter)
@notify.command(name='on', aliases=['start,yes'], pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def notify_on(self, ctx):
"""Turns picarto notifications on
EXAMPLE: !picarto notify on
RESULT: Notifications are sent when you go live"""
r_filter = {'member_id': ctx.message.author.id}
await config.update_content('picarto', {'notifications_on': 1}, r_filter)
await utils.update_content('picarto', {'notifications_on': 1}, r_filter)
await self.bot.say("I will notify if you go live {}, you'll get a bajillion followers I promise c:".format(
ctx.message.author.mention))
@notify.command(name='off', aliases=['stop,no'], pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)
@utils.custom_perms(send_messages=True)
async def notify_off(self, ctx):
"""Turns picarto notifications off
EXAMPLE: !picarto notify off
RESULT: No more notifications sent when you go live"""
r_filter = {'member_id': ctx.message.author.id}
await config.update_content('picarto', {'notifications_on': 0}, r_filter)
await utils.update_content('picarto', {'notifications_on': 0}, r_filter)
await self.bot.say(
"I will not notify if you go live anymore {}, "
"are you going to stream some lewd stuff you don't want people to see?~".format(

View file

@ -75,7 +75,10 @@ class Raffle:
# We don't have to wait for it however, so create a task for it
r_filter = {'id': raffle_id}
self.bot.loop.create_task(config.remove_content('raffles', r_filter))
await self.bot.send_message(server, fmt)
try:
await self.bot.send_message(server, fmt)
except discord.Forbidden:
pass
@commands.command(pass_context=True, no_pm=True)
@checks.custom_perms(send_messages=True)

View file

@ -46,8 +46,8 @@ class Twitch:
# Loop through as long as the bot is connected
try:
while not self.bot.is_closed:
twitch = await config.get_content('twitch', {'notifications_on': 1})
# Online/offline is based on whether they are set to such, in the config file
twitch = await utils.get_content('twitch', {'notifications_on': 1})
# Online/offline is based on whether they are set to such, in the utils file
# This means they were detected as online/offline before and we check for a change
online_users = {data['member_id']: data for data in twitch if data['live']}
offline_users = {data['member_id']: data for data in twitch if not data['live']}
@ -62,7 +62,7 @@ class Twitch:
server = self.bot.get_server(server_id)
if server is None:
continue
server_alerts = await config.get_content('server_alerts', {'server_id': server_id})
server_alerts = await utils.get_content('server_alerts', {'server_id': server_id})
try:
channel_id = server_alerts[0]['channel_id']
except (IndexError, TypeError):
@ -75,7 +75,7 @@ class Twitch:
fmt = "{} has just gone live! View their stream at {}".format(member.display_name, url)
await self.bot.send_message(channel, fmt)
await config.update_content('twitch', {'live': 1}, {'member_id': m_id})
await utils.update_content('twitch', {'live': 1}, {'member_id': m_id})
for m_id, result in online_users.items():
# Get their url and their user based on that url
url = result['twitch_url']
@ -87,7 +87,7 @@ class Twitch:
server = self.bot.get_server(server_id)
if server is None:
continue
server_alerts = await config.get_content('server_alerts', {'server_id': server_id})
server_alerts = await utils.get_content('server_alerts', {'server_id': server_id})
channel_id = server_id
if len(server_alerts) > 0:
channel_id = server_alerts[0].get('channel_id')
@ -97,7 +97,7 @@ class Twitch:
fmt = "{} has just gone offline! Catch them next time they stream at {}".format(
member.display_name, url)
await self.bot.send_message(channel, fmt)
await config.update_content('twitch', {'live': 0}, {'member_id': m_id})
await utils.update_content('twitch', {'live': 0}, {'member_id': m_id})
await asyncio.sleep(30)
except Exception as e:
tb = traceback.format_exc()

View file

@ -61,7 +61,7 @@ async def download_image(url):
return None
# Then wrap it in a BytesIO object, to be used like an actual file
image = io.BytesIO(await r.read())
image = io.BytesIO(bts)
return image
async def request(url, *, headers=None, payload=None, method='GET', attr='json'):

View file

@ -3,6 +3,7 @@ import json
import os
import traceback
import time
import discord
from hashlib import md5
from .exceptions import ExtractionError
@ -270,5 +271,16 @@ class URLPlaylistEntry(BasePlaylistEntry):
else:
# Move the temporary file to it's final location.
os.rename(unhashed_fname, self.filename)
def to_embed(self):
"""Returns an embed that can be used to display information about this particular song"""
# Create the embed object we'll use
embed = discord.Embed()
# Fill in the simple things
embed.add_field(name='Title', value=self.title, inline=False)
embed.add_field(name='Requester', value=self.requester.display_name, inline=False)
# Get the current length of the song and display this
length = divmod(round(self.length, 0), 60)
fmt = "{0[0]}m {0[1]}s".format(length)
embed.add_field(name='Duration', value=fmt,inline=False)
# And return the embed we created
return embed