Draper 36f494ba63 [Audio] One PR to rule them all, One PR to find them, One PR to bring them all, and in the darkness bind them (all-in-one pr) (#2904)
* More changes

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed auto play defaulting to playlist

Signed-off-by: Guy <guyreis96@gmail.com>

* Localtrack fix

Signed-off-by: Guy <guyreis96@gmail.com>

* Updated deps .. since for some reason aiosqlite is not being auto installed for everyone

Signed-off-by: Guy <guyreis96@gmail.com>

* Yupo

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed a crash in [p]now

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed crash on playlist save

Signed-off-by: Guy <guyreis96@gmail.com>

* Debugging Commit

Signed-off-by: Guy <guyreis96@gmail.com>

* Yet more prints

Signed-off-by: Guy <guyreis96@gmail.com>

* Even more spammy debug

Signed-off-by: Guy <guyreis96@gmail.com>

* Debugging commit + NEw Dispatches

Signed-off-by: Guy <guyreis96@gmail.com>

* Debugging commit + NEw Dispatches

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed localpath checks

Signed-off-by: Guy <guyreis96@gmail.com>

* more fixes for Localpaths

Signed-off-by: Guy <guyreis96@gmail.com>

* Spelling mistake on method

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed Crash on event handler

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed Crash on local search

Signed-off-by: Guy <guyreis96@gmail.com>

* Reduced fuzzy match percentage threshold for local tracks to account for nested folders

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed a crash on queue end

Signed-off-by: Guy <guyreis96@gmail.com>

* Sigh ... Removed a duplicate dispatch

Signed-off-by: Guy <guyreis96@gmail.com>

* Sigh i removed this before ...

Signed-off-by: Guy <guyreis96@gmail.com>

* Reorder dispatch signatures so all 3 new dispatch have matching signature

Signed-off-by: Guy <guyreis96@gmail.com>

* Formatting

Signed-off-by: Guy <guyreis96@gmail.com>

* Edited Error Event to support localtracks

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix a Crash on track crash :awesome:

Signed-off-by: Guy <guyreis96@gmail.com>

* Yikes soo much spam

Signed-off-by: Guy <guyreis96@gmail.com>

* Remove spam and improve existance check

Signed-off-by: Guy <guyreis96@gmail.com>

* Repeat and Auto-play are mutually exclusive now

Signed-off-by: Guy <guyreis96@gmail.com>

* DEBUGS for Preda

Signed-off-by: Guy <guyreis96@gmail.com>

* Vimeo tracks can be from both these domains "vimeo.com", "beam.pro"

Signed-off-by: Guy <guyreis96@gmail.com>

* I mean Mixer can be from those 2 domains ....

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed `search sc` command

Signed-off-by: Guy <guyreis96@gmail.com>

* Run everything though lints.
rename localtracks module to dataclasses
Clear lock on errors

Signed-off-by: Draper <guyreis96@gmail.com>

* Try to speed up long playlist loading

Signed-off-by: Draper <guyreis96@gmail.com>

* Im an idiot

Signed-off-by: Draper <guyreis96@gmail.com>

* Im an idiot

Signed-off-by: Draper <guyreis96@gmail.com>

* Added logging for writes

Signed-off-by: Draper <guyreis96@gmail.com>

* Fix crash on cog reload

Signed-off-by: Draper <guyreis96@gmail.com>

* Fix for runtimewarning ?

Signed-off-by: Draper <guyreis96@gmail.com>

* Fix for Local Track cache

Signed-off-by: Draper <guyreis96@gmail.com>

* Remove broken tracks from queue on exception
Theoretically do not auto play if track stop reason is Stopped or cleanup

Signed-off-by: Draper <guyreis96@gmail.com>

* Previous commit was a fluke ... ignore it

Signed-off-by: Draper <guyreis96@gmail.com>

* Change from cleanup to Replaced

Signed-off-by: Draper <guyreis96@gmail.com>

* Fixed AttributeError: 'Track' object has no attribute 'info'.
[p]skip will only work for autoplay is there a track being played.
Fixed Console spam if query saving failed in the background while reloading bot.
Autoplay now respect [p]stop command

Signed-off-by: Guy <guyreis96@gmail.com>

* Black formatting
Fix Issue with auto play working when there is songs in the queue
Stop notifying queue ended if autoplay is on

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed a crash on track load timeout

Signed-off-by: Guy <guyreis96@gmail.com>

* [p]playlist start will now show the playlist name in embed body
Improved Logic for handling broken tracks when repeat is on.

Signed-off-by: Draper <guyreis96@gmail.com>

* Enqueue tracks as soon as we have the youtube URL ....

This basically changes how spotify urls are handled

Need to test saving spotify playlist
Need to test loading a spotify playlist from file
Need to test enqueuing a spotify playlist

Signed-off-by: Draper <guyreis96@gmail.com>

* Updated a track whrn enqueuing spotify playlist

Signed-off-by: Draper <guyreis96@gmail.com>

* Debug

Signed-off-by: Draper <guyreis96@gmail.com>

* Debug

Signed-off-by: Draper <guyreis96@gmail.com>

* Debug

Signed-off-by: Draper <guyreis96@gmail.com>

* Debug

Signed-off-by: Draper <guyreis96@gmail.com>

* Debug

Signed-off-by: Draper <guyreis96@gmail.com>

* Debug

Signed-off-by: Draper <guyreis96@gmail.com>

* Debug

Signed-off-by: Draper <guyreis96@gmail.com>

* Debug

Signed-off-by: Draper <guyreis96@gmail.com>

* Debug

Signed-off-by: Draper <guyreis96@gmail.com>

* Debug

Signed-off-by: Draper <guyreis96@gmail.com>

* Revert spotify_enqueue changes

Signed-off-by: Draper <guyreis96@gmail.com>

* Revert spotify_enqueue changes

Signed-off-by: Draper <guyreis96@gmail.com>

* Allow to set Lavalink jar version from Environment vars

Signed-off-by: Draper <guyreis96@gmail.com>

* Allow to set Lavalink jar version from Environment vars

Signed-off-by: Draper <guyreis96@gmail.com>

* Fix for a crash on Equalizer, Merge Spotify_enqueue changes and revert manager changes

Signed-off-by: Draper <guyreis96@gmail.com>

* Break playlist enqueue after 10 consecutive failures

Signed-off-by: Draper <guyreis96@gmail.com>

* Auto DC, is not compatible with Auto Play

Signed-off-by: Draper <guyreis96@gmail.com>

* Make notifier aware of guild its being called for

Signed-off-by: Draper <guyreis96@gmail.com>

* Type checking

Signed-off-by: Draper <guyreis96@gmail.com>

* Remove lock from 2 exits that i didn't before

Signed-off-by: Draper <guyreis96@gmail.com>

* Fixed TypeError: spotify_enqueue() got an unexpected keyword argument 'notify'

Signed-off-by: Guy <guyreis96@gmail.com>

* Reorder toggles to alphabetical order

Signed-off-by: Guy <guyreis96@gmail.com>

* Update Query to handle spotify URIs

Signed-off-by: Guy <guyreis96@gmail.com>

* update database

Signed-off-by: Guy <guyreis96@gmail.com>

* Dont say tracks enqued on invalid link
Make autop lay a mod only setting

Signed-off-by: Draper <guyreis96@gmail.com>

* Dont say tracks enqued on invalid spotify link

Signed-off-by: Draper <guyreis96@gmail.com>

* Set default age to 365 days

Signed-off-by: Draper <guyreis96@gmail.com>

* Allow Audio mods to set auto play playlists.
Save playlists songs to cache when migrating

Signed-off-by: Guy <guyreis96@gmail.com>

* Black formatting

Signed-off-by: Guy <guyreis96@gmail.com>

* [p]eq cooldown is not triggered is player check fails (i.e if nothing is currently playing)
Adding and removing reaction is no longer a blocking action

Signed-off-by: Guy <guyreis96@gmail.com>

* changelog for non blocking reaction handles

Signed-off-by: Guy <guyreis96@gmail.com>

* Show auto dc  and auto play settings by default

Signed-off-by: Guy <guyreis96@gmail.com>

* lint is being a bitch

Signed-off-by: Guy <guyreis96@gmail.com>

* lint changes

Signed-off-by: Draper <guyreis96@gmail.com>

* stop caching local tracks

Signed-off-by: Draper <guyreis96@gmail.com>

* List of Lavalink.Tracks natively added to Playlist Objects

Signed-off-by: Draper <guyreis96@gmail.com>

* Fix UX changes and should fix autoplay

Signed-off-by: Draper <guyreis96@gmail.com>

* Fixed Skip x number of tracks

Signed-off-by: Draper <guyreis96@gmail.com>

* Lint changes

Signed-off-by: Draper <guyreis96@gmail.com>

* Remvoe dead code

Signed-off-by: Draper <guyreis96@gmail.com>

* Update playlist embed formatting to reflect Preda's suggestions

Signed-off-by: Draper <guyreis96@gmail.com>

* Update change logs

Signed-off-by: Draper <guyreis96@gmail.com>

* Add `async with ctx.typing():` to queue and to local folder

Signed-off-by: Draper <guyreis96@gmail.com>

* Stop queuing now when queue is empty with [p]queue

Signed-off-by: Draper <guyreis96@gmail.com>

* fix ctx.typing()

Signed-off-by: Draper <guyreis96@gmail.com>

* fix ctx.typing()

Signed-off-by: Draper <guyreis96@gmail.com>

* Part 1

Signed-off-by: Draper <guyreis96@gmail.com>

* Dont check local track author and name if title is Unknown

Signed-off-by: Guy <guyreis96@gmail.com>

* Makes auto play more random

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixes local play
Fixed missing format

Signed-off-by: Guy <guyreis96@gmail.com>

* Query.process_input accept lavalink.Track objects

Signed-off-by: Draper <guyreis96@gmail.com>

* docstrings

Signed-off-by: Draper <guyreis96@gmail.com>

* Add TODO for timestamp support

Signed-off-by: Draper <guyreis96@gmail.com>

* Improve autoplay from cache logic (possibly slightly slower but more efficient overall)

Signed-off-by: Draper <guyreis96@gmail.com>

* Add My Lavalink PR as a dependency
Remember to remove this .... The PR will bump it to 0.3.2

Signed-off-by: Draper <guyreis96@gmail.com>

* Add My Lavalink PR as a dependency
Remember to remove this .... The PR will bump it to 0.3.2

Signed-off-by: Draper <guyreis96@gmail.com>

* Add My Lavalink PR as a dependency
Remember to remove this .... The PR will bump it to 0.3.2

Signed-off-by: Draper <guyreis96@gmail.com>

* Compile all regex at runtime

Signed-off-by: Draper <guyreis96@gmail.com>

* Fixes local play
Fixed missing format

Signed-off-by: Guy <guyreis96@gmail.com>

* Revert Dep error

Signed-off-by: Guy <guyreis96@gmail.com>

* black

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed attribute error

Signed-off-by: Guy <guyreis96@gmail.com>

* add `self.bot.dispatch("audio_disconnect", ctx.guild)` dispatch when the player is disconnected

Signed-off-by: Guy <guyreis96@gmail.com>

* Removed shuffle lock on skip

Signed-off-by: Guy <guyreis96@gmail.com>

* Better logic for auto seek (timestamps)

Signed-off-by: Guy <guyreis96@gmail.com>

* Better logic for auto seek (timestamps)

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixes timestamps on spotify tracks

Signed-off-by: Guy <guyreis96@gmail.com>

* Add ctx typing to playlist enqueue

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix Deps

Signed-off-by: Guy <guyreis96@gmail.com>

* Black formatting + Using new lavalink methods for shuffling

Signed-off-by: Guy <guyreis96@gmail.com>

* remove ctx.typing from playlist start

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixes typerror when enqueuing spotify playlists

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix keyerror

Signed-off-by: Guy <guyreis96@gmail.com>

* black formatting, + embed for [p]audioset cache as I forgot it before

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix Error on playlist upload

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix Text help for bump

Signed-off-by: Guy <guyreis96@gmail.com>

* Allow track bumping while shuffle is on

Signed-off-by: Guy <guyreis96@gmail.com>

* Edit bump embed to be consistent with other embed
Hyperlink tracks and removed dynamic title

Signed-off-by: Guy <guyreis96@gmail.com>

* Black

Signed-off-by: Guy <guyreis96@gmail.com>

* Errors not printing fix?

Signed-off-by: Guy <guyreis96@gmail.com>

* Errors not printing fix?

Signed-off-by: Guy <guyreis96@gmail.com>

* Track enqueued footer now shows correct track position when shuffle is on

Signed-off-by: Guy <guyreis96@gmail.com>

* Update changelogs

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix is_owner check in audioset settings

Signed-off-by: Guy <guyreis96@gmail.com>

* Changelogs

Signed-off-by: Guy <guyreis96@gmail.com>

* Dont store searches with no results in cache, fix malformated playlist to cache upon settings migration

Signed-off-by: Guy <guyreis96@gmail.com>

* _clear_lock_on_error > Needs to be reviewed to see if it has been done correctly

Signed-off-by: Guy <guyreis96@gmail.com>

* _clear_lock_on_error > Needs to be reviewed to see if it has been done correctly

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix Query search so that it works with absolute paths for localtracks

Signed-off-by: Guy <guyreis96@gmail.com>

* Extra error if lavalink is set to external and  the query is a localtrack and nothing is found

Signed-off-by: Guy <guyreis96@gmail.com>

* Black

Signed-off-by: Guy <guyreis96@gmail.com>

* More detailed error message

Signed-off-by: Guy <guyreis96@gmail.com>

* [p]seek and [p]skip can be used by user if they are the song requester while DJ mode is enabled, if votes are disabled. , [p]queue shuffle can be used to shuffle the queue manually. and [p]queue clean self can be used to remove all songs you requested from the queue.

Signed-off-by: Guy <guyreis96@gmail.com>

* black

Signed-off-by: Guy <guyreis96@gmail.com>

* All the fixes + a `should_auto_play` dispatch for the tech savy peeps

Signed-off-by: Guy <guyreis96@gmail.com>

* Spellchecker + Pythonic changes

Signed-off-by: Guy <guyreis96@gmail.com>

* NO spam for logs

Signed-off-by: Guy <guyreis96@gmail.com>

* Pass Current voice channel to `red_audio_should_auto_play` dispatch

Signed-off-by: Guy <guyreis96@gmail.com>

* Black

Signed-off-by: Guy <guyreis96@gmail.com>

* playlist upload also updates cache in the background

Signed-off-by: Guy <guyreis96@gmail.com>

* playlist upload also updates cache in the background

Signed-off-by: Guy <guyreis96@gmail.com>

* Add scope to playlist picker

Signed-off-by: Guy <guyreis96@gmail.com>

* Delete Playlist picker message once something is selected

Signed-off-by: Guy <guyreis96@gmail.com>

* OCD Fix

Signed-off-by: Guy <guyreis96@gmail.com>

* Facepalm

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix a Potential crash

Signed-off-by: Guy <guyreis96@gmail.com>

* Update my stupidity

Signed-off-by: Guy <guyreis96@gmail.com>

* Auto Pause +  Skip tracks already in playlist upon playlist append + a command to remove duplicated tracks from playlist

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix DJ mode when Role is deleted - Credits go to Neuro Assassin#4779
Fix an issue where auto play MAY not trigger

Signed-off-by: Guy <guyreis96@gmail.com>

* Change log to  Neuro Assassin#4779 fix

Signed-off-by: Guy <guyreis96@gmail.com>

* Black

Signed-off-by: Guy <guyreis96@gmail.com>

* Dont auto pause manual pauses

Signed-off-by: Guy <guyreis96@gmail.com>

* Adds `[p]autoplay` that can be run by mods or higher

Signed-off-by: Guy <guyreis96@gmail.com>

* 🤦

Signed-off-by: Guy <guyreis96@gmail.com>

* 2x 🤦

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed wrong import

Signed-off-by: Guy <guyreis96@gmail.com>

* Added Autoplay notify

Signed-off-by: Guy <guyreis96@gmail.com>

* Added Autoplay notify

Signed-off-by: Guy <guyreis96@gmail.com>

* Black

Signed-off-by: Guy <guyreis96@gmail.com>

* Store Track object as prev song instead of URI

Signed-off-by: Guy <guyreis96@gmail.com>

* Black why do u hate me

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix command name

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix Autoplay notify

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix missing await and TypeError, Thanks Flame

Signed-off-by: Guy <guyreis96@gmail.com>

* Add a list of tracks to show as a menu

Signed-off-by: Guy <guyreis96@gmail.com>

* adds the `[p]genre` command which uses the Spotify and Youtube API

Signed-off-by: Guy <guyreis96@gmail.com>

* Enqueue Playlists from genre command

Signed-off-by: Guy <guyreis96@gmail.com>

* Pretify `[p]genre`

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix a Typo and correct jukebox charge order

Signed-off-by: Guy <guyreis96@gmail.com>

* Add genre command to error handling

Signed-off-by: Guy <guyreis96@gmail.com>

* Type checking

Signed-off-by: Guy <guyreis96@gmail.com>

* Update naming scheme for `[p]genre`

Signed-off-by: Guy <guyreis96@gmail.com>

* Black why do you hate me

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixed `[p]local start`
Playlist picker auto selects if theres just 1 playlist found
`[p]queue cleanself` added

Signed-off-by: Guy <guyreis96@gmail.com>

* *sigh* back compatibility with old localtrack paths

Signed-off-by: Guy <guyreis96@gmail.com>

* *sigh* back compatibility with old localtrack paths, even more

Signed-off-by: Guy <guyreis96@gmail.com>

* *sigh* back compatibility with old localtrack paths Even more

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixes localtracks in playlist info command

Signed-off-by: Guy <guyreis96@gmail.com>

* Debug Local Strings

Signed-off-by: Guy <guyreis96@gmail.com>

* Debug Local Strings

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixes `[p]playlist info` for local tracks + fixed error in `[p]remove`

Signed-off-by: Guy <guyreis96@gmail.com>

* Black

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixes formatting in `[p]playlist info`

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix an issue with User Scope playlists were not being deleted

Signed-off-by: Guy <guyreis96@gmail.com>

* Typechecking

Signed-off-by: Guy <guyreis96@gmail.com>

* Black

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix the logic of `delegate_autoplay`

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix a Crash on Load due to type hinting

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix a Crash on Load due to type hintingBlack + fix order of `red_audio_should_auto_play`

Signed-off-by: Guy <guyreis96@gmail.com>

* Add `red_audio_initialized` dispatch so that ownership of auto play can be maintained after a reload

Signed-off-by: Guy <guyreis96@gmail.com>

* Check if the current owner is loaded before raising an error

Signed-off-by: Guy <guyreis96@gmail.com>

* Fixes the Existence Check in `delegate_autoplay`

Signed-off-by: Guy <guyreis96@gmail.com>

* Turns `own_autoplay` in a property of Audio and improves `delegate_autoplay` Thanks Sinbad!

Signed-off-by: Guy <guyreis96@gmail.com>

* Fix for Localtracks playlists

Signed-off-by: Guy <guyreis96@gmail.com>

* When disconnecting send `Disconnecting...`
Fix Stop after a skip
Fix UX discrepancy on Playlist IDs
Fixed Exception when theres a track error

Signed-off-by: Guy <guyreis96@gmail.com>

* add `on_red_audio_unload` dispatch

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Fix a crash on track start where `player.current` can be none?

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Missing new line

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Allow `--author` for playlist to be used to filter playlist for an specific author.
Plus a few bugfixes for UX

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Rename `remdupe` to `dedupe`
Make global scope always be referenced as Global
add missing backwards quotes around the Playlist ID for 1 string

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Towncrier entries for dep changes

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Remove track index when shuffle is on
Fix Progress bar for livestreams

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Trigger autoplay on `QUEUE_END` event instead of `TRACK_END`

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Can't reproduce Ians bug but here a safeguard agaisnt it just in case

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Fixes 2 Messages that had the wrong formatting

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* standerdize playlist naming scheme

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Fix `[p]autoplay` message when Notify is enabled

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* y u h8 me black

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Fix an issue with `[p]audioset localpath` where the localtracks folder was incorrect

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Pythonic formatting

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Ugh

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Fix a typo

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Silently try to delete messages + fixes error Ian found with `[p]genre`

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* sigh black

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Add humanize_number usage correctly

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Bump RLL to 0.4.0

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Update changelog entries

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Change `bot.db` to new API's added by #2967

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Additional reformatting

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Remove PyCharm noise + Fixes a few Pycharm warnings

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Rework `index` parsing for youtube urls

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Addess Aika's review

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Fix a potential crash, saves guild ID to playlists to avoid an scheme change in the future

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Add handling for Python installs without sqlite3.

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Address Flame's review

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Fix ma stupidity

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Address Aika's latest review.

1. Update docstring for `[p]playlist rename`.
2. Fix punctuation for playlist matching.
3. `[p]playlist update` now respect playlist management perms
4. Playlist management errors now shows playlist name, id and scope where possible
5. Remove duplicated code and dead code.

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>

* Pluralize string

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
2019-10-10 22:09:01 -04:00

496 lines
19 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import argparse
import functools
from typing import Optional, Tuple, Union
import discord
from redbot.core import Config, commands
from redbot.core.bot import Red
from redbot.core.i18n import Translator
from .playlists import PlaylistScope, standardize_scope
_ = Translator("Audio", __file__)
__all__ = [
"ComplexScopeParser",
"PlaylistConverter",
"ScopeParser",
"LazyGreedyConverter",
"standardize_scope",
"get_lazy_converter",
"get_playlist_converter",
]
_config = None
_bot = None
_SCOPE_HELP = """
Scope must be a valid version of one of the following:
Global
Guild
User
"""
_USER_HELP = """
Author must be a valid version of one of the following:
User ID
User Mention
User Name#123
"""
_GUILD_HELP = """
Guild must be a valid version of one of the following:
Guild ID
Exact guild name
"""
def _pass_config_to_converters(config: Config, bot: Red):
global _config, _bot
if _config is None:
_config = config
if _bot is None:
_bot = bot
class PlaylistConverter(commands.Converter):
async def convert(self, ctx: commands.Context, arg: str) -> dict:
global_scope = await _config.custom(PlaylistScope.GLOBAL.value).all()
guild_scope = await _config.custom(PlaylistScope.GUILD.value).all()
user_scope = await _config.custom(PlaylistScope.USER.value).all()
user_matches = [
(uid, pid, pdata)
for uid, data in user_scope.items()
for pid, pdata in data.items()
if arg == pid or arg.lower() in pdata.get("name", "").lower()
]
guild_matches = [
(gid, pid, pdata)
for gid, data in guild_scope.items()
for pid, pdata in data.items()
if arg == pid or arg.lower() in pdata.get("name", "").lower()
]
global_matches = [
(None, pid, pdata)
for pid, pdata in global_scope.items()
if arg == pid or arg.lower() in pdata.get("name", "").lower()
]
if not user_matches and not guild_matches and not global_matches:
raise commands.BadArgument(_("Could not match '{}' to a playlist.").format(arg))
return {
PlaylistScope.GLOBAL.value: global_matches,
PlaylistScope.GUILD.value: guild_matches,
PlaylistScope.USER.value: user_matches,
"arg": arg,
}
class NoExitParser(argparse.ArgumentParser):
def error(self, message):
raise commands.BadArgument()
class ScopeParser(commands.Converter):
async def convert(
self, ctx: commands.Context, argument: str
) -> Tuple[str, discord.User, Optional[discord.Guild], bool]:
target_scope: str = PlaylistScope.GUILD.value
target_user: Optional[Union[discord.Member, discord.User]] = ctx.author
target_guild: Optional[discord.Guild] = ctx.guild
specified_user = False
argument = argument.replace("", "--")
command, *arguments = argument.split(" -- ")
if arguments:
argument = " -- ".join(arguments)
else:
command = None
parser = NoExitParser(description="Playlist Scope Parsing.", add_help=False)
parser.add_argument("--scope", nargs="*", dest="scope", default=[])
parser.add_argument("--guild", nargs="*", dest="guild", default=[])
parser.add_argument("--server", nargs="*", dest="guild", default=[])
parser.add_argument("--author", nargs="*", dest="author", default=[])
parser.add_argument("--user", nargs="*", dest="author", default=[])
parser.add_argument("--member", nargs="*", dest="author", default=[])
if not command:
parser.add_argument("command", nargs="*")
try:
vals = vars(parser.parse_args(argument.split()))
except Exception as exc:
raise commands.BadArgument() from exc
if vals["scope"]:
scope_raw = " ".join(vals["scope"]).strip()
scope = scope_raw.upper().strip()
valid_scopes = PlaylistScope.list() + [
"GLOBAL",
"GUILD",
"AUTHOR",
"USER",
"SERVER",
"MEMBER",
"BOT",
]
if scope not in valid_scopes:
raise commands.ArgParserFailure("--scope", scope_raw, custom_help=_SCOPE_HELP)
target_scope = standardize_scope(scope)
elif "--scope" in argument and not vals["scope"]:
raise commands.ArgParserFailure("--scope", "Nothing", custom_help=_SCOPE_HELP)
is_owner = await ctx.bot.is_owner(ctx.author)
guild = vals.get("guild", None) or vals.get("server", None)
if is_owner and guild:
target_guild = None
guild_raw = " ".join(guild).strip()
if guild_raw.isnumeric():
guild_raw = int(guild_raw)
try:
target_guild = ctx.bot.get_guild(guild_raw)
except Exception:
target_guild = None
guild_raw = str(guild_raw)
if target_guild is None:
try:
target_guild = await commands.GuildConverter.convert(ctx, guild_raw)
except Exception:
target_guild = None
if target_guild is None:
try:
target_guild = await ctx.bot.fetch_guild(guild_raw)
except Exception:
target_guild = None
if target_guild is None:
raise commands.ArgParserFailure("--guild", guild_raw, custom_help=_GUILD_HELP)
elif not is_owner and (guild or any(x in argument for x in ["--guild", "--server"])):
raise commands.BadArgument("You cannot use `--guild`")
elif any(x in argument for x in ["--guild", "--server"]):
raise commands.ArgParserFailure("--guild", "Nothing", custom_help=_GUILD_HELP)
author = vals.get("author", None) or vals.get("user", None) or vals.get("member", None)
if author:
target_user = None
user_raw = " ".join(author).strip()
if user_raw.isnumeric():
user_raw = int(user_raw)
try:
target_user = ctx.bot.get_user(user_raw)
except Exception:
target_user = None
user_raw = str(user_raw)
if target_user is None:
member_converter = commands.MemberConverter()
user_converter = commands.UserConverter()
try:
target_user = await member_converter.convert(ctx, user_raw)
except Exception:
try:
target_user = await user_converter.convert(ctx, user_raw)
except Exception:
target_user = None
if target_user is None:
try:
target_user = await ctx.bot.fetch_user(user_raw)
except Exception:
target_user = None
if target_user is None:
raise commands.ArgParserFailure("--author", user_raw, custom_help=_USER_HELP)
else:
specified_user = True
elif any(x in argument for x in ["--author", "--user", "--member"]):
raise commands.ArgParserFailure("--scope", "Nothing", custom_help=_USER_HELP)
return target_scope, target_user, target_guild, specified_user
class ComplexScopeParser(commands.Converter):
async def convert(
self, ctx: commands.Context, argument: str
) -> Tuple[
str,
discord.User,
Optional[discord.Guild],
bool,
str,
discord.User,
Optional[discord.Guild],
bool,
]:
target_scope: str = PlaylistScope.GUILD.value
target_user: Optional[Union[discord.Member, discord.User]] = ctx.author
target_guild: Optional[discord.Guild] = ctx.guild
specified_target_user = False
source_scope: str = PlaylistScope.GUILD.value
source_user: Optional[Union[discord.Member, discord.User]] = ctx.author
source_guild: Optional[discord.Guild] = ctx.guild
specified_source_user = False
argument = argument.replace("", "--")
command, *arguments = argument.split(" -- ")
if arguments:
argument = " -- ".join(arguments)
else:
command = None
parser = NoExitParser(description="Playlist Scope Parsing.", add_help=False)
parser.add_argument("--to-scope", nargs="*", dest="to_scope", default=[])
parser.add_argument("--to-guild", nargs="*", dest="to_guild", default=[])
parser.add_argument("--to-server", nargs="*", dest="to_server", default=[])
parser.add_argument("--to-author", nargs="*", dest="to_author", default=[])
parser.add_argument("--to-user", nargs="*", dest="to_user", default=[])
parser.add_argument("--to-member", nargs="*", dest="to_member", default=[])
parser.add_argument("--from-scope", nargs="*", dest="from_scope", default=[])
parser.add_argument("--from-guild", nargs="*", dest="from_guild", default=[])
parser.add_argument("--from-server", nargs="*", dest="from_server", default=[])
parser.add_argument("--from-author", nargs="*", dest="from_author", default=[])
parser.add_argument("--from-user", nargs="*", dest="from_user", default=[])
parser.add_argument("--from-member", nargs="*", dest="from_member", default=[])
if not command:
parser.add_argument("command", nargs="*")
try:
vals = vars(parser.parse_args(argument.split()))
except Exception as exc:
raise commands.BadArgument() from exc
is_owner = await ctx.bot.is_owner(ctx.author)
valid_scopes = PlaylistScope.list() + [
"GLOBAL",
"GUILD",
"AUTHOR",
"USER",
"SERVER",
"MEMBER",
"BOT",
]
if vals["to_scope"]:
to_scope_raw = " ".join(vals["to_scope"]).strip()
to_scope = to_scope_raw.upper().strip()
if to_scope not in valid_scopes:
raise commands.ArgParserFailure(
"--to-scope", to_scope_raw, custom_help=_SCOPE_HELP
)
target_scope = standardize_scope(to_scope)
elif "--to-scope" in argument and not vals["to_scope"]:
raise commands.ArgParserFailure("--to-scope", "Nothing", custom_help=_SCOPE_HELP)
if vals["from_scope"]:
from_scope_raw = " ".join(vals["from_scope"]).strip()
from_scope = from_scope_raw.upper().strip()
if from_scope not in valid_scopes:
raise commands.ArgParserFailure(
"--from-scope", from_scope_raw, custom_help=_SCOPE_HELP
)
source_scope = standardize_scope(from_scope)
elif "--from-scope" in argument and not vals["to_scope"]:
raise commands.ArgParserFailure("--to-scope", "Nothing", custom_help=_SCOPE_HELP)
to_guild = vals.get("to_guild", None) or vals.get("to_server", None)
if is_owner and to_guild:
target_guild = None
to_guild_raw = " ".join(to_guild).strip()
if to_guild_raw.isnumeric():
to_guild_raw = int(to_guild_raw)
try:
target_guild = ctx.bot.get_guild(to_guild_raw)
except Exception:
target_guild = None
to_guild_raw = str(to_guild_raw)
if target_guild is None:
try:
target_guild = await commands.GuildConverter.convert(ctx, to_guild_raw)
except Exception:
target_guild = None
if target_guild is None:
try:
target_guild = await ctx.bot.fetch_guild(to_guild_raw)
except Exception:
target_guild = None
if target_guild is None:
raise commands.ArgParserFailure(
"--to-guild", to_guild_raw, custom_help=_GUILD_HELP
)
elif not is_owner and (
to_guild or any(x in argument for x in ["--to-guild", "--to-server"])
):
raise commands.BadArgument("You cannot use `--to-server`")
elif any(x in argument for x in ["--to-guild", "--to-server"]):
raise commands.ArgParserFailure("--to-server", "Nothing", custom_help=_GUILD_HELP)
from_guild = vals.get("from_guild", None) or vals.get("from_server", None)
if is_owner and from_guild:
source_guild = None
from_guild_raw = " ".join(from_guild).strip()
if from_guild_raw.isnumeric():
from_guild_raw = int(from_guild_raw)
try:
source_guild = ctx.bot.get_guild(from_guild_raw)
except Exception:
source_guild = None
from_guild_raw = str(from_guild_raw)
if source_guild is None:
try:
source_guild = await commands.GuildConverter.convert(ctx, from_guild_raw)
except Exception:
source_guild = None
if source_guild is None:
try:
source_guild = await ctx.bot.fetch_guild(from_guild_raw)
except Exception:
source_guild = None
if source_guild is None:
raise commands.ArgParserFailure(
"--from-guild", from_guild_raw, custom_help=_GUILD_HELP
)
elif not is_owner and (
from_guild or any(x in argument for x in ["--from-guild", "--from-server"])
):
raise commands.BadArgument("You cannot use `--from-server`")
elif any(x in argument for x in ["--from-guild", "--from-server"]):
raise commands.ArgParserFailure("--from-server", "Nothing", custom_help=_GUILD_HELP)
to_author = (
vals.get("to_author", None) or vals.get("to_user", None) or vals.get("to_member", None)
)
if to_author:
target_user = None
to_user_raw = " ".join(to_author).strip()
if to_user_raw.isnumeric():
to_user_raw = int(to_user_raw)
try:
source_user = ctx.bot.get_user(to_user_raw)
except Exception:
source_user = None
to_user_raw = str(to_user_raw)
if target_user is None:
member_converter = commands.MemberConverter()
user_converter = commands.UserConverter()
try:
target_user = await member_converter.convert(ctx, to_user_raw)
except Exception:
try:
target_user = await user_converter.convert(ctx, to_user_raw)
except Exception:
target_user = None
if target_user is None:
try:
target_user = await ctx.bot.fetch_user(to_user_raw)
except Exception:
target_user = None
if target_user is None:
raise commands.ArgParserFailure("--to-author", to_user_raw, custom_help=_USER_HELP)
else:
specified_target_user = True
elif any(x in argument for x in ["--to-author", "--to-user", "--to-member"]):
raise commands.ArgParserFailure("--to-user", "Nothing", custom_help=_USER_HELP)
from_author = (
vals.get("from_author", None)
or vals.get("from_user", None)
or vals.get("from_member", None)
)
if from_author:
source_user = None
from_user_raw = " ".join(from_author).strip()
if from_user_raw.isnumeric():
from_user_raw = int(from_user_raw)
try:
target_user = ctx.bot.get_user(from_user_raw)
except Exception:
source_user = None
from_user_raw = str(from_user_raw)
if source_user is None:
member_converter = commands.MemberConverter()
user_converter = commands.UserConverter()
try:
source_user = await member_converter.convert(ctx, from_user_raw)
except Exception:
try:
source_user = await user_converter.convert(ctx, from_user_raw)
except Exception:
source_user = None
if source_user is None:
try:
source_user = await ctx.bot.fetch_user(from_user_raw)
except Exception:
source_user = None
if source_user is None:
raise commands.ArgParserFailure(
"--from-author", from_user_raw, custom_help=_USER_HELP
)
else:
specified_source_user = True
elif any(x in argument for x in ["--from-author", "--from-user", "--from-member"]):
raise commands.ArgParserFailure("--from-user", "Nothing", custom_help=_USER_HELP)
return (
source_scope,
source_user,
source_guild,
specified_source_user,
target_scope,
target_user,
target_guild,
specified_target_user,
)
class LazyGreedyConverter(commands.Converter):
def __init__(self, splitter: str):
self.splitter_Value = splitter
async def convert(self, ctx: commands.Context, argument: str) -> str:
full_message = ctx.message.content.partition(f" {argument} ")
if len(full_message) == 1:
full_message = (
(argument if argument not in full_message else "") + " " + full_message[0]
)
elif len(full_message) > 1:
full_message = (
(argument if argument not in full_message else "") + " " + full_message[-1]
)
greedy_output = (" " + full_message.replace("", "--")).partition(
f" {self.splitter_Value}"
)[0]
return f"{greedy_output}".strip()
def get_lazy_converter(splitter: str) -> type:
"""
Returns a typechecking safe `LazyGreedyConverter` suitable for use with discord.py.
"""
class PartialMeta(type(LazyGreedyConverter)):
__call__ = functools.partialmethod(type(LazyGreedyConverter).__call__, splitter)
class ValidatedConverter(LazyGreedyConverter, metaclass=PartialMeta):
pass
return ValidatedConverter
def get_playlist_converter() -> type:
"""
Returns a typechecking safe `PlaylistConverter` suitable for use with discord.py.
"""
class PartialMeta(type(PlaylistConverter)):
__call__ = functools.partialmethod(type(PlaylistConverter).__call__)
class ValidatedConverter(PlaylistConverter, metaclass=PartialMeta):
pass
return ValidatedConverter