You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

634 lines
27KB

  1. # -*- coding: utf-8 -*-
  2. # MIT License
  3. #
  4. # Copyright (c) 2017-2018 Roxanne Gibson
  5. #
  6. # Permission is hereby granted, free of charge, to any person obtaining a copy
  7. # of this software and associated documentation files (the "Software"), to deal
  8. # in the Software without restriction, including without limitation the rights
  9. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. # copies of the Software, and to permit persons to whom the Software is
  11. # furnished to do so, subject to the following conditions:
  12. #
  13. # The above copyright notice and this permission notice shall be included in all
  14. # copies or substantial portions of the Software.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. # SOFTWARE.
  23. import asyncio
  24. import datetime
  25. import os
  26. from math import ceil
  27. import discord
  28. import youtube_dl
  29. from discord.ext import commands
  30. import roxbot
  31. from roxbot.db import *
  32. # Suppress noise about console usage from errors
  33. youtube_dl.utils.bug_reports_message = lambda: ''
  34. ytdl_format_options = {
  35. 'format': 'bestaudio/best',
  36. 'outtmpl': './roxbot/cache/%(extractor)s-%(id)s-%(title)s.%(ext)s',
  37. 'restrictfilenames': True,
  38. 'noplaylist': True,
  39. 'nocheckcertificate': True,
  40. 'ignoreerrors': False,
  41. 'logtostderr': False,
  42. 'quiet': True,
  43. 'no_warnings': True,
  44. 'default_search': 'auto',
  45. }
  46. ffmpeg_options = {
  47. 'before_options': '-nostdin',
  48. 'options': '-vn -loglevel panic'
  49. }
  50. ytdl = youtube_dl.YoutubeDL(ytdl_format_options)
  51. class VoiceSingle(db.Entity):
  52. need_perms = Required(bool, default=False)
  53. skip_voting = Required(bool, default=False)
  54. skip_ratio = Required(float, default=0.6, py_check=lambda v: 0 <= v <= 1)
  55. max_length = Required(int, default=600)
  56. guild_id = Required(int, size=64, unique=True)
  57. def need_perms():
  58. def predicate(ctx):
  59. with db_session:
  60. settings = VoiceSingle.get(guild_id=ctx.guild.id)
  61. if settings.need_perms:
  62. return roxbot.utils.has_permissions_or_owner(ctx, manage_channels=True)
  63. else:
  64. return True
  65. return commands.check(predicate)
  66. class NowPlayingEmbed(discord.Embed):
  67. def __init__(self, **kwargs):
  68. image = kwargs.pop("image", None)
  69. thumbnail = kwargs.pop("thumbnail", None)
  70. footer = kwargs.pop("footer", None)
  71. super().__init__(**kwargs)
  72. if thumbnail:
  73. super().set_thumbnail(url=thumbnail)
  74. if footer:
  75. super().set_footer(text=footer)
  76. if image:
  77. super().set_image(url=image)
  78. @staticmethod
  79. def _format_duration(duration):
  80. """Static method to turn the duration of a file (in seconds) into something presentable for the user"""
  81. if not duration:
  82. return duration
  83. hours = duration // 3600
  84. minutes = (duration % 3600) // 60
  85. seconds = duration % 60
  86. format_me = {"second": int(seconds), "minute": int(minutes), "hour": int(hours)}
  87. formatted = datetime.time(**format_me)
  88. output = "{:%M:%S}".format(formatted)
  89. if formatted.hour >= 1:
  90. output = "{:%H:}".format(formatted) + output
  91. return output
  92. @classmethod
  93. def make(cls, now_playing, playing_status):
  94. np = now_playing
  95. title = "{0}: '{1.title}' from {1.host}".format(playing_status, now_playing)
  96. duration = cls._format_duration(np.duration)
  97. time_played = cls._format_duration(np.source.timer / 1000)
  98. description = """Uploaded by: [{0.uploader}]({0.uploader_url})
  99. URL: [Here]({0.webpage_url})
  100. Duration: {1}
  101. Queued by: {0.queued_by}""".format(now_playing, duration)
  102. image = np.thumbnail_url
  103. footer_text = "{}/{} | Volume: {}%".format(time_played, duration, int(now_playing.volume * 100))
  104. return cls(
  105. title=title,
  106. url=np.webpage_url,
  107. description=description,
  108. colour=roxbot.EmbedColours.pink,
  109. image=image,
  110. footer=footer_text
  111. )
  112. class ModifiedFFmpegPMCAudio(discord.FFmpegPCMAudio):
  113. """Modifies the read function of FFmpegPCMAudio to add a timer.
  114. Thanks to eliza(nearlynon#3292) for teaching me how to do this"""
  115. def __init__(self, source, options):
  116. super().__init__(source, **options)
  117. self.timer = 0
  118. def read(self):
  119. self.timer += 20
  120. return super().read()
  121. class YTDLSource(discord.PCMVolumeTransformer):
  122. def __init__(self, source, *, data, volume):
  123. self.source = source
  124. super().__init__(self.source, volume)
  125. self.data = data
  126. self.title = data.get('title')
  127. self.uploader = data.get("uploader")
  128. self.uploader_url = data.get("uploader_url")
  129. self.url = data.get('url')
  130. self.duration = data.get("duration")
  131. self.host = data.get("extractor_key")
  132. self.webpage_url = data.get('webpage_url')
  133. self.thumbnail_url = data.get("thumbnail", "")
  134. @classmethod
  135. async def from_url(cls, url, *, loop=None, stream=False, volume=0.2):
  136. loop = loop or asyncio.get_event_loop()
  137. data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))
  138. if 'entries' in data:
  139. # take first item from a playlist. This shouldn't need to happen but in case it does.
  140. data = data['entries'][0]
  141. filename = data['url'] if stream else ytdl.prepare_filename(data)
  142. return cls(ModifiedFFmpegPMCAudio(filename, ffmpeg_options), data=data, volume=volume)
  143. class Voice(commands.Cog):
  144. """The Voice cog is a cog that adds and manages a fully-functional music bot for Roxbot."""
  145. # TODO: Add voting to many of the commands.
  146. def __init__(self, bot):
  147. # Auto Cleanup cache files on boot
  148. self._clear_cache()
  149. # Setup variables and then add dictionary entries for all guilds the bot can see on boot-up.
  150. self.bot = bot
  151. self.autogen_db = VoiceSingle
  152. # TODO: Make this into a on roxbot joining voice thing instead of generating this for all servers on boot.
  153. self.refresh_rate = 1/60 # 60hz
  154. self._volume = {}
  155. self.playlist = {} # All audio to be played
  156. self.skip_votes = {}
  157. self.am_queuing = {}
  158. self.now_playing = {} # Currently playing audio
  159. self.queue_logic = {}
  160. self.bot.add_listener(self._create_dicts, "on_ready")
  161. async def _create_dicts(self):
  162. # TODO: Probably still move this to be dynamic but that might be weird with discord connection issues.
  163. for guild in self.bot.guilds:
  164. self._volume[guild.id] = 0.2
  165. self.playlist[guild.id] = []
  166. self.skip_votes[guild.id] = []
  167. self.am_queuing[guild.id] = False
  168. self.now_playing[guild.id] = None
  169. self.queue_logic[guild.id] = None
  170. @staticmethod
  171. def _clear_cache():
  172. """Clears the cache folder for the music bot. Ignores the ".gitignore" file to avoid deleting versioned files."""
  173. for file in os.listdir("roxbot/cache"):
  174. if file != ".gitignore":
  175. os.remove("roxbot/cache/{}".format(file))
  176. async def on_guild_join(self, guild):
  177. """Makes sure that when the bot joins a guild it won't need to reboot for the music bot to work."""
  178. self.playlist[guild.id] = []
  179. self.skip_votes[guild.id] = []
  180. self.am_queuing[guild.id] = False
  181. self.now_playing[guild.id] = None
  182. self.queue_logic[guild.id] = None
  183. async def _queue_logic(self, ctx):
  184. """Background task designed to help the bot move on to the next video in the queue"""
  185. try:
  186. while ctx.voice_client.is_playing() or ctx.voice_client.is_paused():
  187. await asyncio.sleep(self.refresh_rate)
  188. except AttributeError:
  189. pass # This is to stop any errors appearing if the bot suddenly leaves voice chat.
  190. self.now_playing[ctx.guild.id] = None
  191. self.skip_votes[ctx.guild.id] = []
  192. if self.playlist[ctx.guild.id] and ctx.voice_client:
  193. player = self.playlist[ctx.guild.id].pop(0)
  194. await ctx.invoke(self.play, url=player, stream=player.get("stream", False), from_queue=True, queued_by=player.get("queued_by", None))
  195. def _queue_song(self, ctx, video, stream):
  196. """Fuction to queue up a video into the playlist."""
  197. video["stream"] = stream
  198. video["queued_by"] = ctx.author
  199. self.playlist[ctx.guild.id].append(video)
  200. return video
  201. @roxbot.checks.has_permissions_or_owner(manage_channels=True)
  202. @commands.guild_only()
  203. @commands.command()
  204. async def join(self, ctx, *, channel: discord.VoiceChannel = None):
  205. """Summon Roxbot to a voice channel, usually the one you are currently in.
  206. This is done automatically when you execute the `;play` or `;stream` commands.
  207. Options:
  208. - `Voice Channel` - OPTIONAL. The name of a Voice Channel
  209. Example:
  210. # Join a voice channle called General
  211. ;join General
  212. """
  213. # Get channel
  214. if channel is None:
  215. try:
  216. channel = ctx.author.voice.channel
  217. except AttributeError:
  218. raise roxbot.UserError("Failed to join voice channel. Please specify a channel or join one for Roxbot to join.")
  219. # Join VoiceChannel
  220. if ctx.voice_client is not None:
  221. await ctx.voice_client.move_to(channel)
  222. else:
  223. await channel.connect()
  224. return await ctx.send("Joined {0.name} :ok_hand:".format(channel))
  225. async def _play(self, ctx, url, stream, queued_by):
  226. player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=stream, volume=self._volume[ctx.guild.id])
  227. player.stream = stream
  228. player.queued_by = queued_by or ctx.author
  229. self.now_playing[ctx.guild.id] = player
  230. ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None)
  231. # Create task to deal with what to do when the video ends or is skipped and how to handle the queue
  232. self.queue_logic[ctx.guild.id] = self.bot.loop.create_task(self._queue_logic(ctx))
  233. @commands.guild_only()
  234. @commands.cooldown(1, 0.8, commands.BucketType.guild)
  235. @commands.command(aliases=["yt"])
  236. async def play(self, ctx, *, url, stream=False, from_queue=False, queued_by=None):
  237. """Plays a video over voice chat using the given URL. This URL has to be one that YoutubeDL can download from. [A list can be found here.](https://rg3.github.io/youtube-dl/supportedsites.html)
  238. If the bot is already playing something, this will be queued up to be played later. If you want to play a livestream, use the `;stream` command.
  239. The user needs to be in a voice channel for this command to work. This is ignored if the user has the `manage_channels` permission. There is also a duration limit that can be placed on videos. This is also ignored if the user has the `manage_channels` permission.
  240. Options:
  241. - `url` - A url to a video or playlist or a search term. If a playlist, it will play the first video and queue up all other videos in the playlist. If just text, Roxbot will play the first Youtube search result.
  242. Examples:
  243. # Play the quality youtube video
  244. ;play https://www.youtube.com/watch?v=3uOPGkEJ56Q
  245. """
  246. guild = ctx.guild
  247. with db_session:
  248. max_duration = VoiceSingle.get(guild_id=guild.id).max_length
  249. # Checks if invoker is in voice with the bot. Skips admins and mods and owner and if the song was queued previously.
  250. if not (roxbot.utils.has_permissions_or_owner(ctx, manage_channels=True) or from_queue):
  251. if not ctx.author.voice:
  252. raise roxbot.UserError("You're not in the same voice channel as Roxbot.")
  253. if ctx.author.voice.channel != ctx.voice_client.channel:
  254. raise roxbot.UserError("You're not in the same voice channel as Roxbot.")
  255. # For internal speed. This should make the playlist management quicker when play is being invoked internally.
  256. if isinstance(url, dict):
  257. video = url
  258. url = video.get("webpage_url")
  259. else:
  260. video = ytdl.extract_info(url, download=False)
  261. # Playlist and search handling.
  262. if 'entries' in video and video.get("extractor_key") != "YoutubeSearch":
  263. await ctx.send("Looks like you have given me a playlist. I will queue up all {} videos in the playlist.".format(len(video.get("entries"))))
  264. data = dict(video)
  265. video = data["entries"].pop(0)
  266. for entry in data["entries"]:
  267. self._queue_song(ctx, entry, stream)
  268. elif 'entries' in video and video.get("extractor_key") == "YoutubeSearch":
  269. video = video["entries"][0]
  270. # Duration limiter handling
  271. if video.get("duration", 1) > max_duration and not roxbot.utils.has_permissions_or_owner(ctx, manage_channels=True):
  272. raise roxbot.UserError("Cannot play video, duration is bigger than the max duration allowed.")
  273. # Actual playing stuff section.
  274. # If not playing and not queuing, and not paused, play the song. Otherwise queue it.
  275. if (not ctx.voice_client.is_playing() and self.am_queuing[guild.id] is False) and not ctx.voice_client.is_paused():
  276. self.am_queuing[guild.id] = True
  277. async with ctx.typing():
  278. await self._play(ctx, url, stream, queued_by)
  279. self.am_queuing[ctx.guild.id] = False
  280. embed = NowPlayingEmbed.make(self.now_playing[ctx.guild.id], "Now Playing")
  281. await ctx.send(embed=embed)
  282. else:
  283. # Queue the song as there is already a song playing or paused.
  284. self._queue_song(ctx, video, stream)
  285. # Sleep because if not, queued up things will send first and probably freak out users or something
  286. while self.am_queuing[guild.id] is True:
  287. await asyncio.sleep(self.refresh_rate)
  288. embed = discord.Embed(description='Added "{}" to queue'.format(video.get("title")), colour=roxbot.EmbedColours.pink)
  289. await ctx.send(embed=embed)
  290. @commands.guild_only()
  291. @commands.cooldown(1, 0.5, commands.BucketType.guild)
  292. @commands.command()
  293. async def stream(self, ctx, *, url):
  294. """
  295. A version of `;play` that should be used to stream livestreams or internet radio sources.
  296. For more details on how this command words, please look at the documentation for the `;play` command.
  297. """
  298. # Just invoke the play command with the stream argument as true. Deals with everything else anyway.
  299. return await ctx.invoke(self.play, url=url, stream=True)
  300. @need_perms()
  301. @commands.guild_only()
  302. @commands.command()
  303. async def volume(self, ctx, volume: int):
  304. """
  305. Sets the volume percentage for Roxbot's audio.
  306. The current volume of Roxbot is displayed by her nowplaying rich embeds that are displayed when she begins to play a video or when the `;nowplaying` command is used.
  307. Options:
  308. - `percent` - A positive integer between 0-100 representing a percentage.
  309. Example:
  310. # Set volume to 20%
  311. ;volume 20
  312. """
  313. if 0 <= volume <= 100:
  314. ctx.voice_client.source.volume = volume / 100 # Volume needs to be a float between 0 and 1... kinda
  315. self._volume[ctx.guild.id] = volume / 100 # Volume needs to be a float between 0 and 1... kinda
  316. else:
  317. raise roxbot.UserError("Volume needs to be between 0-100%")
  318. return await ctx.send("Changed volume to {}%".format(volume))
  319. @need_perms()
  320. @commands.guild_only()
  321. @commands.cooldown(1, 2)
  322. @commands.command()
  323. async def pause(self, ctx):
  324. """Pauses the current video, if playing."""
  325. if ctx.voice_client.is_paused():
  326. return await ctx.send("I already am paused!")
  327. else:
  328. ctx.voice_client.pause()
  329. return await ctx.send("Paused '{}'".format(ctx.voice_client.source.title))
  330. @need_perms()
  331. @commands.guild_only()
  332. @commands.cooldown(1, 2)
  333. @commands.command()
  334. async def resume(self, ctx):
  335. """Resumes the bot, if paused."""
  336. if ctx.voice_client.is_paused():
  337. ctx.voice_client.resume()
  338. return await ctx.send("Resumed '{}'".format(ctx.voice_client.source.title))
  339. else:
  340. if ctx.voice_client.is_playing():
  341. return await ctx.send("Can't resume if I'm already playing something!")
  342. else:
  343. return await ctx.send("Nothing to resume.")
  344. @commands.guild_only()
  345. @commands.command()
  346. async def skip(self, ctx, option=""):
  347. """Skips the current playing video.
  348. If skipvoting is enabled, multiple people will have to use this command to go over the ratio that is also set by server moderators.
  349. Options:
  350. - `--force` - if skip voting is enabled, users with the `manage_channel` permission can skip this process and for the video to be skipped.
  351. Examples:
  352. # Vote to skip a video
  353. ;skip
  354. # Force skip a video
  355. ;skip --force
  356. """
  357. with db_session:
  358. voice = VoiceSingle.get(guild_id=ctx.guild.id)
  359. if voice.skip_voting and not (option == "--force" and ctx.author.guild_permissions.manage_channels): # Admin force skipping
  360. if ctx.author in self.skip_votes[ctx.guild.id]:
  361. return await ctx.send("You have already voted to skip the current track.")
  362. else:
  363. self.skip_votes[ctx.guild.id].append(ctx.author)
  364. # -1 due to the bot being counted in the members generator
  365. ratio = len(self.skip_votes[ctx.guild.id]) / (len(ctx.voice_client.channel.members) - 1)
  366. needed_users = ceil((len(ctx.voice_client.channel.members) - 1) * voice["skip_ratio"])
  367. if ratio >= voice.skip_ratio:
  368. await ctx.send("{} voted the skip the video.".format(ctx.author))
  369. await ctx.send("Votes to skip now playing has been met. Skipping video...")
  370. self.skip_votes[ctx.guild.id] = []
  371. else:
  372. await ctx.send("{} voted the skip the song.".format(ctx.author))
  373. return await ctx.send("{}/{} votes required to skip the video. To vote, use the command `{}skip`".format(len(self.skip_votes[ctx.guild.id]), needed_users, ctx.prefix))
  374. else:
  375. await ctx.send("Skipped video")
  376. # This should be fine as the queue_logic function should handle moving to the next song and all that.
  377. self.now_playing[ctx.guild.id] = None
  378. ctx.voice_client.stop()
  379. @commands.guild_only()
  380. @commands.command(aliases=["np"])
  381. async def nowplaying(self, ctx):
  382. """Displays what is currently playing."""
  383. if self.now_playing[ctx.guild.id] is None:
  384. return await ctx.send("Nothing is playing.")
  385. else:
  386. if ctx.voice_client.is_paused():
  387. x = "Paused"
  388. else:
  389. x = "Now Playing"
  390. embed = NowPlayingEmbed.make(self.now_playing[ctx.guild.id], x)
  391. return await ctx.send(embed=embed)
  392. @commands.guild_only()
  393. @commands.command()
  394. async def queue(self, ctx):
  395. """Displays what videos are queued up and waiting to be played."""
  396. paginator = commands.Paginator(prefix="", suffix="")
  397. index = 1
  398. if not self.playlist[ctx.guild.id]:
  399. return await ctx.send("Nothing is up next. Maybe you should add something!")
  400. else:
  401. for video in self.playlist[ctx.guild.id]:
  402. paginator.add_line("{}) '{}' queued by {}\n".format(index, video["title"], video["queued_by"]))
  403. index += 1
  404. if len(paginator.pages) <= 1:
  405. embed = discord.Embed(title="Queue", description=paginator.pages[0], colour=roxbot.EmbedColours.pink)
  406. return await ctx.send(embed=embed)
  407. else:
  408. pages = []
  409. pages.append(discord.Embed(title="Queue", description=paginator.pages.pop(0), colour=roxbot.EmbedColours.pink))
  410. for page in paginator.pages:
  411. pages.append(discord.Embed(description=page, colour=roxbot.EmbedColours.pink))
  412. for page in pages:
  413. await ctx.send(embed=page)
  414. @commands.guild_only()
  415. @commands.has_permissions(manage_channels=True)
  416. @commands.command()
  417. async def remove(self, ctx, index):
  418. """Removes a item from the queue with the given index.
  419. Options:
  420. - `index/all` - A number representing an index in the queue to remove one video, or "all" to clear all videos.
  421. Examples:
  422. # Remove 2nd video
  423. ;remove 2
  424. # Clear the queue
  425. ;remove all
  426. """
  427. # Try and convert index into an into. If not possible, just move forward
  428. try:
  429. index = int(index)
  430. except ValueError:
  431. pass
  432. # If not str "all" or an int, raise error.
  433. if index != "all" and not isinstance(index, int):
  434. raise roxbot.UserError("No valid option given.")
  435. elif index == "all":
  436. # Remove all queued items
  437. length = len(self.playlist[ctx.guild.id])
  438. self.playlist[ctx.guild.id] = []
  439. return await ctx.send("Removed all queued videos. ({})".format(length))
  440. else:
  441. try:
  442. # Try and remove item using index.
  443. removed = self.playlist[ctx.guild.id].pop(index-1) # -1 because queue index shown starts from 1, not 0
  444. return await ctx.send("Removed '{}' from the queue.".format(removed.get("title", index)))
  445. except IndexError:
  446. raise roxbot.UserError("Valid Index not given.")
  447. @commands.guild_only()
  448. @commands.has_permissions(manage_channels=True)
  449. @commands.command(alaises=["disconnect"])
  450. async def stop(self, ctx):
  451. """Stops Roxbot from playing music and has her leave voice chat."""
  452. # Clear up variables before stopping.
  453. self.playlist[ctx.guild.id] = []
  454. self.now_playing[ctx.guild.id] = None
  455. self.queue_logic[ctx.guild.id].cancel()
  456. await ctx.voice_client.disconnect()
  457. return await ctx.send(":wave:")
  458. @play.before_invoke
  459. @stream.before_invoke
  460. async def ensure_voice(self, ctx):
  461. """Ensures the bot is in a voice channel before continuing and if it cannot auto join, raise an error."""
  462. if ctx.voice_client is None:
  463. if ctx.author.voice:
  464. await ctx.author.voice.channel.connect()
  465. else:
  466. raise roxbot.UserError("Roxbot is not connected to a voice channel and couldn't auto-join a voice channel.")
  467. @skip.before_invoke
  468. @stop.before_invoke
  469. @pause.before_invoke
  470. @resume.before_invoke
  471. @volume.before_invoke
  472. async def check_in_voice(self, ctx):
  473. if ctx.voice_client is None:
  474. raise roxbot.UserError("Roxbot is not in a voice channel.")
  475. @skip.before_invoke
  476. @pause.before_invoke
  477. async def check_playing(self, ctx):
  478. try:
  479. if not ctx.voice_client.is_playing():
  480. raise roxbot.UserError("I'm not playing anything.")
  481. except AttributeError:
  482. raise roxbot.UserError("I'm not playing anything.")
  483. @commands.guild_only()
  484. @commands.has_permissions(manage_channels=True)
  485. @commands.command()
  486. async def voice(self, ctx, setting=None, change=None):
  487. """Edits settings for the voice cog.
  488. Options:
  489. - `enable/disable`: Enable/disables specified change.
  490. - `skipratio`: Specify what the ratio should be for skip voting if enabled. Example: 0.6 for 60%
  491. - `maxlength/duration`: Specify (in seconds) the max duration of a video that can be played.
  492. Possible settings to enable/disable:
  493. - `needperms`: specifies whether `volume`, `pause`, or `resume` require permissions or not.
  494. - `skipvoting`: enables voting to skip instead of one user skipping.
  495. Example:
  496. # Enable skipvoting
  497. ;voice enable skip_voting
  498. # Disbale needing perms for volume, pause, etc.
  499. ;voice disable need_perms
  500. # Edit max_length to 5 minutes
  501. ;voice max_length 300
  502. """
  503. setting = setting.lower()
  504. change = change.lower()
  505. with db_session:
  506. voice = VoiceSingle.get(guild_id=ctx.guild.id)
  507. if setting == "enable":
  508. if change in ("needperms", "need_perms"):
  509. voice.need_perms = True
  510. await ctx.send("'{}' has been enabled!".format(change))
  511. elif change in ("skipvoting", "skip_voting"):
  512. voice.skip_voting = True
  513. await ctx.send("'{}' has been enabled!".format(change))
  514. else:
  515. return await ctx.send("Not a valid change.")
  516. elif setting == "disable":
  517. if change in ("skipvoting", "skip_voting"):
  518. voice.need_perms = False
  519. await ctx.send("'{}' was disabled :cry:".format(change))
  520. elif change in ("skipvoting", "skip_voting"):
  521. voice.skip_voting = False
  522. await ctx.send("'{}' was disabled :cry:".format(change))
  523. else:
  524. return await ctx.send("Not a valid change.")
  525. elif setting in ("skipratio", "skip_ratio"):
  526. change = float(change)
  527. if 1 > change > 0:
  528. voice.skip_ratio = change
  529. elif 0 < change <= 100:
  530. change = change/10
  531. voice.skip_ratio = change
  532. else:
  533. return await ctx.send("Valid ratio not given.")
  534. await ctx.send("Skip Ratio was set to {}".format(change))
  535. elif setting in ("maxlength", "max_length"):
  536. change = int(change)
  537. if change >= 1:
  538. voice.max_length = change
  539. else:
  540. return await ctx.send("Valid max duration not given.")
  541. await ctx.send("Max Duration was set to {}".format(change))
  542. else:
  543. return await ctx.send("Valid option not given.")
  544. def setup(bot_client):
  545. bot_client.add_cog(Voice(bot_client))