Compare commits

..

799 Commits

Author SHA1 Message Date
palmtree5
194db33746 Readd the classes for games from the old PR branch 2020-04-17 22:05:26 -08:00
palmtree5
a3ca78b64a Remove the last remnant of communities 2020-04-17 21:38:32 -08:00
palmtree5
6a3a79947a Merge remote-tracking branch 'release/V3/develop' into V3/develop 2020-04-17 21:21:03 -08:00
Ryan
66fc28ec1b [Trivia] Correct spelling of compact disc in games.yaml (#3759) 2020-04-15 00:08:17 +02:00
Kowlin
10ad2a559a [Core] Support setting avatar via attachment (#3747)
* [Core] Support avatars via attachments

* Fix the thing I was actually annoyed about.

* Updated error texts

* English is a damn annoying language.
2020-04-13 02:27:55 +02:00
MiniJennJenn
b8ac70e59a Update leagueoflegends.yaml 2020-04-12 17:41:42 -04:00
jack1142
7492636818 Fix ignored channels list in [p]ignore (#3746)
* Fix ignored channels list

* Update settings_caches.py

* Update core_commands.py
2020-04-12 00:09:05 +02:00
github-actions[bot]
36a0eabf4a Automated Crowdin downstream (#3739)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-04-09 21:27:35 +02:00
jack1142
cf4fdbbab1 dev bump (#3736) 2020-04-09 00:39:48 +02:00
Kowlin
f1b6b5f6f9 Update changelog_3_3_0.rst (#3737) 2020-04-09 00:32:20 +02:00
Kowlin
e06b3fceb5 Version bump (#3735) 2020-04-09 00:26:43 +02:00
jack1142
4628dd07e4 Red 3.3.5 - Changelog (#3727)
* Add 3.3.5 section

* PR 3730

* PR 3734

* Remove empty sections and add release date
2020-04-09 00:24:09 +02:00
jack1142
55a3d9b157 Properly check for command's existence in [p]alias add (#3734) 2020-04-09 00:19:14 +02:00
jack1142
c70c1d97e5 Revert "[CI] Less weekly churn from automated tasks (#3548)" (#3732)
This reverts commit f6c85cd37a.
2020-04-08 17:25:30 +02:00
Kowlin
da4e4d4ad0 Stop Outdated field from showing in [p]info when up-to-date (#3730)
* Oudated-B-Gone!

* Meh

* mehhh
2020-04-07 10:01:51 +02:00
Kowlin
3d9ee3f2b4 Dev version bump, for realzies (#3726)
* Damn tests that keep breaking if you forget a single damn number!

Who designed this shit?!

* Update __init__.py
2020-04-05 04:10:32 +02:00
Kowlin
b9331ffa55 Update __init__.py (#3725) 2020-04-05 04:05:53 +02:00
jack1142
2b0935c4aa Red 3.3.4 - Changelog (#3706)
* Add 3.3.4 section

* PR 3710 (issue 3545)

* PR 3708

* PR 3717

* PR 3723

* PR 3707

* Add release date, remove unused section
2020-04-05 03:58:39 +02:00
Kowlin
9d7047864e Version bump (#3724) 2020-04-05 03:57:50 +02:00
jack1142
20d507dbef Fix bank check (used in Bank, Economy and Trivia cogs) 2020-04-05 03:49:02 +02:00
jack1142
be7d1d2cd2 Bump deps (includes d.py bump) (#3723) 2020-04-05 03:34:53 +02:00
github-actions[bot]
587968710f Automated Crowdin downstream (#3719)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-04-04 06:28:55 +02:00
jack1142
a0b30bb3ae Fix Flame's irk with code blocks in windows install guide (#3722)
* Update install_windows.rst

* God, I'm an idiot
2020-04-04 03:08:39 +02:00
kennnyshiwa
60e954634d Include domains added in #3717 in filter_invites's docstring (#3721) 2020-04-02 20:17:46 +02:00
kennnyshiwa
ffecf1ed15 Fix redbot.core.utils.common_filters.INVITE_URL_RE regex (#3717)
* Fix invite_url_re

Found that the regex used previously could falsely catch strings such as discord member and discord gggggg which aren't invites and shouldn't be caught, testing with this regex string still catches all invite url variations while allowing these other previously caught strings to keep working

* Update redbot/core/utils/common_filters.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-04-02 09:11:12 +02:00
jack1142
b7a142c21e Include versions for pre-requirements in Windows install guide (#3708)
* Update install_windows.rst

* Update install_linux_mac.rst
2020-03-30 10:00:01 +02:00
jack1142
5716cebb0f Send error in [p]alias add when target command doesn't exist (#3710) 2020-03-30 06:48:51 +02:00
jack1142
ff68538085 Bump version to 3.3.4.dev1 (#3705) 2020-03-29 00:02:01 +01:00
Kowlin
7c251b8017 Bump version to 3.3.3 (#3703)
* Bump version to 3.3.3

* Update manager.py
2020-03-28 23:51:09 +01:00
jack1142
bf5917177a Red 3.3.3 - Changelog (#3660)
* Add 3.3.3 section

* PR 3630 (issue 3628)

* PR 3589 (issue 3145)

* PR 3631

* PR 3627

* PR 3504 (issue 3465)

* PR 3632 (issue 3626)

* PR 2929 (issue 2800)

* PR 3646 (issue 3590)

* PR 2382

* PR 3659

* PR 3653 (issue 3633)

* PR 3668 (issue 3583)

* PR 3663

* PR 3666 (issue 3584)

* PR 3656

* PR 3644

* PR 3420 (issue 3307)

* PR 3643 (issue 3642)

* PR 3638 (issue 3636)

* PR 3665 (issue 2988)

* PR 3667 (issue 3044)

* PR 3689 (issue 3685)

* PR 3688

* PR 3675 (issue 3436)

* PR 3695 (issue 2826)

* PR 3027

* PR 3637 (issue 3639)

* PR 3674 (issue 3443)

* PR 3690

* PR 3673 (issue 3507)

* PR 3652 (issue 3645)

* PR 2573

* PR 3657

* PR 3678 (issue 3448)

* PR 3684

* PR 3700

* PR 3669

* PR 3649 (issue 3648)

* PR 3676 (issue 3596)

* PR 3677 (issue 3588)

* PR 3672 (issue 3233)

* PR 3704

* Add release date
2020-03-28 23:50:50 +01:00
jack1142
dacfb931bb remove dis (#3704) 2020-03-28 23:45:37 +01:00
jack1142
97d77f5c51 Make checks in Bank, Economy and Trivia cogs Permissions-friendly (#3672)
* Create converters.py

* Update trivia.py

* Create checks.py

* Update checks.py

* Update checks.py

* Update trivia.py

* Update checks.py

* Update checks.py

* Update trivia.py

* Update bank.py

* Update economy.py

* Update trivia.py

* Update checks.py

* Update checks.py

* Update __init__.py
2020-03-28 23:24:12 +01:00
jack1142
fce8186759 Add [p]set regionalformat command to set regional formatting (#3677)
* Add `[p]set region` command to set regional formatting

* Add Babel to intersphinx

* rename 'region' to 'regional format`
2020-03-28 23:23:02 +01:00
jack1142
d35f6abca0 Use Babel in [p]set locale to allow any valid locale (#3676)
* Use Babel in `[p]set locale` to allow any valid locale

* Remove `[p]listlocales` and link to Crowdin in `[p]set locale`

* import babel.Locale as BabelLocale to avoid confousion
2020-03-28 23:11:25 +01:00
jack1142
cf31c22e5d Revert "Add changelog label to merged PRs (#3664)" (#3702)
This reverts commit 3d7ff7a149.
2020-03-28 23:06:41 +01:00
jack1142
3f7c2e8842 Update dev_commands.py (#3649) 2020-03-28 23:05:50 +01:00
PredaaA
ecdcc27749 Fix count of streaming members and a typo in [p]serverinfo (#3701) 2020-03-28 20:15:16 +01:00
kennnyshiwa
497d244f95 Show member's activities in [p]userinfo command (#3669)
* [mod] status improvements in userinfo command

Adds support for statues in the userinfo command including custom status, playing and listening improvements

* fix style

* maybe fix style now

* Update redbot/cogs/mod/names.py

Co-Authored-By: Draper <27962761+Drapersniper@users.noreply.github.com>

* fix spaces

* address review and add emojis

* fix broken variable

* Update redbot/cogs/mod/names.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/names.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/names.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/names.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/names.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update names.py

* Update names.py

* Update redbot/cogs/mod/names.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update names.py

* Update redbot/cogs/mod/names.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/names.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

Co-authored-by: Draper <27962761+Drapersniper@users.noreply.github.com>
Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-03-28 19:44:56 +01:00
jack1142
a738736d6b Update __init__.py (#3700) 2020-03-28 19:30:16 +01:00
jack1142
a4ce2d01ce Use choco upgrade, not choco install (#3684)
* Use `choco upgrade`, not `choco install` and add `--version` for Python

* Update install_windows.rst

* actually, nah
2020-03-28 16:08:25 +01:00
jack1142
41c2b76d8d Make [p]cog checkforupdates indicate when cog can't be updated on current Red/Python version 2020-03-28 15:53:10 +01:00
jack1142
35ebc4899e Log failures when requesting Twitch bearer token (#3657)
* [Streams] Log failures when requesting Twitch bearer token

* Oops

* Style
2020-03-28 15:51:36 +01:00
jack1142
9552d210f5 Remove towncrier (#3661) 2020-03-28 02:46:15 +01:00
Neuro Assassin
9370b5110e Add [p]cc search command (#2573)
* Add search command for Custom Commands

* Spelling mistake

* Create 2573.feature.rst

* Remove `bot.db` usage, improve returned message

* remove towncrier entry

* Fix CI errors

* Fix more CI errors

* Add `@staticmethod` decorator

Co-authored-by: Toby Harradine <Tobotimus@users.noreply.github.com>
Co-authored-by: Michael H <michael@michaelhall.tech>
Co-authored-by: Kowlin <Kowlin@users.noreply.github.com>
Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-03-28 02:11:36 +01:00
jack1142
7009be8922 white/blacklist commands now properly require at least one user passed (#3652) 2020-03-28 01:38:46 +01:00
jack1142
c1a96c83fb Update _internal_utils.py (#3673) 2020-03-28 01:37:59 +01:00
jack1142
5d66e4eaf8 Update streamtypes.py (#3690) 2020-03-28 01:35:51 +01:00
jack1142
ed267d17f2 [ModLog] Keep last known username in case's data (#3674) 2020-03-28 01:34:40 +01:00
jack1142
6e91ebeb3d Use surrogateescape when decoding subprocess's stream (#3639)
* use surrogateescape when decoding subprocess's stream

* stop using surrogateescape for logging
2020-03-28 01:14:54 +01:00
jack1142
d70c6e1734 Update mod.py (#3683) 2020-03-28 01:05:25 +01:00
PredaaA
4de4c32c0e Bump Red-Lavalink (#3687) 2020-03-28 00:46:09 +01:00
Jeremiah Boby
637ae34839 Print quickstart guide at the end of redbot-setup (#3027)
* Print quickstart guide during setup

Resolves #3025

* Refer users to stable documentation
2020-03-26 22:33:57 +01:00
jack1142
d23144bc61 [Docs] Fix Mac instructions in the install guide (#3675)
* Update install_linux_mac.rst

* Address review

* Use new Homebrew installer

* Add python@3.8 to PATH
2020-03-26 22:33:12 +01:00
FancyJesse
e90868072e [Cleanup] Add [p]cleanup spam command (#3688)
* Added `[p]cleanup spam` function

* formatting

* accepts number and considers command message to total counts

* use existing kwarg instead

Safer to use the before kwarg as other messages might get included accidentally between invoking and executing

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* safer way to compare embeds and skip attachment only msgs

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* keeps the original message before the spam starts

* now asks if its ok to delete 100+ spam msgs if found

* unnecessary calculations, just add after

* code styling and reverted history oldest_first option

* switched to recommended logging formatting, thank you jack1142

* now using `get_messages_for_deletion()`, added needed limit kwarg

* ugh forgot to swap em

* duh

* small string clarification

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-03-26 22:32:10 +01:00
Kowlin
980d2a8dc3 Removal of Dispatch, since its not working. (#3696)
* Removal of Dispatch, since its not working.

* Add changelog behavior
2020-03-26 14:13:30 +01:00
github-actions[bot]
eef5ddb416 Automated Crowdin downstream (#3697)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-03-26 14:06:51 +01:00
jack1142
3d7ff7a149 Add changelog label to merged PRs (#3664) 2020-03-26 13:56:10 +01:00
Stonedestroyer
287edb755d Fix misleading error appearing when repo name is already taken (#3695)
* Error string change

* String
2020-03-26 13:26:05 +01:00
Dav
8c612a96c8 [Streams] Make preview picture for stream alerts bigger (#3689) 2020-03-25 11:22:33 +01:00
jack1142
efe67e2acc [Image] Add [count] argument to [p]imgur commands (#3667)
* Update image.py

* Update image.py

* welp

* Update image.py

* Add `count` arg to `[p]imgur subreddit`

* me dumb (a little)
2020-03-24 00:42:27 +01:00
jack1142
2cdf3c16ab Make command errors translatable (#3665)
* Make command errors translatable

* style

* moar style

* Update events.py

* Separate strings to make it better for translators
2020-03-24 00:35:32 +01:00
PredaaA
a88b2af4a9 [General] Fix streaming count in serverinfo command. (#3680)
* [General] Fix streaming count on serverinfo command.

* Update general.py

* Update general.py

* is
2020-03-21 22:23:56 +01:00
TrustyJAID
b9f07e8684 [Mod] Move deletedelay to core (#3638)
* Move deletedelay to core

* line lengths

* address review

* move settings change

* fine...
2020-03-21 18:39:58 +01:00
TrustyJAID
d957e44e1e Fix issues with the black/whitelist cache (#3643)
* Fix issues with the black/whitelist cache

* Address review

* address review

* or
2020-03-21 18:14:49 +01:00
Stonedestroyer
15e3437001 [Trivia] Commands for managing custom trivia lists (#3420)
* Trivia upload

* Second push

* Black

* A bit further

* Logic fix

* Getting closer

* Almost there

* Abomination ready for review.

Abomination ready for review.

* Upload trivia review

* Black

* Fix debug

* Delete a trivia

* List cleaned.

* Make it nicer

* Pass black

* Unlink file

* Translation

* Thanks Jack

* Beautify

* Black

* Fix

* Handling empty custom lists

* Use existing fileobject.

* Black

* Test

* Change up

* Black

* Changelog

* Typo error

* Fix up issues.

* Fixes stuff.

* Styling

* Add reactions

* Reactions

* Add safe handling of reactions

* Style

* Fix grammar and remove obsolete code

* Timeout

* Fix

* Requested changes

* Styling

* Fixes

* Typehint

* Remove

* mistake

* Constant

* Style

* return
2020-03-21 16:30:33 +01:00
jack1142
eebea59fe3 Remove usage of loop arg in calls to start_adding_reactions (#3644)
* fix stacklevels of warnings

* stop using loop arg when calling start_adding_reactions

* [Audio] Stop using loop arg when calling start_adding_reactions
2020-03-20 20:44:57 +01:00
jack1142
5074f2dbab Improve error message for OSError in [p]repo add (#3656)
* Update downloader.py

* Update downloader.py
2020-03-20 20:40:59 +01:00
github-actions[bot]
0f364a6d13 Automated Crowdin downstream (#3671)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-03-19 23:38:27 +01:00
jack1142
d9db03bca3 Tell the command caller when bot was unable to send warn (#3653) 2020-03-19 08:23:07 +01:00
jack1142
b9e2e1098f [Trivia] Forbid use of non-finite numbers in [p]triviaset subcommands (#3668)
* Update trivia.py

* style
2020-03-19 08:21:54 +01:00
jack1142
a126da5f23 Prevent PyPI's unavailability from causing command failure (#3663) 2020-03-19 08:19:01 +01:00
jack1142
23eae27a8f Update session.py (#3666) 2020-03-19 08:17:32 +01:00
github-actions[bot]
3e1bb88ab7 Automated Crowdin downstream (#3650)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-03-18 20:48:15 +01:00
PredaaA
4de7ce32dd [General] Add shard ID to serverinfo if bot uses multiple shards (#3659)
* [General] Add shard ID on serverinfo if there is over than two shards

* Update general.py

* Update general.py

* Update general.py

* Update general.py
2020-03-16 01:42:35 +01:00
PredaaA
6128d54601 [General] Add toggle to [p]serverinfo that shows more details (#2382)
* Improvements of "serverinfo" command

- Added some more informations about the server.
  (Verification level, AFK channel and timeout, custom emojis and special features).
- Added custom texts for Region (and Verification levels).
- Added more details about members (Number of humans, bots, and all status).
- Show special features only if the server has one.
- Show "Verified" logo only if the server is verified.
- And changed the footer by adding how many time the bot as join the server.

* Black formatting for serverinfo

* Black formatting fix and Sinbad suggestions

* Fix the discord.errors.NotFound on server command

Added exception on server command for the phrase "To leave a server, just type its number.".

* Update core_commands.py

* Delete core_commands.py

* Create core_commands.py

* Update core_commands.py

* Update core_commands.py

* Delete core_commands.py

* Create core_commands.py

* Update core_commands.py

* Update core_commands.py

* Little changes on serverinfo command

- Adding "lurkers" check, for the new feature that are on bot which allow users to join a server in read-only (Works only on verified servers for now)
- Adding streaming status.
- Adding mobile status with suggestion of Sinbad.

* Black style fix

* Add verbose for serverinfo and disable lurkers

I've added a bool that are False by default and sent the original serverinfo command, and if set to True send my edited version.
And I've disabled the lurkers detection until this bug (https://github.com/discordapp/discord-api-docs/issues/855) isn't fixed.

* Add of India region

* Code cleanup

* Emojis are not needed to be translated

* Update serverinfo

* Changelog.

* Update 2382.enhancement.rst

* Adress Jack's requests changes.

* oops

* Put guild description first + few last nitpicks

Co-authored-by: PredaMart <46051820+PredaMart@users.noreply.github.com>
Co-authored-by: Toby Harradine <Tobotimus@users.noreply.github.com>
Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-03-15 23:46:21 +01:00
Dav
4d81887bcf [Core] Unify commands that set bot's activity status (#3646)
* alias [p]set game

* Implement suggestions
2020-03-12 22:46:19 +01:00
Ianardo DiCaprio
2e5dc82692 [Warnings] Add setting for warn channel and DMing warns toggle (#2929)
* Initial Commit

* Add changelog

* Review changes

* Changed to use contextlib.suppress

* Update warnings.py

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Rename 2932.bugfix.rst.txt to 2932.bugfix.rst

* Rename 2929.enhance.rst.txt to 2929.enhance.rst

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: Draper <27962761+Drapersniper@users.noreply.github.com>

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: Draper <27962761+Drapersniper@users.noreply.github.com>

* Update redbot/cogs/warnings/warnings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Rename 2929.enhance.rst to 2929.feature.rst

* Update warnings.py

* Add files via upload

* Update warnings.py

* Black

* Update warnings.py

* black

* Update warnings.py

* Update warnings.py

* Delete 2932.bugfix.rst

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
Co-authored-by: Draper <27962761+Drapersniper@users.noreply.github.com>
2020-03-12 21:17:59 +01:00
jack1142
4afe1ff569 Use correct prefixes when sending messages to owners (#3632)
* use correct prefixes when sending messages to owners

* make internal util for this instead

* oops

* fix circular import

* let's add back the actual fix I made this PR for...

* fix wrong logger name

* improve log message
2020-03-12 17:31:33 +01:00
Kowlin
3ff127d514 Automated Crowdin downstream (#3640) 2020-03-05 22:01:26 +01:00
Kowlin
ea3ca66303 Handle users leaving the server while we're applying mutes (#3627)
* Handle when users leave a server while we're applying mutes

Goddamn guilds with >100 text channels >.<

* Damn you tox.

* Damn you Bread.

* Fixed a bug whereby Guild would always return a success

Also handled error checking on NotFound so that the propper handling can be done.

* Fixed flake8 compile error (whoops)

* Revert "Fixed flake8 compile error (whoops)"

This reverts commit ec8b672347.

* Revert "Fixed a bug whereby Guild would always return a success"

This reverts commit 8d08ac31af.

* Apply the lost commits that we actually want

Since cherry picking is hard.

* Isn't the English language FUN!?

* *sigh*
2020-03-01 21:11:46 +01:00
jack1142
279f0e4f6c Update streamtypes.py (#3631) 2020-02-29 17:47:50 +01:00
Stonedestroyer
61fc00dc08 [Permissions] Alwaysavailable error message (#3504)
* [Permissions] Fix message for always available commands.

* Changelog
2020-02-29 16:29:31 +01:00
jack1142
d6f9ddc3af Check permissions before trying to clear reactions (#3589) 2020-02-29 16:00:19 +01:00
Stonedestroyer
b52c838018 Fix game command empty argument. (#3630) 2020-02-29 15:35:44 +01:00
jack1142
e0de25ed65 dev bump (#3624) 2020-02-28 22:23:59 +01:00
Kowlin
03a2c11225 Bump the version to 3.3.2 (#3622) 2020-02-28 22:15:07 +01:00
jack1142
52480783ee 3.3.2 changelog (#3621)
* 3.3.2 changelog

* few fixes

* add contributors

* that one last change from milestone
2020-02-28 21:48:32 +01:00
Michael H
f6361992e3 Use a metaclass for config's singletons (#3137)
* Usea metaclass for config's singletons

* make this a little safer
2020-02-28 21:37:35 +01:00
jack1142
eedec4ff02 fix: use clean prefix in code blocks (#3591) 2020-02-28 21:23:13 +01:00
jack1142
7b042be9db bump Red-Lavalink (#3623) 2020-02-28 21:21:17 +01:00
jack1142
b2cba6b6d7 docs(utils): add headers for each event predicate (#3595) 2020-02-28 21:20:59 +01:00
jack1142
0ff000d660 Fix config conversion in image cog (#3617) 2020-02-28 21:18:23 +01:00
jack1142
4e4420af2e Replace [p] with clean prefix in install messages (#3592) 2020-02-28 21:17:45 +01:00
jack1142
9ea7262352 Bump deps (includes d.py and Sphinx bump) (#3609) 2020-02-28 20:37:30 +01:00
jack1142
227009733e Revert "new mention behavior, new filter behavior (#3553)" (#3619)
* Revert "new mention behavior, new filter behavior (#3553)"

This reverts commit 066bf516d9.

* keep invalid escape fix
2020-02-28 20:34:51 +01:00
aikaterna
136fcd7bb2 [Audio] Update Lavalink.jar version (#3620) 2020-02-28 18:50:13 +00:00
Flame442
67ab59106f [Trivia] Better handling for session errors (#3606)
* Better handling for session errors

* Use `stop` instead of `end_game`, translation support
2020-02-28 19:47:01 +01:00
jack1142
e8b975a095 [Core] Use new PyPI urls (#3607)
* fix: use new PyPI urls

* Update core_commands.py

* Update core_commands.py
2020-02-28 02:43:21 +01:00
Stonedestroyer
ad4a75bdc1 Deprecation warning - alternative (#3615)
* Alternative solution

* BLACK

* Args

* Changes

* style

* Ordering

* Debug shows warnings

* style
2020-02-28 02:22:50 +01:00
jack1142
582f64b2e7 fix some deprecation warnings (#3610) 2020-02-28 02:21:58 +01:00
Michael H
d4e982faea Fix Red specific behavior of invoke_without_command (#3614) 2020-02-28 01:40:28 +01:00
github-actions[bot]
4db77c9051 Automated Crowdin downstream (#3616) 2020-02-28 01:02:44 +01:00
Dav
0397401216 [Docs] randomize_color now has its own docstring (#3491)
* randomize_color now has it's own docstring and definition

* hi CI

* reverse CI trigger

* reversed work on embed.py

* Changed docs file
2020-02-22 22:37:25 +01:00
Draper
6c062ab1e2 [Core] Update licenseinfo command to conform with non-American English. (#3460)
* Update the licence command to confirm with non-American English and keep it inline with the `colour`/`color` command

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* What is Black

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* improve error handling

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-02-22 20:52:01 +01:00
Dav
106804ad45 [Docs] Make Getting started guide explain use of quotes for arguments with spaces (#3555)
* getting_started.rst now explains use of quotes for arguments with spaces

* double quotes

* proposal 1

* proposal 2

* fix typo

Co-Authored-By: Flame442 <34169552+Flame442@users.noreply.github.com>

Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com>
2020-02-22 00:12:08 +01:00
jack1142
4745f9e999 [Trivia] Remove empty string from answers (#3581)
* [Trivia] Remove empty string from answers

* another one

* nobody minds that I put this change too, right?
2020-02-20 22:27:22 -05:00
jack1142
901c4f7b80 Update core_commands.py (#3585) 2020-02-20 22:25:36 -05:00
jack1142
5ee73cdf63 Add clearer error when pages isn't made of embeds nor strings in menu() (#3571)
* Update menus.py

* Update menus.py
2020-02-20 22:25:14 -05:00
jack1142
54b712fa71 Fix type hints for var-positional parameters in whitelist/blacklist commands (#3577)
* Fix type hints in whitelist/blacklist commands

* fix formatting (yay for black diff in CI again)
2020-02-20 22:23:45 -05:00
zephyrkul
e70fcef651 [Core] Utilize clean prefix (#3579)
* [core] use clean prefix

* forgot an import
2020-02-20 18:58:41 +01:00
github-actions[bot]
6779a76b68 Automated Crowdin downstream (#3580) 2020-02-20 13:21:37 +01:00
jack1142
cd48e06060 Update layout.html (#3575) 2020-02-19 03:24:28 -05:00
jack1142
4956e67348 [Mod] Delegate send_to_owners call in initialize() to a task (#3573)
* fix(mod): delegate send_to_owners call in initialize() to a task

* enhance(mod): reorder cog's setup()
2020-02-19 02:12:50 -05:00
jack1142
9a8c134c97 docs: add a warning on latest version of docs (#3570) 2020-02-18 21:53:07 -05:00
Michael H
ef101ccb9a [Docs] Cog Creator Guidelines (#3568)
* Cog Creator Guidelines

* print

* docs warnings

* Apply suggestions from code review

Co-Authored-By: Flame442 <34169552+Flame442@users.noreply.github.com>

* more feedback handling

* one more

Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com>
2020-02-17 22:42:13 -05:00
jack1142
bb90a50ae4 i18n fixes for #3562 (#3565) 2020-02-17 12:11:42 -05:00
Dav
6c62817de5 [Core] Setting the bot's game/listening/watching displays new text in chat message and is capped at 128 chars. (#3562)
* Le code.

* Say Status instead of Game/Listening/Watching
2020-02-17 11:48:43 -05:00
jack1142
7c06d9a638 Update install_windows.rst (#3564) 2020-02-17 11:47:09 -05:00
Fixator10
ae7773cfcf [Mod] fix exception caused by typo (#3559) 2020-02-17 11:46:14 -05:00
Dav
d7b73eb50f [Warnings] Fix inconsistency for warnreason (#3561) 2020-02-17 16:55:28 +01:00
jack1142
eff1014911 Add logging for unexpected OSError in [p]repo add (#3558)
* Update downloader.py

* Update downloader.py

Co-authored-by: Draper <27962761+Drapersniper@users.noreply.github.com>
2020-02-17 11:57:04 +00:00
Michael H
f6c85cd37a [CI] Less weekly churn from automated tasks (#3548)
* Less weekly churn pls

* be smarter here *just in case*
2020-02-16 22:53:02 +01:00
palmtree5
2c996307f5 Merge remote-tracking branch 'release/V3/develop' into V3/develop 2020-02-15 11:27:21 -09:00
Michael H
888c47cdd2 Payday: Fix missing await (#3554) 2020-02-15 14:47:33 +01:00
TrustyJAID
74a3eba08f [Mod] Move ignored guilds and channels to core (#3472)
* Move ignored guilds and channels to core
Add caching for ignored guilds and channels
Add caching for whitelist and blacklist
Fix #3220
Add consume-rest for whitelist and blacklist commands to add multiple users or roles in one command

* Add ability to ignore channel categories

* black

* moveignorechannels should be owner only and cleanup changes

* add changelog entries

* address Feedback
2020-02-15 01:21:09 -05:00
jack1142
78192dc1af [Downloader] Add schema validation to info.json file processing (#3533)
* schema v1

* set hidden to True for shared libs

* fix test data

* add warning about invalid top-level structure

* don't show full traceback for JSONDecodeError
2020-02-15 00:18:47 -05:00
PredaaA
ed6d012e6c [Streams] Use new Twitch API and Bearer tokens (#3487)
* Update streams.py

* Update streams

* Changelog.

* Adress Trusty's review

Co-authored-by: Michael H <michael@michaelhall.tech>
2020-02-15 00:14:34 -05:00
Draper
04b5a5f9ac [Streams] Significantly reduce number the quota usage for YouTube Data api (#3237)
* chore

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

* Pre-tests

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

* *sigh*

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

* Streams + black formatting

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

* *sigh* lets not spam the logs shall we

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

* lets be extra sure

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

* chore

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

* [Streams] Fix Twitch token for streamalert (#2)

* [Streams] Fix Twitch token for streamalert

* [Streams] Fix Twitch token for streamalert

Co-authored-by: PredaaA <46051820+PredaaA@users.noreply.github.com>
2020-02-15 00:06:03 -05:00
jack1142
a763726c89 Update alias.py (#3546) 2020-02-14 09:20:32 -05:00
jack1142
3991a2d88f [Docs] Restructure venv instructions (put them in install guides) (#3495)
* docs: restructure venv instructions

* docs: add a warning about PowerShell usage
2020-02-14 09:19:51 -05:00
jack1142
48e2dad3a4 Indicate instructions for different venv types in systemd guide better (#3538)
* Update autostart_systemd.rst

* Update autostart_systemd.rst
2020-02-14 09:16:33 -05:00
Dav
e7969992c3 [Warnings]Make it possible to add reason with unwarn (#3490)
* Unwarn now able to have a reason

* black

* Update string to say Run instead of Do

Co-Authored-By: Draper <27962761+Drapersniper@users.noreply.github.com>

* Make error for unregistered reason less agressive

Co-Authored-By: Draper <27962761+Drapersniper@users.noreply.github.com>

* Removing unneccessary lines and not putting the user input through the translator.

* Because black hates me, here black formatting with adjusted line length

* Trigger CI

* Now always sendes a message when an invalid reason is passed, not only if the command author was an admin, guild owner or bot owner.

* That should do the trick

* Correct

* Make Reason optional

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

Co-authored-by: Draper <27962761+Drapersniper@users.noreply.github.com>
Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-02-14 09:13:44 -05:00
Hugo Hedlund
6ddaff6260 Payday should store the last time it was used so it can be compared to the cooldown value rather than using a cooldown deco that won't reset if cooldown goes lower (#3496)
* Payday #3438 Changed next_payday to last_payday

* Created towncrier entry

* [PR #3496] Requested changes

* rm .vs
2020-02-14 09:13:18 -05:00
jack1142
9e1f358f82 Add make stylediff command and run it in tox (#3535)
* Update Makefile

* Update make.bat

* Update tox.ini
2020-02-14 09:09:52 -05:00
Michael H
066bf516d9 new mention behavior, new filter behavior (#3553)
* new mention behavior, new filter behavior

* and here too, ffs

* docs and reformat

* review handling
2020-02-14 09:07:16 -05:00
Michael H
a44047bfe3 We require a network connection, don't start before the network is ready (#3549) 2020-02-13 20:40:25 +01:00
github-actions[bot]
0913b68a1e Automated Crowdin downstream (#3543) 2020-02-13 12:54:28 -05:00
zephyrkul
42a23277cd [Dev] Allow top-level await in code statements (#3508)
* [dev] allow top-level await in code statements

* style

* use staticmethod, cls is unneeded

* add asyncio and aiohttp to env

* fix repl

* add __builtins__ to repl env

* style...

* fix debug with no coro

* add `optimize=0` to eval
2020-02-13 12:29:10 -05:00
jack1142
cc30726ab6 Skip publish actions for forks (#3544) 2020-02-13 17:43:42 +01:00
Michael H
c2143fdf86 [commands module] functools.partial support (#3542)
* No reason we can't support this

* meh

* there we go...
2020-02-12 09:28:52 -05:00
zephyrkul
da3f86d6ba Make systemd guide clearer on obtaining username and python path (#3537)
* [docs] make username clearer

* make systemd docs even more clear

* fix first code block
2020-02-11 19:11:36 +01:00
chasehult
26fdbf63ee string (#3536) 2020-02-10 04:10:59 -05:00
Michael H
7bee668888 [CI] Stop messing with our contributor data with automated PRs (#3534)
* CC: Kowlin

* *sigh*

* Their own docs said that was allowed in expressions...

* python linting needs it too

* Quit with the dumb

* whoops
2020-02-09 21:18:02 +01:00
Kowlin
7f6418b18f Fixed Commiter (#3532) 2020-02-09 00:53:37 +01:00
Cog-Creators Bot
4370ec922b Automated Crowdin downstream (#3531)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-02-08 19:49:30 +01:00
Kowlin
ec97f0c316 Updates to the Crowdin CLI (#3530) 2020-02-08 19:14:33 +01:00
jack1142
84870f2fa2 Add black config to pyproject.toml (#3493)
* chore: add black config to pyproject.toml

* enhance: make files will now use settings from pyproject.toml

* docs: update CONTRIBUTING.md
2020-02-08 07:47:39 -05:00
DiscordLiz
42a4d10ea5 Update websockets & Show DeprecationWarnings from all modules (#3527)
related : #3526
2020-02-08 07:45:45 -05:00
DiscordLiz
8e3a76186b [Mod] Use a better converter for Hackban & Unban (#3524)
closes #3523
2020-02-08 07:43:03 -05:00
DiscordLiz
9a278213bd Use development versions of CI tools (#3525)
Allows assignment expressions to not break PRs
2020-02-08 07:29:18 -05:00
Kowlin
c6bc4c1bd6 First attempt automated crowdin (#3521) 2020-02-07 22:44:40 -05:00
jack1142
17b8ef09c6 Update guide_publish_cogs.rst (#3520) 2020-02-08 02:15:10 +01:00
jack1142
8f7ba02ab1 [Warnings] Stop using inspect.getsource to check for is_owner check (#3516)
* Update helpers.py

* Create 3515.misc.rst

* Update helpers.py
2020-02-06 19:42:36 -05:00
Flame442
246f9ce17f Adds traceback logging to task exception handling (#3517) 2020-02-06 18:46:54 -05:00
trundleroo
8d73838d80 Update announcer.py (#3514)
* Update announcer.py

* Update announcer.py
2020-02-06 18:27:32 +01:00
Kowlin
1fc4ece14c Updated readme badges. (#3511) 2020-02-05 17:32:35 -05:00
Michael H
0adc960c60 dev bump (#3512) 2020-02-05 17:32:05 -05:00
Michael H
c426aefd1a Version 3.3.1 (#3510)
* 331

* okay sphinx
2020-02-05 23:21:38 +01:00
Michael H
00cf395483 Handle deprecations in asyncio (#3509)
* passing loop to certain things was deprecated. additionally, `asyncio.get_event_loop()` is being deprecated

* awesome, checks are functioning as intended

* fun with fixtures

* we can just stop misuing that anyhow

* Update redbot/pytest/downloader.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-02-05 17:16:13 -05:00
Kowlin
61ed864e02 CI ports from Travis CI (#3435)
* Attempt 1, I suppose.

* Add the remaining 2 out of 3 jobs

* Spacing matters T_T

* So does formatting...

* More formatting fixing.

* First attempt at postgres services.

* Postgres attempt 2

* Update tests.yml

Flatten a python version I suppose.

* Update tests.yml

* Update tests.yml

* Update tests.yml

* Update tests.yml

* I wonder if this works lmao

* this is fun™

* let's go back

* add fail-fast

* Added publishing workflows

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-02-05 16:02:05 -05:00
Lane Babuder
90b099395b Adding CentOS 8 Documentation (#3463)
IUS will not be supporting RHEL 8, so utilizing epel-release and telling the system to use standard git is the best option.
2020-02-03 16:57:09 -05:00
aikaterna
12e6f44135 [Core] No DMing the bot (#3478)
* [Core] No DMing the bot

* Return early if target user is a bot
2020-02-03 16:26:33 -05:00
PredaaA
e44fc69d14 [Core] Add a cli flag for setting a max size of message cache (#3474)
* Add an arg in cli to change message cache size

* Add an arg in cli to change message cache size

* Changelog

* Actually pass None in message_cache_size

* Update cli.py

* Add a cli arg to disable message cache.

* Add a cli arg to disable message cache.

* well go away you useless

* you actually are an int

* Check if message cache is higher than 0 when set it.

* Use sys.maxsize as max cache size.

* Update cli.py

* Add bot.max_messages property.

* typos

* 🤦

* style
2020-02-03 16:14:45 -05:00
jack1142
8454239a98 [Mod] Fix shorthelp for [p]modset dm (#3488)
* Update settings.py

* Update settings.py

* Create 3488.misc.rst

* Update settings.py
2020-02-03 16:14:19 -05:00
jack1142
64106c771a Allow to edit prefixes through redbot --edit (#3486)
* feat: allow to edit prefixes through `redbot --edit`

* enhance: allow to setup multiple prefixes

* fix: gotta break out of the loop

* fix: gotta sort prefixes in reversed order

* fix: editing prefix shouldn't save it as token

* fix: sort prefixes when using flag too

* chore(changelog): add towncrier entry

* docs: update help for `--edit` flag
2020-02-03 16:08:48 -05:00
jack1142
17234ac8fa Add -e flag to journalctl command in systemd guide so that it takes the user to the end of logs automatically. (#3483)
* Make journalctl's pager go to the end of logs automatically

* Aaaaaaaand changelog
2020-02-01 01:26:39 +01:00
Kowlin
b64802b92f Fix for the unknown days argument on hackban. (#3475) 2020-01-30 18:55:11 +01:00
jack1142
6fa02b1a8d [Docs] Trigger update on sudo add-apt-repository (#3464) 2020-01-27 18:41:57 -09:00
Michael H
7420df9598 let's fix this for dev testers (#3458) 2020-01-27 03:35:16 -05:00
Michael H
00bcd480e7 dev bump (#3455) 2020-01-26 20:39:38 -05:00
Michael H
0d3c72f356 changelog and bump (#3454) 2020-01-26 20:18:25 -05:00
Michael H
97a9fde5fd slowmode should properly error out on 7 hours now (#3453) 2020-01-27 02:01:22 +01:00
Michael H
a664615a2d shortdoc should be formatted too, + generic replacement method (#3451) 2020-01-27 01:25:58 +01:00
Michael H
3d4f9500e9 [Permissions] Ordering fix (#3452) 2020-01-27 01:00:08 +01:00
Michael H
a8450580e8 [Commands Module] Improve usability of type hints (#3410)
* [Commands Module] Better Typehint Support

  We now do a lot more with type hints

  - No more rexporting d.py commands submodules
  - New type aliases for GuildContext & DMContext
  - More things are typehinted

  Note: Some things are still not typed, others are still incorrectly
  typed, This is progress.

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-01-26 17:54:39 -05:00
Draper
8654924869 [Audio] Allow lazy searching for playlist across scopes (#3430)
* Allow lazy searching for playlist cross scope

* Chore
2020-01-26 16:38:49 -05:00
jack1142
068585379a docs: deprecation of shared libraries has been postponed to 3.4 (#3449) 2020-01-26 12:16:44 -05:00
jack1142
fc5fc08962 [Downloader] Log errors from initialization task (#3444)
* Update downloader.py

* Create 3444.misc.rst

* enhance(downloader): don't type infinitely on init error

* fix(downloader): unindent `_ready_raised` check

* Update downloader.py
2020-01-26 12:16:13 -05:00
Stonedestroyer
41fdcb2ae8 [Core] Embeds toggle for channels. (#3418)
* [Core] Embedset toggle for channels.

* Typo fix

* Add to contact as well

Thanks Jack.

* Add guild only and check.
2020-01-26 12:15:22 -05:00
Ianardo DiCaprio
de4804863a [Mod] Option to DM user with kick/ban reason. (#2990)
* FUCK

* FUCK

* FUCK

* Update kickban.py

* Update settings.py

* Update kickban.py

* Update kickban.py

* Add files via upload

* black

* Update kickban.py

* Update kickban.py

* Update redbot/cogs/mod/kickban.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/kickban.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update settings.py

* Update kickban.py

* Update and rename 2990.enhance.rst.txt.txt to 2990.enhance.rst.txt

* Update settings.py

* Rename 2990.enhance.rst.txt to 2990.enhance.rst

* Update redbot/cogs/mod/kickban.py

Co-Authored-By: DevilXD <DevilXD@users.noreply.github.com>

* Update redbot/cogs/mod/kickban.py

Co-Authored-By: DevilXD <DevilXD@users.noreply.github.com>

* Update redbot/cogs/mod/settings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/settings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update changelog.d/mod/2990.enhance.rst

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/settings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/settings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/kickban.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/kickban.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update kickban.py

* Update settings.py

* Update kickban.py

* Update kickban.py

* Update redbot/cogs/mod/kickban.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update kickban.py

* Update kickban.py

* Update mod.py

* Update settings.py

* Fix SyntaxError

* Don't pass "No reason was given." to modlog case

* Update settings.py

* Update 2990.enhance.rst

* black

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
Co-authored-by: DevilXD <DevilXD@users.noreply.github.com>
2020-01-26 04:18:13 +01:00
Michael H
2ac4dde729 update for d.py 1.3 (#3445)
* update for d.py 1.3

* Update redbot/core/commands/commands.py

Co-Authored-By: Danny <Rapptz@users.noreply.github.com>

* a few more places we use owner info

* add the cli flag + handling

* set fix

* Handle MaxConcurrencyReached.

* Bump `aiohttp-json-rpc`

Co-authored-by: Danny <Rapptz@users.noreply.github.com>
Co-authored-by: Kowlin <Kowlin@users.noreply.github.com>
Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-01-25 18:59:08 -05:00
Michael H
498d0d22fb resolves #3443 (#3447) 2020-01-25 18:57:07 -05:00
jack1142
2a38777379 [Downloader] Do the initialization in task to avoid timeout on bot startup (#3440)
* enhance(downloader): run init in task

* chore(changelog): add towncrier entry

* fix: address review
2020-01-24 12:38:42 -05:00
jack1142
01c1fdfd16 [Mod] Make [p]hackban use default days setting too. (#3437)
* Update kickban.py

* freaking whitespace
2020-01-24 10:30:32 +00:00
Draper
0a8e7f5663 stop dc interacting with repeat (#3426) 2020-01-23 17:05:50 -05:00
Ianardo DiCaprio
1755334124 [Mod] Default days in [p]ban command are now configurable (#2930)
* Initial Commit

* Added changelog

* Update redbot/cogs/mod/settings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/settings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/settings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Update redbot/cogs/mod/settings.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Rename 2930.enhance.rst.txt to 2930.enhance.rst

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-01-23 20:37:11 +01:00
zephyrkul
40c0d8d83b [systemd] fix which cmd for pyenv (#3434) 2020-01-22 23:20:35 -05:00
DevilXD
ee53d50c3a Help delete pages delay feature (#3433)
* Added 'deletedelay' feature for help

* Fixes

* More fixes

* Use better message when disabling

* Added changelog entry

* Addressed feedback

* Improved the pages check

* Added additional command check

* Improved command description

* Final feedback improvements
2020-01-22 17:15:51 -05:00
Stonedestroyer
8570971f68 [Core] Make bot name adjustable in bot. (#3429)
* First draft

* Up for discussion

* Revert "Up for discussion"

This reverts commit 2f00b7ded8.
2020-01-22 12:52:06 -05:00
Stonedestroyer
e1a110b1bf [Misc] Typo fixes (#3427)
* [Misc] Typo fixes

* Changelog

* Trivia list

* Update 3427.misc.rst

* Changelog
2020-01-22 10:00:52 +00:00
Michael H
77235f7750 [commands] Implement __call__ to commands.Command (#3241)
* This is technically awesome, but let's not document it for public use rn

* changelog
2020-01-20 23:23:15 +01:00
jack1142
c7fd64e0c8 [Downloader] Improve InstalledCog converter's error message (#3409)
* Update converters.py

* Create 3409.misc.rst
2020-01-20 17:09:55 -05:00
Flame442
8f04fd436f Catches discord.NotFound in utils.mod.mass_purge (#3414)
* Catches `discord.NotFound` in `mass_purge`

* Create 3378.bugfix.rst
2020-01-20 17:09:17 -05:00
Stonedestroyer
b085c1501f [General] Max amount to roll (#3395)
* [General] Rolls max amount

Adds max amount to roll.

* Removed redundant code.

* QA changes

* Add typehinting.
2020-01-20 16:49:46 -05:00
Stonedestroyer
7f390df879 [Customcom] Fix error on exiting customcom interactive menu. (#3417)
* [Customcom] Fixes error on exit

* Changelog

* Fixed spelling.

* Typehinting
2020-01-19 18:08:31 +01:00
Flame442
54e65082bc [Admin] Notify when the user has/doesn't have the role when att… (#3408)
* Update admin.py

* Create 3010.enhance.rst
2020-01-18 18:10:25 +00:00
Michael H
826dae129e dev bump (#3406) 2020-01-17 20:23:16 -05:00
jack1142
12da3bd89e Update changelog_3_2_0.rst (#3405) 2020-01-17 20:06:32 -05:00
Michael H
b089be7b49 fixup docs (#3404) 2020-01-17 19:57:27 -05:00
Michael H
33ea3a1419 version bump w/changelog (#3403) 2020-01-17 19:47:22 -05:00
Stonedestroyer
66cae71d90 [Docs] Changed python version references on docs (#3402)
* [Docs] Changes Python references to Python 3.8

* [Misc] Remove obsolete mention to Zenhub
2020-01-18 01:05:39 +01:00
Michael H
6219f0da67 [Modlog API] Add resolution for people inpacted by bad casetypes (#3333)
* add resolution for people inpacted by bad casetypes

* *some* amount of notice on this

* Fine.

* clearer warnings

* actually, unnneded
2020-01-17 18:51:49 -05:00
Michael H
7f2e5a0b70 [Docs] Remaining doc improvements for 3.2.3 (#3400)
* double the fun

* double

* pluralize this
2020-01-17 18:00:24 -05:00
Michael H
d52f8974fd Stop special casing help in bot.embed_requested (#3382)
- However, we are not changing the signature
  - This was previously special cased for reasons related to the older
  version of the help formatter we used and never re-evaluated for need.
  - We should leave the signature as is both for lack of breaking, and
  for potential future changes

// actually this was already done once in GH-2966 but got accidentally overwritten
2020-01-17 23:59:37 +01:00
Draper
2c12e4f6bf [Audio] Show symbolic link folders (#3376)
* Fixes Bump play

* Fixed #3332

* Revert "Fixed #3332"

This reverts commit d76d3acb

* Revert "Fixes Bump play"

This reverts commit 3839bdaf

* *sigh*

* *sigh*

* *sigh*

* use iglob + async iterator

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

* black

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

*  + fixes

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-17 17:07:49 -05:00
Michael H
b88bd5d44d More exit tweaks (#3392) 2020-01-17 23:07:34 +01:00
Draper
41b283ce5d [Audio] Show correct time remaining for bumped tracks (#3375)
* Fixes Bump play

* *sigh*
2020-01-17 17:00:29 -05:00
Michael H
cd7f4681a4 Cache prefixes (#3150)
* Cache prefixes

 - This works towards #3148
 - Ends up centralizing some logic
   - Including that prefixes should be a reverse sorted list

* handle global prefix attempts at none

* fix prefix set for server

* cache using guild id
2020-01-17 16:49:25 -05:00
jack1142
d1b7f836db Update auto_labeler.yml (#3396) 2020-01-17 22:45:41 +01:00
Draper
3d1e6eab00 [Audio] Add backticks to commands in docstrings, fix GH-3140 (#3374)
* docstring change

* remove backticks

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

* Seems like i cant read

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

* Rename 3140.enchance.1.rst to 3140.enhance.1.rst
2020-01-17 22:30:29 +01:00
Stonedestroyer
48ccd9070c [Core] Adds server IDs to servers command. (#3393)
* [Core] Adds server ID to servers command.

* Changelog
2020-01-17 06:08:31 -05:00
jack1142
67fbcb1b4a enhance(downloader): pagify any output that might be too long (#3388) 2020-01-17 04:44:10 -05:00
Stonedestroyer
a203fe34cf [Typo Fix] Permissions (#3390)
* [Typo Fix] Permissions

* Changelog file
2020-01-17 04:43:37 -05:00
Michael H
85438e7454 [Setup] Fix data deletion. (#3384)
* I'm ready to 🔪 some of these entrypoints

* If we're gonna teardown here, may as well do it right
2020-01-17 01:09:09 +01:00
Redjumpman
d6d14617d2 Update __init__.py (#3381)
Removed redundant check.
2020-01-16 13:18:20 -05:00
jack1142
a1b95e5072 enhance(downloader): log git commands that failed (#3372) 2020-01-15 20:54:23 -05:00
jack1142
29feab638a Update install_linux_mac.rst (#3371) 2020-01-15 20:45:35 -05:00
Michael H
60dc54b081 Allow pre_invoke to be used by 3rd party cogs safely. (#3369)
* Okay, so there's a lot in this diff

* fix docstrings

* meh

* fix misleading var name

* meh...

* useful typehints

* Apply suggestions from code review

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* dep warn in locations suitable

* Fix this...

* Apply suggestions from code review

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-01-15 20:44:21 -05:00
flaree
27e6f677e8 [Docs] Modlog Example: action -> action_type (#3368)
* action -> action_type

* Changelog.
2020-01-14 22:15:55 -05:00
jack1142
d8199201a5 [Changelog] Clarify breaking change related to setup() function (#3367)
* Update changelog_3_2_0.rst

* Update changelog_3_2_0.rst

* Update changelog_3_2_0.rst
2020-01-14 22:14:20 -05:00
Michael H
a7f0e2b7c6 Globally ensure send_messages for commands (#3361)
* wew

* typo fix, thanks Danny
2020-01-14 17:42:40 -05:00
jack1142
79dcd22ff6 Update bank.py (#3366) 2020-01-14 12:53:28 -05:00
jack1142
2be4080bc6 stop messing with distutils's internal just to copy directory (#3364) 2020-01-14 12:52:18 -05:00
jack1142
b646c2fd98 [Docs] Add links to operating systems + minor readability improvements (#3365)
* add operating systems links + some minor readability improvements

* meh, let's add this too, draper
2020-01-14 11:54:44 -05:00
Dav
90c0f76ae4 [Warnings] Make [p]warnings usable on base of permissions (#3327)
* new code

Added the admin check to warnings and removed the part where the user could check themselves.

Added new mywarns which replaces part of the old behaviour of warn

* Update warnings.py

* Create 2900.enhance.rst

* Fixed command name

Because appearently I can't remember a command for 10 seconds

* Commands in backticks

Put command names in changelog in double backticks after being advised to do so in discord

* made user not optional, and the other thing sinbad requested

* switched parts. magic

resolves #2900
2020-01-13 17:57:39 -05:00
Michael H
3c53b89040 [Help] formatting additions (#3339)
* formatting additions

* I really need to redo this module later

* fix some casing
2020-01-13 11:50:45 -05:00
Michael H
a7987a83fd Exit code handling (#3360)
* Exit code handling

* clear up a docstring
2020-01-13 11:37:49 -05:00
Michael H
ef8b57a1d2 Add a command to set the bot description (#3340)
* description-command

* Cap the description length

* mmk
2020-01-13 10:12:31 -05:00
Michael H
ab2e87a8fb Start making use of typehints for devs (#3335)
* Start making use of typehints for devs

* changelog
2020-01-13 09:46:05 -05:00
jack1142
088360ec51 Make Red shutdown when resetting token (#3358)
* Update __main__.py

* Update __main__.py
2020-01-12 20:26:01 -05:00
Draper
7bdd177713 [3.2.3][Audio] Correct an unsupported LoadType (#3337)
* Limit Playlists

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

* logging improvements

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

* logging improvements

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

* sigh

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

* chore

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-12 18:35:49 -05:00
Draper
81b6d5bb93 why the hell is this here (#3357)
Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-12 18:35:23 -05:00
jack1142
5eb4bda600 Update install_linux_mac.rst (#3336) 2020-01-12 18:25:01 -05:00
jack1142
cb49c5d420 [Downloader] Improve partial-uninstall message in [p]cog uninstall (#3343)
* Update downloader.py

* Let's use more of Flame's suggestions.

Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com>

Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com>
2020-01-12 18:21:00 -05:00
Draper
a984971774 [3.2.3][Audio] Fixes some Playlists strings (#3347)
* chore

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

* chore

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-12 18:20:31 -05:00
Draper
9f027cc3e0 [3.2.3][Audio] Improved Playlist cooldowns (#3342)
* Improved Playlist cooldowns

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

* Improved Playlist cooldowns

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

* formatting

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-12 18:19:36 -05:00
Flame442
fe7770c833 [Admin] Fixes hierarchy issues in [p]selfrole and [p]selfroleset (#3331)
* More fixes

* Update admin.py
2020-01-12 18:01:45 -05:00
Draper
8514dbe96a [3.2.3][Docs]Only support venv and virtualenv users (#3351)
* Limit Playlists

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

* docs

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

* jack

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

* update pip

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

* flame's review

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-12 17:42:59 -05:00
Draper
ed76454ddb ... (#3350)
Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-12 17:42:17 -05:00
jack1142
54711b2054 [Docs] Update autostart guides to use -O flag (#3354)
* Update autostart_systemd.rst

* Update autostart_pm2.rst
2020-01-12 17:41:29 -05:00
Draper
fdfbfe7b60 [3.2.3][Audio] Full fix for #3328 (#3355)
* Limit Playlists

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

* Hotfix

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

* Hotfix

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

* [Audio] Hotfix an edge case where an attribute error can be raised (#3328)

* Limit Playlists

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

* Hotfix

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

* Hotfix

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

* flame's review

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

* Delete 3328.hotfix.1.rst

* lets be extra safe here

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-12 17:37:50 -05:00
Draper
d6936c87f3 chore (#3348)
Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-12 17:37:04 -05:00
Michael H
a105217e83 Merge pull request #3356 from mikeshardmind/temp-branch
Merge an audio dix into dev
2020-01-12 17:28:31 -05:00
Draper
e52c20b9e7 [Audio] Hotfix an edge case where an attribute error can be raised (#3328)
* Limit Playlists

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

* Hotfix

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

* Hotfix

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-12 16:20:46 -05:00
jack1142
4754160055 [Docs] Update Debian/Raspbian instructions (#3352)
* Update install_linux_mac.rst

* Update install_linux_mac.rst

* Update install_linux_mac.rst
2020-01-12 16:08:34 -05:00
jack1142
d2b9504c3b [Docs] Add "Fork me on GitHub" ribbon (#3306)
* docs: add "Fork me on GitHub" ribbon

* chore(changelog): add towncrier entry
2020-01-12 11:13:58 -05:00
Michael H
b0b76c5a00 *exasperated sighs* (#3326) 2020-01-10 06:46:18 -05:00
Michael H
deab24e916 3.2.2 (#3324)
* page sizing changes

* docs
2020-01-10 06:43:35 -05:00
Michael H
2bb9b87db9 dev bump (#3322) 2020-01-10 00:14:06 -05:00
Michael H
5bd044d646 3.2.1 Hotfix (#3321) 2020-01-10 00:10:59 -05:00
Michael H
371292e03a prevent an empty iterable issue (#3320) 2020-01-10 00:04:12 -05:00
Michael H
acc5baec7d possible mongo fix (#3319)
* possible mongo fix

* prevent swallowing the exception

* better log str
2020-01-09 23:56:05 -05:00
Michael H
ed692bcaa5 This shouldn't be possible normally, but we've have enough issues with it (#3318) 2020-01-09 19:20:34 -09:00
Michael H
7352f76b87 mark dev (#3315) 2020-01-09 22:36:45 -05:00
Michael H
ad505b2b2f Version 3.2.0 (#3314)
* Thanks Caleb!

* version bump
2020-01-09 18:24:04 -09:00
Flame442
6ce421363d Grammar and content fixes for the 3.2 changelog (#3313)
* yall need better grammar...

* Update changelog_3_2_0.rst

* Delete 2940.enhancement.1.rts

* Delete 2940.enhancement.2.rst

* Delete 3058.enhancement.rst

* Delete 2924.fix.rst

* Delete 2945.fix.rst

* Delete 3060.fix.rst

* Delete 3069.fix.rst
2020-01-09 22:14:38 -05:00
Michael H
248259b312 Revert "remove the ugly dep handling (#3311)" (#3312)
This reverts commit f010df7082.
2020-01-09 20:48:23 -05:00
Michael H
f010df7082 remove the ugly dep handling (#3311) 2020-01-09 20:11:30 -05:00
Michael H
69e2ebf2e7 [docs] Proofreading based updates to release notes (#3310)
* updates thanks to proofreading by draper

* more
2020-01-09 18:19:31 -05:00
Michael H
c852505a62 Release notes (#3309) 2020-01-09 17:46:35 -05:00
Michael H
d3e8d99bdf [3.2.0] Changelog (#3303)
* changelog

* refs
2020-01-09 12:59:55 -05:00
jack1142
a9d3e271b0 [Docs] Update Linux install guides for python 3.8.1 (#3302)
* Update install_linux_mac.rst

* Update install_linux_mac.rst
2020-01-09 12:43:05 -05:00
Michael H
139119e954 kill the changelog check, we'll GH Action it (#3301) 2020-01-09 12:20:51 -05:00
Michael H
09a3a87cef update black target version (#3300) 2020-01-09 11:35:19 -05:00
jack1142
83e93916e8 [CI Docs] Allow linkcheck to retry before declaring link broken (#3276)
* Update conf.py

* Create 3276.misc.rst
2020-01-09 17:13:49 +01:00
Draper
3546dd14d0 change executor to 1 (#3299)
* Limit Playlists

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

* 1

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-09 11:13:32 -05:00
Draper
e75b5b3be5 Limit Playlists (#3298)
Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-09 11:10:57 -05:00
Michael H
ed3b4e5b29 Embed pagination fixing (pt2) (#3248)
* I hate embeds

* changelog

* until splitting the fields, ensure a field

* make this work, from a user perspective
2020-01-09 16:50:04 +01:00
Michael H
9698baf6e7 [3.2.0 Docs] Some clarifications (#3292)
* docs

*  changelog

* Fix python for choco

* k

* little more
2020-01-09 16:16:10 +01:00
Michael H
26677004f1 Handle regression in redbot edit (#3297)
* fixes #3296

* changelog

* k

* @Kowlin

* *sigh*
2020-01-09 16:14:38 +01:00
Michael H
25f0c37a20 jar-bump (#3291) 2020-01-09 09:38:34 -05:00
jack1142
ab3b567cd8 [Core] Use owner set in config (#3294)
* Update bot.py

* Create 3293.misc.rst

* style: forking whitespace
2020-01-09 08:52:34 -05:00
Michael H
a0f548fc0b minimize the launcher (#3289)
* minimize the launcher

* changelog
2020-01-09 08:51:17 -05:00
Kowlin
e3720bb4a6 Updated to GH Scripts (#3295) 2020-01-09 14:09:41 +01:00
Michael H
b35b8d98c3 bump red lavalink (#3290) 2020-01-08 20:05:25 -05:00
Michael H
2612453597 [3.2.0] dep update (#3288)
* dep update

* changelog + 1 which was previously missed
2020-01-08 18:35:01 -05:00
Flame442
42d83e80a3 [Trivia] Fix various things changed by game updates (#3236)
* Fix various things changed by game updates

* oops...

* Create 3236.bugfix.rst
2020-01-08 18:20:59 -05:00
Draper
44e680ee41 [3.2]Audio] some hotfixes to avoid crashing the bot (#3286)
* Add a command to toggle daily queues, and  restrict playlist length to 10k tracks and try to avoid some blocking calls

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

* Add a command to toggle daily queues, and  restrict playlist length to 10k tracks and try to avoid some blocking calls

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

* indents

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

* *sigh* forgot single tracks

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

* formatting plus some other fixes

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

* formatting plus some other fixes

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-08 17:04:52 -05:00
Michael H
965416de73 [Docs] Ensure users have setuptools and wheel (#3262)
* Ensure users have setuptools and wheel

* changelog
2020-01-08 19:44:33 +01:00
Michael H
1c75c47a9c Allow migrating away from mongo (#3253)
* Restore mongo driver

* make it possible to convert

* style

* add in known issues with other backends while at it
2020-01-08 13:41:35 -05:00
Michael H
35c27c5741 Be quieter in expected shutdown cases (#3261)
* Be quieter in expected cases

* lets put this in the log file

* inline description use because setuptools entrypoint scripts are dumb

* Another setuptools entrypoint related issue

* maybe don't crash the bot on tasks

* improve the handling a bit more + document some of the lower level bits from the perspective of 'why?'

* Adding myself to codeowners on this one

* Let's not clobber our exit code

* And, there we go

* finish that thought

* right, I bumped the python version for (part of) this

* Update redbot/__main__.py

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* Okay, we should be good now

* correct exit code enum use

* cosmetic

* minor fix for linux and ctrl+c

Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com>
2020-01-08 19:39:52 +01:00
jack1142
af859aa755 [Downloader] Move author key handling to RepoJSONMixin, fix NameError (#3285)
* Update downloader.py

* Update json_mixins.py

* Update installable.py

* changelog pt 1

* changelog pt2

* edit of changelog pt1

* edit of changelog pt 2 (last commit before review)

* Kidding, this is the last one before review.
2020-01-08 13:08:55 -05:00
Kowlin
f5949f2664 Cleaning up the Flake8 workflow (#3283) 2020-01-08 12:24:27 -05:00
Kowlin
96e9e55642 Added an auto labeler. (#3282) 2020-01-08 12:21:42 -05:00
Michael H
778c701b87 May as well handle that part... (#3281) 2020-01-07 17:47:54 -05:00
Michael H
a73b174d9f update translation catalogs (#3280) 2020-01-07 17:16:21 -05:00
Draper
17123c1d88 [3.2][Audio] Add a limiter of 500 to the Visible queue (#3279)
* Add a limited of 500 to the Visible queue and somce asyncio sleep every n tracks to assist with queue loading so it stops being blocking

* add a text for queue

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

* chore

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

* add asyncio sleep on playlist queue command

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

* Better error handling for large playlists

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

* remove files even if it errors

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2020-01-07 16:33:10 -05:00
zephyrkul
45860ca2a6 [CustomCom] Use humanize_list for iterable arguments (#3277)
* [cc] use humanize_list on lists

* [cc] need classmethod

* add changelog
2020-01-06 16:49:08 -05:00
jack1142
d3c97eedfe [Downloader] Fix AttributeError in [p]findcog for cogs that weren't installed through Downloader (#3278)
* Update downloader.py

* Create 3278.misc.rst
2020-01-06 16:48:43 -05:00
jack1142
a2b68ea7fc [Docs] Update Python version for docs (#3258)
* Update .readthedocs.yml

* Create 3258.misc.rst
2020-01-06 07:13:55 -05:00
Draper
62aad10008 [3.2][Audio] Database migration fix (#3275)
* Missed this due to the mess of a fork i had

* chore
2020-01-06 05:28:56 -05:00
jack1142
474bb0904e [Core, Downloader] Clear lib folder on minor Python version change, add [p]cog reinstallreqs command (#3274)
* feat(downloader): add `[p]cog reinstallreqs` command

* enhance: clear lib folder on minor Python version change

* chore(changelog): add towncrier entries

* enhance: warn user about detected change in OS or arch

* enhance: use actual prefix instead of `[p]`

* Whoops...

Co-Authored-By: Michael H <michael@michaelhall.tech>

* enhance: wrap message sending in try except

Co-authored-by: Michael H <michael@michaelhall.tech>
2020-01-05 19:21:49 -05:00
jack1142
b0f840c273 [Core] Add Red.wait_until_red_ready() function to wait until post connection startup is done (#3273)
* enhance: add `Red.wait_until_red_ready()` for post connection startup

* enhance: fill `bot.owner_id` in our `on_ready`

* enhance: log missing destinations in `get_owner_notification_destinations`

* chore(changelog): add towncrier entries

* chore(changelog): use past form of verb "add"
2020-01-05 18:38:59 -05:00
Michael H
9ec78d1455 Fix ctx.clean_prefix for *new* discord behavior (#3249)
* I just love when discord changes important syntax without warning

  - sarcasm approaching dangerous levels

* changelog
2020-01-04 14:08:35 -05:00
jack1142
f2d2b9a682 [Setup] Stop logging to disk (#3269)
* Update setup.py

* Create 3269.enhance.rst

* Update 3269.enhance.rst
2020-01-04 02:10:19 -05:00
Michael H
23fe991c36 Update games.yaml (#3268) 2020-01-04 01:34:35 -05:00
Michael H
d9d2e0017e patchup name error from 3254 (#3267) 2020-01-04 00:39:48 -05:00
jack1142
b6ca8f7d2c [Core] Escape markdown in python executable path in `[p]debuginfo` command. (#3254)
* Update core_commands.py

* Create 3254.misc.rst
2020-01-03 22:44:33 -05:00
jack1142
cacfa163ce [Docs] Fix broken docs for commands.Context.react_quietly (#3257)
* Update context.py

* Create 3257.docs.rst
2020-01-03 22:43:00 -05:00
Michael H
d00609bb8a pyflakes (#3266) 2020-01-03 22:22:10 -05:00
Draper
b59f136ece [3.2.0][Audio] Daily playlist (#3199)
* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

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

* Initial Commit

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* I need to make sure I keep aika on her toes.

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Fixes a few missing kwargs and case consistency

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Fixes a few missing kwargs and case consistency v2 and typos

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Reset cooldowns + add changelogs

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Add 3 extra file formats.

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* IRDUMB - fix capitalization.

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Fix a silent error, and some incorrect messages.

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

* Remove unnecessary emojis from queue when they are not needed

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

* Remove duplicated call in `[p]playlist update`

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

* Remove duplicated call in `[p]playlist update`

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

* Resolve conflicts

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Bring all files up to date + Black

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Facepalm

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* *Sigh*

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* *Sigh* 2.0

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Merge branch 'V3/develop' of https://github.com/Cog-Creators/Red-DiscordBot into audio-misc-pt1

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

# Resolve Conflicts:
#	redbot/cogs/audio/audio.py
#	redbot/cogs/audio/utils.py

* Import missing Typecheck

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

* Fix Broken docstrings

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

* Sort Local Tracks

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

* 🤦

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

* Reorder the sorting of local tracks,
`alphanumerical lower then alphanumerical upper`
`a comes before A, but B comes after A`

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

* Black formatting

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

* Make the local file sorting case insensitive

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

* Add global blacklist/whitelist + fix some issues with original server based whitelist/blacklist

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

* Remove the pre-commit yaml

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

* Nottin to see

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

* Further improvement to the blacklists

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

* Further improvement to the blacklists

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

* Fix  the __str__ method on LocalTracks Object

* Rename LocalTracks.to_string_hidden() to LocalTracks.to_string_user() To keep it inline with the Query object

* Remove encoding pragmas + a few typo fixes

* Update some typehints + fix some typos

* Remove this duplicate call

* Black

* fix capitalization

* Address preda's review

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

* Remove the API from the audio cog

 - Is in direct conflict with goals stated in #2804
 - Features this was intended to enable can be enabled in other more
 appropriate ways later on

* changelog

* Address Aika's review

* Black

* *sigh* dont use github web ui

* Fuck windows Long live linux... *sigh* no lets ensure windows users can still use local tracks

* Merge branch 'V3/develop' of https://github.com/Cog-Creators/Red-DiscordBot into refactoring

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

# Conflicts:
#	redbot/cogs/audio/audio.py

* 👀 + chore

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

* facepalm

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

* facepalm... again y u h8 me bruh

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

* fuk this fuk u tube fuck python fuck all

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

* awyehfqwajefhnqeffawefqa eqewarfqaesf qwef qaf qwfr

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

* fuck everything

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

* oh lord saviour resus i love you just make this work

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

* Change logic to be no errors within last 10 seconds... this should be a valid work around discord ratelimits caused by the spam

* Remove auto deletion

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

* See I did a ting

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

* irdumb

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

* black

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

* Add an is_url attribute to Query objects

* chore

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

* Black

* Address Aikas review

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

* Hyperlink Playlist names

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

* Make shit bold

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

* why was this here

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

* why was this here

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

* Initial commit

* Workinnng

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

* Improve SQL Statements +  migrate from SQL Alchemy + Databases to APSW

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

* apsw tested and working

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

* chose

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

* Migrate Playlist to DB 3 TODO
1 Migrate Config to Schema 3 without playlists
and update get_playlist methods

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

* Revert "Migrate Playlist to DB 3 TODO 1 Migrate Config to Schema 3 without playlists and update get_playlist methods"

This reverts commit 4af33cff

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

* Implement schema migration

* Lets not touch the deps since #3192 is already adding them

* chore

* *sigh* Black

* Follow the existing logic and always default Playlist to guild scope

* wghqjegqf black

* Update usage of last_fetched and last_updated to be Ints... However column migration still pending

* Some bug fixes

* Update usage of last_fetched and last_updated to be Ints... However column migration still pending

* working

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

* partial match

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

* better partial match

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

* black

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

* I thought i done this before

* Delete 3195.misc.1.rst

Wrong PR

* Thanks Sinbad

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

* Thanks Sinbad

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

* Log Errors  in init ...

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

* Update error logs.

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

* Create index

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

* Create index

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

* :Drapersweat:

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

* Chore

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

* Revert "Chore"

This reverts commit edcc9a9f

UGHHHH

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

* KMS

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

* Allow removing tracks from queue by URL

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

* Words matter

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

* *sigh*

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

* chore

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

* arghhh CONFLICTS

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

* Review sinbads latest comment ..

ToDo.. Nuke existing playlist - check version and set version

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

* migrate the DB schema to v3 (to keep in line with the schema visioning of Config

* Add a Todo

* *sigh* conflicts and black

* *sigh* black

* Passively delete playlist deletion mechanism

* Delete Old entries on startup

* Since we are dropping the table mightaware make these into JSON for future proofing

* Don't Dump strings in JSON field ? :think:

* Move some things around to make easier to use 1 connection to the Audio DB

* Move some things around to make easier to use 1 connection to the Audio DB

* *sigh*

* Clean up api

* *sigh* black

* Red + reorder some variables

* 🤦

* how could i forget this .......

* Black

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

* Black

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

* Black

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

* #automagically

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

* FINAFUCKINGLY

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

* FINAFUCKINGLY

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

* Remove unused config default

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

* Remove the API from the audio Cog (Properly)

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

* Missed these changes

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

* ARGHHH

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

* Crerrypick - Some fixes I've noticed while running through the code line by line

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

* Translation + UX (show playlist author ID if can't find user)

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

* *sigh* missed this one

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

* this is no longer needed ....

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

* Im a fucking idiot

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

* merger v3/develop

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

Co-authored-by: Michael H <michael@michaelhall.tech>
2020-01-03 21:58:11 -05:00
Michael H
25999cea10 Docs fix (2) (#3265)
* Fix docs building

* And 1
2020-01-03 21:48:28 -05:00
Draper
95e8d60729 [3.2][Audio] Part 6 (Last? maybe?) (#3244)
* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

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

* Initial Commit

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* I need to make sure I keep aika on her toes.

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Fixes a few missing kwargs and case consistency

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Fixes a few missing kwargs and case consistency v2 and typos

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Reset cooldowns + add changelogs

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Add 3 extra file formats.

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* IRDUMB - fix capitalization.

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Fix a silent error, and some incorrect messages.

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

* Remove unnecessary emojis from queue when they are not needed

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

* Remove duplicated call in `[p]playlist update`

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

* Remove duplicated call in `[p]playlist update`

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

* Resolve conflicts

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Bring all files up to date + Black

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Facepalm

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* *Sigh*

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* *Sigh* 2.0

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Merge branch 'V3/develop' of https://github.com/Cog-Creators/Red-DiscordBot into audio-misc-pt1

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

# Resolve Conflicts:
#	redbot/cogs/audio/audio.py
#	redbot/cogs/audio/utils.py

* Import missing Typecheck

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

* Fix Broken docstrings

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

* Sort Local Tracks

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

* 🤦

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

* Reorder the sorting of local tracks,
`alphanumerical lower then alphanumerical upper`
`a comes before A, but B comes after A`

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

* Black formatting

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

* Make the local file sorting case insensitive

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

* Add global blacklist/whitelist + fix some issues with original server based whitelist/blacklist

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

* Remove the pre-commit yaml

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

* Nottin to see

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

* Further improvement to the blacklists

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

* Further improvement to the blacklists

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

* Fix  the __str__ method on LocalTracks Object

* Rename LocalTracks.to_string_hidden() to LocalTracks.to_string_user() To keep it inline with the Query object

* Remove encoding pragmas + a few typo fixes

* Update some typehints + fix some typos

* Remove this duplicate call

* Black

* fix capitalization

* Address preda's review

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

* Remove the API from the audio cog

 - Is in direct conflict with goals stated in #2804
 - Features this was intended to enable can be enabled in other more
 appropriate ways later on

* changelog

* Address Aika's review

* Black

* *sigh* dont use github web ui

* Fuck windows Long live linux... *sigh* no lets ensure windows users can still use local tracks

* Merge branch 'V3/develop' of https://github.com/Cog-Creators/Red-DiscordBot into refactoring

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

# Conflicts:
#	redbot/cogs/audio/audio.py

* 👀 + chore

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

* facepalm

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

* facepalm... again y u h8 me bruh

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

* fuk this fuk u tube fuck python fuck all

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

* awyehfqwajefhnqeffawefqa eqewarfqaesf qwef qaf qwfr

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

* fuck everything

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

* oh lord saviour resus i love you just make this work

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

* Change logic to be no errors within last 10 seconds... this should be a valid work around discord ratelimits caused by the spam

* Remove auto deletion

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

* See I did a ting

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

* irdumb

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

* black

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

* Add an is_url attribute to Query objects

* chore

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

* Black

* Address Aikas review

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

* Hyperlink Playlist names

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

* Make shit bold

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

* why was this here

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

* why was this here

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

* Initial commit

* Workinnng

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

* Improve SQL Statements +  migrate from SQL Alchemy + Databases to APSW

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

* apsw tested and working

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

* chose

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

* Migrate Playlist to DB 3 TODO
1 Migrate Config to Schema 3 without playlists
and update get_playlist methods

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

* Revert "Migrate Playlist to DB 3 TODO 1 Migrate Config to Schema 3 without playlists and update get_playlist methods"

This reverts commit 4af33cff

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

* Implement schema migration

* Lets not touch the deps since #3192 is already adding them

* chore

* *sigh* Black

* Follow the existing logic and always default Playlist to guild scope

* wghqjegqf black

* Update usage of last_fetched and last_updated to be Ints... However column migration still pending

* Some bug fixes

* Update usage of last_fetched and last_updated to be Ints... However column migration still pending

* working

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

* partial match

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

* better partial match

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

* black

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

* I thought i done this before

* Delete 3195.misc.1.rst

Wrong PR

* Thanks Sinbad

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

* Thanks Sinbad

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

* Log Errors  in init ...

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

* Update error logs.

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

* Create index

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

* :Drapersweat:

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

* Chore

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

* Revert "Chore"

This reverts commit edcc9a9f

UGHHHH

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

* Allow removing tracks from queue by URL

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

* Words matter

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

* *sigh*

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

* chore

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

* arghhh CONFLICTS

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

* Review sinbads latest comment ..

ToDo.. Nuke existing playlist - check version and set version

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

* migrate the DB schema to v3 (to keep in line with the schema visioning of Config

* Add a Todo

* *sigh* conflicts and black

* *sigh* black

* Passively delete playlist deletion mechanism

* Delete Old entries on startup

* Since we are dropping the table mightaware make these into JSON for future proofing

* Don't Dump strings in JSON field ? :think:

* Move some things around to make easier to use 1 connection to the Audio DB

* Move some things around to make easier to use 1 connection to the Audio DB

* *sigh*

* Clean up api

* *sigh* black

* Red + reorder some variables

* 🤦

* how could i forget this .......

* Black

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

* Black

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

* Black

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

* #automagically

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

* FINAFUCKINGLY

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

* FINAFUCKINGLY

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

* Remove unused config default

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

* Remove the API from the audio Cog (Properly)

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

* Missed these changes

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

* ARGHHH

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

* Some fixes I've noticed while running through the code line by line

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

* Translation + UX (show playlist author ID if can't find user)

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

* *sigh* missed this one

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

* this is no longer needed ....

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

* 🤦

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

* fix new lines in error messages

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

* Black

* Sinbads Review

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

* Sinbads Review

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

* *sigh* copy paste

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

* imrpove backups

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

* Im a fucking idiot

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

* Fix #3238

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

* chore

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

* humans

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

* humans

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

* add play alias to playlists

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

* Im dumb ...

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

* Im dumb ...

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

* fix new line

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

* fix new line

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

* show playlist count on playlist picker

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

* DJ/Vote system fixes

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

* DJ/Vote system fixes

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

* *sigh* fix currency check

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

* show playlist count on playlist picker

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

* DJ/Vote system fixes

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

* DJ/Vote system fixes

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

* *sigh* fix currency check

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

* Fix duplicate messages on timeout

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

* fix SQL Statement logic

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

* fix SQL Statement logic

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

* Markdown escape

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

* Markdown escape

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

* Markdown escape fix

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

* Markdown escape fix

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

* clean up local cache more frequently

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

* clean up db more frequently

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

* Await in hell

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

* *sigh* im dumb

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

* *sigh* im dumb

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

* Black cuz I hate red

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

* Black cuz I hate red

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

* StringIO to ByteIO

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

* StringIO to ByteIO

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

* *sigh* im dumb

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

* :Facepalm: the whole purpose of this is so its offline so this can be backed up without being blocking

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

* Run write queries on ThreadPoolExecutor

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

* Backup Audio.db

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

* *sigh* im dumb

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

* blaaaack

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

* *sigh*

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

* formatting

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

* remove duplicated string of code

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

* ffs awaits

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

Co-authored-by: Michael H <michael@michaelhall.tech>
2020-01-03 20:36:09 -05:00
Michael H
1d2dd19244 Fix docs building (#3263) 2020-01-03 19:13:14 -05:00
PredaaA
743ce71c5c [Streams] Fix TypeError in TwitchStream class and change stream_alert function for Twitch (#3042)
* Update streamtypes.py

* Create 3042.bugfix.rst

* Black the things.

* Fix Twitch streams alert and [p]streamalert twitch

* Update 3042.bugfix.rst
2020-01-02 19:28:57 -05:00
jack1142
42e3f73088 [Core] Fix missing `await in code of redbot --edit`. (#3256)
* Update __main__.py

* Create 3256.misc.rst
2020-01-02 18:53:40 -05:00
Kowlin
ec6877dbc6 Update the licence info to 2020 (#3259)
* We're somewhat on time for once!

* Helps to update RTD

* Added changelog
2020-01-02 18:37:26 -05:00
Jonas Bohmann
c8f753db0d Fix typo '.foramt()' -> '.format()' in cogs/admin/admin.py (#3255)
* Fix typo '.foramt()' -> '.format()'

* Add changelog file
2020-01-02 12:48:13 -05:00
Flame442
e776b5ca1a [Admin] Code prettification and bugfixing (#3250)
* Facelift for Admin

* Remove unnecessary converter, reorder existing steps

* Delete admin.py

* Delete __init__.py

* Delete test_admin.py

* Remove one extra unneeded check

* Create 3250.bugfix.1.rst

* Create 3250.bugfix.2.rst

* Create 3250.bugfix.3.rst

* Create 3250.bugfix.4.rst

* Create 3250.misc.1.rst

* Create 3250.misc.2.rst

* Create 3250.misc.3.rst

* Create 3250.breaking.1.rst

* Create 3250.breaking.2.rst

* ...

* I hate black...
2020-01-02 09:11:27 -05:00
jack1142
36e2cde04d Move [p]backup command to cli command - redbot-setup backup (#3235)
* refactor: replace backup command with cli command

* chore(changelog): add towncrier entries
2020-01-02 09:03:32 -05:00
jack1142
f3e7c2028c [Setup] Use instance name in default data path (#3171)
* enhance(setup): use instance name in default data path

* chore(changelog): add towncrier entries

* enhance(setup): tell user that instance name is case-sensitive
2020-01-02 08:59:22 -05:00
jack1142
f3c57b6730 [Docs] Fix driver docs showing twice (#3035)
* docs(config): fix doubled docs for drivers

* enhance(drivers): add docstrings to enums that show in docs

* chore(changelog): add towncrier entries
2020-01-02 08:54:25 -05:00
jack1142
bc90f5186a [Downloader] Actually use disabled key in updates (#3203)
* fix(downloader): actually use disabled key in updates

* chore(changelog): add towncrier entry
2020-01-02 08:49:31 -05:00
jack1142
62b679b1b9 Replace links to v3-develop docs with links to stable docs (#3186)
* Update customcom.py

* Update permissions.py

* Create 3186.docs.rst

* Create 3186.docs.rst

* Rename 3186.docs.rst to 3186.misc.rst

* Rename 3186.docs.rst to 3186.misc.rst
2020-01-02 08:48:33 -05:00
jack1142
ab747d2432 [Utils] Privatize internal utils (#3240)
* refactor(utils): privatize some utils

* chore(changelog): add towncrier entry

* refactor: update internal utils imports
2020-01-02 08:44:55 -05:00
jack1142
debed501b2 [Docs] Remove API Reference for downloader, add page about publishing cogs (#3234)
* docs: add info about publishing cogs, remove downloader reference

Co-authored-by: Redjumpman <Redjumpman@users.noreply.github.com>

* chore(changelog): add towncrier entries

* docs: fix broken reference in 3.1.0 changelog

Co-authored-by: Redjumpman <Redjumpman@users.noreply.github.com>
2020-01-02 08:28:50 -05:00
Michael H
a80e20067c do better with loop cleanup (#3245)
* do better with loop cleanup

* changelog

* remove redundant line

* Do this a bit better than the initial pass

* Improve windows support

Make some other things coroutines to work with improved design

* Wish we'd have done this right from the start...

* Update deps surrounding this

 - see bpo-23057
 - neccessary for windows users
 - nice for consistent support channel info / feature availability

* dep issue

* Fix tests

* duplication plugin py version

* actually handle this

* Reconfigure some checks with codeclimate, disable pylint for now

* style

* Is my exasperation showing yet?

* handle some stupid stuff

* meh

* dep changelog
2020-01-01 19:26:32 -05:00
Michael H
22268eed9d Help newline... (#3247)
* whee

* change
2019-12-31 16:01:51 -05:00
Draper
f7e2617911 Fix an attribute error that can be raised in humanize_timedelta if seconds = 0 (#3231)
* Migrate Playlist to DB 3 TODO
1 Migrate Config to Schema 3 without playlists
and update get_playlist methods

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

* Revert "Migrate Playlist to DB 3 TODO 1 Migrate Config to Schema 3 without playlists and update get_playlist methods"

This reverts commit 4af33cff

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

* Allow removing tracks from queue by URL

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

* Words matter

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

* *sigh*

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

* chore

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

* Fix an attribute error that can be raised here is seconds = 0

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

* Fix an attribute error that can be raised here is seconds = 0

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

* go away

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

* *sigh*

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2019-12-29 09:37:50 -05:00
jack1142
f9211ff50f [Downloader] Fix UnboundLocalError in cog update that happened when cogs were already up-to-date. (#3230)
* Update downloader.py

* Create 3229.misc.rst
2019-12-27 18:53:38 -05:00
jack1142
aabdabf3bc [Core] Prevent users from locking out themselves or guild owner with localblacklist (#3221)
* Update core_commands.py

* Update core_commands.py

* Create 3207.bugfix.rst

* Update core_commands.py

* Create 3221.bugfix.rst

* Update redbot/core/core_commands.py

Co-Authored-By: Michael H <michael@michaelhall.tech>

* Update bot.py

* Rename 3221.bugfix.rst to 3221.bugfix.1.rst

* Create 3221.bugfix.2.rst

* Update bot.py

Co-authored-by: Michael H <michael@michaelhall.tech>
2019-12-27 17:33:22 -05:00
jack1142
de229f63fe [Downloader] Add more information to [p]repo info, [p]cog info and [p]findcog (#3225)
* Update downloader.py

* Update downloader.py

* Update downloader.py

* Create 3225.enhance.1.rst

* Create 3225.enhance.2.rst

* Create 3225.enhance.3.rst

* Update downloader.py

* Style fix, ready for review
2019-12-27 17:14:04 -05:00
jack1142
75c4bee8a3 [Core] Tell user that the (local) whitelist/blacklist is empty when using a list command. (#3219)
* Update core_commands.py

* Create 3219.bugfix.rst
2019-12-27 17:07:43 -05:00
zephyrkul
60a1b3294d [CustomCom] add Query typehint for URI-based CCs (#3228)
* [cc] add Query typehint for URI query ccs

* Create 3228.enhance.rst
2019-12-27 11:43:25 -05:00
Michael H
ef99174585 prevent abuse cases with qualname length (#3223) 2019-12-26 17:20:59 -05:00
Michael H
8b18526f46 [Help] Fix embed size calculation for additional text (#3208)
* Fix size calculation for additional text

* changelog

* ffs

* because of course that's a thing

* *sigh*

* prevent an edge case

* more

* ...

* ...
2019-12-26 16:53:03 -05:00
Michael H
12af6232e2 [Docs] Config best practices (#3189)
* config best practices, resolves #3149

* Update framework_config.rst

* update to config wording
2019-12-26 15:34:59 -05:00
Michael H
153f4d20f1 exta info in docs about context attrs (#3151)
* exta info in docs about context attrs

* changelog

* slight addition for clarity
2019-12-26 15:34:37 -05:00
jack1142
df5cfabfe5 [Core] Use [p] for command prefix in help docstring of [p]removepath (#3214)
* Update cog_manager.py

* Create 3214.misc.rst
2019-12-24 22:57:21 -05:00
Michael H
bf6297aaf2 Add a small wrapper for APSW use (#3202)
* Add a small wrapper for APSW use

* changelog
2019-12-22 13:18:31 -05:00
jack1142
b72c05d3d4 [Core] Print a link to the guide explaining how to obtain token when Red prompts for it (#3204)
* Update cli.py

* Create 3204.enhance.rst
2019-12-21 03:55:32 -05:00
Michael H
a36e95c286 Remove a large amount of fetch_user calls (#3075)
* Non-audio changes

* address Flame's feedback
2019-12-21 01:15:34 -05:00
jack1142
b457f8d1c1 [Core] Add deprecation warnings about removal of shared libraries. (#3106)
* feat: add deprecation warning when importing shared libs

* enhance(downloader): add shared libs deprecation warns

* enhance: add deprecation warning when (re)loading cogs

* docs(downloader): add deprecation note about shared libs

* chore(changelog): add towncrier entries

* style: split long tuple unpacks in multiple lines

* fix: argument to `humanize_list` has to be a sequence
2019-12-20 02:06:53 -05:00
jack1142
9d027747d1 [Backup] Fix generation of repos.json file in backup process (#3114)
* fix: generation of `repos.json` file in backup process

* chore(changelog): add towncrier entry
2019-12-20 02:03:46 -05:00
Draper
6bf9ff5637 [Audio] Fix console spam caused by disconnect_timer if a player is destroyed before the task completes (#3123)
* Remove servers from the auto disconnect/pause list is their players no longer exist...
Prevents a console spam

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

* Chore

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2019-12-20 01:59:09 -05:00
Draper
0b042532fd [Audio] Fix Attribute error raised by is_alone method when channel was None (#3122)
* Fix attribute Fixes #3120

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

* Chore

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2019-12-20 01:58:08 -05:00
Draper
61f467a323 Audio converters - Remove all da fetches (#3089)
* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

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

* Update Audio Scope converters to respect changes done in #3075
To be merged after #3075

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

* Change logs

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

* Fix Typo

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

* Fix an attribute error when the converter returned None

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

* 🤦

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

* 🤦 2x

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

* Address Aika's review

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2019-12-20 01:55:34 -05:00
Michael H
02bb1fc390 add apsw-wheels to deps (#3192) 2019-12-20 01:51:33 -05:00
palmtree5
9a82830f13 Merge remote-tracking branch 'release/V3/develop' into V3/develop 2019-12-14 23:28:31 -09:00
Michael H
e32eecd6e7 [Docs] Update windows deps instructions (#3188)
* update windows deps instructions

* changelog

* be more explicit that manual dependency handling is an excersice left to the reader

* let's only grab the MSVC C++ stuff here...

* Meh

* update language

* Update install_windows.rst

* it's really that easy
2019-12-14 21:00:08 -05:00
jack1142
988536f96b [Downloader] Possible solution for "partial" unload of cog in [p]cog uninstall (#3180)
* Update downloader.py

* Create 3180.bugfix.rst

* Rename 3180.bugfix.rst to 3179.bugfix.rst

* Update redbot/cogs/downloader/downloader.py

Co-Authored-By: Michael H <michael@michaelhall.tech>
2019-12-11 18:28:52 -05:00
jack1142
335ded674e [Core] Add Python executable field to [p]debuginfo command (#3184)
* Update core_commands.py

* Create 3184.enhance.rst
2019-12-11 16:40:27 -05:00
jack1142
b6ae7a6d21 [Core] Add redbot --debuginfo flag (#3183)
* [Core] Add `redbot --debuginfo` flag

* Update cli.py

* Create 3183.enhance.rst

* Update __main__.py

* Update __main__.py
2019-12-11 15:49:57 -05:00
Draper
c67b6cd443 [Audio] Say no to busylooping :Awesome: (#3176)
* Say no to busylooping :Awesome:

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

* chrore

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

* black y u do dis 2 me

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

* Return regardless if error is raised here
2019-12-09 20:26:26 -05:00
palmtree5
7dd5ed3446 Merge remote-tracking branch 'release/V3/develop' into V3/develop 2019-12-08 20:17:45 -09:00
Flame442
8cba47f382 Fixed [p]announce failing due to errors messaging the owner. (#3166)
* Fixed owner message behavior

* Create 3166.bugfix.rst

* Reduce messages

* Fix plurality
2019-12-08 18:04:57 -05:00
Vexed
203cde9805 Slightly reword installation guide to attempt to stop people using dev (#3079)
* create

* add the code

* change de logs

* lets remember linux exists

* fix formtting and change wording

* change the wording a bit more... after running `sphinx-build` it def looks like i used bold a lot

god that was a long commit mesage

* review changes

* draper's review (see description)

i have put or worse partially to scare users and partially as there are other possible effects of a downgrade

* review

* clarity
2019-12-08 18:02:40 -05:00
Tomas S
064d97f87b [Downloader] Catch and handle erorr in update_all when target repository/branch is missing (#3080)
* [Downloader] Catch and handle erorr in update_all when target repository/branch is removed from remote

* Rewrite fix, remove ctx from repo_manager, edit docstring, add annotations

* Text formatting

* Group failed repo messages into padded table, catch single updated repo fails

* Error catching v2; repo_manager design change

* Docstrings, typos and changelog

* Add Optional to update_repos annotatition

* Wrong logic

* Clear-er log message.

* add format_failed_repos, change _repo_update for failed messages

* Merge cog updating with fail repo logic; Filter out failed repos

* Merge cog updating with fail repo logic; Cog updating logic shuffled to support sending fails at the end

* Docstring typo

* format_failed_repos - proper docstring

* repo_manager.update_repos argument name fix

* downloader._cog_checkforupdates added missed failed message

* downloader._cog_update_logic place back return on some errors

* Purge unused stuff from downloader._repo_update

* downloader._cog_update_logic Change exception catching

* _cog_update_logic purging obsolete

* Remove obsolete 'message' from _cog_checkforupdates

* Fix forgotten ctx.send

* Wording

* Removed obsolete 'message'

* Fix wrong type hint in , update docstring

* repo update logic fix

* format_failed_repos type hint and docstring repair

* Extend _get_cogs_to_check with 'update_repos'

* Fix type mangling in _get_cogs_to_check

* fix: typo

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>

* _repo_update; Added single repo up-to-date message
2019-12-08 17:59:53 -05:00
jack1142
9a051ef2c6 [Downloader] Fix [p]findcog not working with different levels of imports (#3178)
* Update downloader.py

* Create 3177.bugfix.rst
2019-12-08 17:58:43 -05:00
jack1142
0f62614055 [Downloader] Disable all git auth prompts on clone/pull (#3159)
* fix(downloader): disable all git auth prompts on clone/pull

* chore(changelog): add towncrier entry
2019-12-07 19:54:49 -05:00
jack1142
672050727f [Downloader] Ensure consistent output from git commands (#3160)
* fix(downloader): ensure consistent output from git commands

* chore(changelog): add towncrier entry
2019-12-07 19:31:43 -05:00
jack1142
d136d594f4 [Changelogs] Remove info about branch support in revision argument (#3158)
* chore(changelog): remove info about branch support

* chore(changelog): fix grammar issue
2019-12-07 19:29:41 -05:00
Flame442
b4186d6724 Clarifies what [p]backup does (#3172)
* Clarify `[p]backup`

* Create 3172.enhance.rst
2019-12-07 19:24:07 -05:00
jack1142
02d6b7d658 [Launcher] To make experience with launcher amazing /s (#3175)
* Update launcher.py

* Create 3174.bugfix.rst

* Revert launcher.py

* Update launcher.py

* Update launcher.py
2019-12-07 18:56:23 -05:00
Flame442
d07e718ab8 Fixes errors on repo add from empty string values for install_msg (#3153)
* Fixes errors on repo add from empty string values for `install_msg`

* Create 3153.bugfix.rst
2019-12-02 11:41:28 -05:00
jack1142
f0836d7182 [Core] Dispatch on_red_api_tokens_update event on api keys update (#3146)
* feat: dispatch `on_red_api_tokens_update` event on api keys update

* docs: add event reference in Shared API Keys docs

* chore(changelog): add tonwcrier entries

* fix: wrap dispatched api tokens in MappingProxyType

* docs: reflect change of type change to read-only Mapping
2019-11-23 16:58:35 -05:00
jack1142
bc5c2513f6 [Audio] Improve help string for [p]audioset emptydisconnect (#3051)
* enhance(audio): improve help string for [p]audioset emptydisconnect

* chore(changelog): add towncrier entry
2019-11-22 18:59:38 -05:00
Vexed
ec834a0666 Audio playlist capitalisation changes (#3048)
* commit une

* changelog

* help me whi can i mot thnik

* i can make changelogs correctly
2019-11-22 18:54:39 -05:00
PredaaA
77742179c0 [Core] Add [p]listdisabled command (#3118)
* Update core_commands.py

* Create 3115.feature.rst

* Rename 3115.feature.rst to 3118.feature.rst

* Add a message if there's any disabled commands.

* Use the same format as [p]command disable/enable

* Make strings more i18n friendly.

* Flame's requested changes.
2019-11-22 18:54:01 -05:00
kennnyshiwa
a3140b6659 [audio] adds typing indicator to playlist dedupe (#3058)
* [audio] adds typing indicator to playlist dedupe

* [audio] not sure what happened here lol

* [audio] forgot the return

* add changelog

* [audio] fix for black
2019-11-22 18:53:42 -05:00
jack1142
4b62598a3d [Downloader] Make Repo.clean_url work with relative urls. (#3142)
* fix(downloader): return string, catch ValueError for relative urls

* chore(changelog): add towncrier entry
2019-11-19 13:14:22 -05:00
Michael H
ddfabb0c0e Changes from 3.1.8 (#3139) 2019-11-18 23:45:32 -05:00
Michael H
51298f156b pt2 (#3132) 2019-11-17 11:08:30 -05:00
Michael H
141b48d3cf Add .codeclimate.yml (#3131)
- This is still not ready to be used as a PR check
  - Can be used to get an idea of where we can look to clean up code
2019-11-17 11:00:26 -05:00
jack1142
5a7c36c581 chore(changelog): fix wrong references in changelog entries for Downloader (#3130) 2019-11-17 16:35:48 +01:00
jack1142
8a90996b36 [Downloader] Add Repo.clean_url and use it in [p]findcog (#3129)
* enhance(downloader): add `Repo.clean_url` and use it in `[p]findcog`

* chore(changelog): add towncrier entries
2019-11-17 10:25:15 -05:00
jack1142
548a50b984 [Docs] Add information about `info.json's min_python_version` key in Downloader Framework page. (#3125)
* docs(downloader): add missing `min_python_version` key

* chore(changelog): add towncrier entry
2019-11-15 22:28:17 +01:00
jack1142
19e8e60a4d [Audio] Stop player before destroying on emptydisconnect (#3119)
* fix(audio): stop player before disconnect in emptydisconnect

* chore(changelog): add towncrier entry
2019-11-14 13:05:48 -05:00
Vexed
6aeca83c63 Increased clarity of wording in info command (#3121)
* make branch + preliminary code

* correction

* towncrier

* sinbad's changes
2019-11-14 13:04:00 -05:00
Draper
33178ef034 [Audio-3.2] Fix an issue with mixplaylist being recognised as single tracks (#3104)
* Fix an issue with mixplaylist being recognised as single tracks

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

* Add changelogs

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
2019-11-11 11:59:51 -05:00
flaree
710b520da9 [Docs] get_shared_api_keys -> get_shared_api_tokens (#3110)
* keys -> tokens

* Changelog
2019-11-09 14:58:10 -05:00
Michael H
b3363acf77 reorder some startup to prevent heartbeat issues (#3073)
* reorder some startup to prevent heartbeat issues

* changelog

* handle startup cleanup in audio

* style

* rebased to handle conflict

* be a little smarter to prevent (some) infinite hangs

* Fix a pre-existing NoneType Error

* Migrate config before things are using it...

* another place we should ensure we're ready

* rename-toavoid-issues

* fix cache ordering and mis-use of ensure_future

* remove incorrect typehints

* style
2019-11-09 14:19:57 -05:00
Michael H
6852b7a1d1 License info command (#3090)
* Adds a licenseinfo command

* good enough for now

* changelog

* *sigh* Fine, have it your way Draper

* thanks Flame
2019-11-09 14:06:07 -05:00
Kowlin
418f957332 Update the issue templates (#3109)
This update will automatically attach the relevant labels to the dedicated issue templates.
2019-11-09 13:54:41 -05:00
Vexed
a05508a9f2 [Docs] It's 2019. Let's not say it's 2018. (#3105)
* guys... it's still not 2018

* changelog

i miss git

* review
2019-11-09 15:50:40 +01:00
Michael H
dd899c804a Remove the mongo driver (#3099)
* kills mongo

* changelog

* more refeences to mongo needed to go
2019-11-08 14:48:04 -05:00
jack1142
1651de1305 [Core] Add redbot --edit cli flag (replacement for [p]set owner&token) (#3060)
* feat(core): add `redbot --edit` cli flag

* chore(changelog): add towncrier entries

* refactor(core): clean up `redbot --edit`, few fixes

* fix(core): prepare for review

* chore(changelog): update towncrier entry to use double ticks :p

* style(black): ugh, Sinbad's git hook isn't perfect (using worktrees)

* fix: Address Flame's first review
2019-11-08 12:07:32 -05:00
Bakersbakebread
078210b54c change to_check.guild to getattr() (#3101)
* change to_check.guild to getattr()

* add webhook check

* changelog

* Update changelog.d/3100.bugfix.rst

Co-Authored-By: Michael H <michael@michaelhall.tech>
2019-11-08 09:43:21 -05:00
jack1142
e2c8b11008 [V3 Downloader] Revision tracking (#2571)
* feat(downloader): Install cog from specific commit in repo (initial commit)

- Repo and Installable have commit property now
- New class inheriting from Installable -
InstalledCog (old one from converters.py removed)
- New Repo.checkout() method, which is also async ctx manager
ref #2527

* fix(downloader): Keep information about repo's branch in config

- This is needed to make sure that repo can go back from detached state in some rare unexpected
cases
- current branch is determined by `git symbolic-ref` now as this command errors for detached
HEAD

* feat(downloader): Update repo without cogs, update single cog

The most important part of issue #2527 has been added here
- `[p]repo update` command added
- new conf format - nested dictionary repo_name->cog_name->cog_json
  installed libraries are now kept in conf too
  - `InstalledCog` renamed to `InstalledModule` - installed libraries use this class
  - `Downloader.installed_libraries()` and `Downloader.installed_modules()` added
  - `Downloader._add_to_installed()` and `Downloader._remove_from_installed()`
    now accept list of modules, of both cogs and libraries
- `[p]cog install` tells about fails of copying cog and installing shared libraries
- `[p]cog update` will truly update only chosen cogs (if provided) or cogs that need update
  - pinned cogs aren't checked
  - before update, repos are updated
  - to determine if update is needed `Repo.get_modified_modules()` is used
- `[p]cog pin` and `[p]cog unpin` commands for pinning/unpinning cogs added
- `Repo.checkout()` allows to choose ctx manager exit's checkout revision
- `Repo.install_cog()` returns `InstalledModule` now and raises CopyingError (maybe breaking?)
- `Repo.install_libraries()` returns 2-tuple of installed and failed libraries (maybe breaking?)
- `RepoManager.get_all_cogs()` added, which returns cogs from all repos
- `RepoManager.repos` property added, which contains tuple of `Repo`

* test(downloader): Repo.current_branch() throws an exception, when branch can't be determined

* style(downloader): rename _add_to_installed to _save_to_installed

This method is used for both adding and updating existing modules in Config

* refactor(downloader): add ctx.typing() for few commands

`[p]cog install` is nested hell, can't wait for moving install logic to separate method

* fix(downloader): refactor and fix `set` usage

* perf(downloader): update commits for ALL checked modules to omit diffs next time

This will also disable running git diff for cogs that have the same commit as the latest one

* style(downloader): few style improvements

- use of mutable object in method definition
- make Repo._get_full_sha1() public method
- too long
line
- don't use len to check if sequence is empty

* feat(downloader): add `[p]cog updateallfromrepos` and `[p]cog updatetoversion` commands

- moved cog update logic into `Downloader._cog_update_logic()` (lack of better name)
  - splitted
whole cog update process into smaller methods
  - might still need some improvements
- added new
methods to `Repo` class:
  - `is_on_branch()` to check if repo is currently checked out to branch

- `is_ancestor()` to check if one commit is ancestor of the other
- fix for
`Downloader._available_updates()` behaviour
broken by commit
5755ab08ba67556b3863e907c6f44d80f4f13d88

* feat(downloader): try to find last commit where module is still present

Enhancements:
- `Installable` now has `repo` attribute containing repo object or `None` if repo is
missing
- `Downloader._install_cogs()` and `Downloader._reinstall_libraries()` are able to install
modules from different commits of repo
- `Repo.checkout()` as ctx manager will now exit to commit
which was active before checking out
- unification of `rev` and `hash` terms:
All function
parameters are explicitly called `hash`, if it can only be commit's full sha1 hash or `rev` if it
can be anything that names a commit object, see
[link](https://git-scm.com/docs/git-rev-parse#_specifying_revisions)
- new
`Repo.get_last_module_occurence()` method, which gets module's Installable from last commit in which
it still occurs

* docs(downloader): Add basic description for `InstalledModule`

* fix(downloader): cog ignored during updates if its commit was missing

After config format update, commit string is empty until update and when such cog was checked and it
wasn't available in repo anymore, it was ignored

* refactor(downloader): Installing cogs from specific rev will pin them

* perf(downloader): Don't checkout when current commit equals target hash

- changes to `Repo.checkout()`:
  - `exit_to_rev` is now keyword only argument
  - added
`force_checkout` to force checkout even if `Repo.commit` value is the same as target hash

* refactor(downloader): Repo._run() stderr is redirected to debug log now

- added two keyword arguments:
  - `valid_exit_codes` which specifies valid exit codes, used to
determine if stderr should be sent as debug or error level in logging
  - `debug_only` which
specifies if stderr can be sent only as debug level in logging

* style(downloader): stop using `set` as arg name in `_load_repos()`

* feat(downloader): pass multiple cogs to `[p]cog (un)pin`

* refactor(downloader): accept module name instead of instance, fix spelling

* style(downloader): few small style changes

* fix(downloader): add type annotations + fixes based on them

- fix wrong type annotations and add a lot of new ones
- add checks for `Installable.repo` being `None`
- fix wrong return type in `Downloader._install_requirements`
- show repo names correctly when updating all repos
- fix error when some requirement fails to install

BREAKING CHANGE:
- type of `Repo.available_modules` is now consistent (always `tuple`)

* tests: use same event loop policy as in Red's code

* enhance(downloader): fully handle ambiguous revisions

* build(deps): add pytest-mock dependency to tests extra

* fix(downloader): minor fixes

* feat(downloader): add tool for editing Downloader's test repo

This script aims to help update the human-readable version of repo
used for git integration tests in ``redbot/tests/downloader_testrepo.export``
by exporting/importing it in/from provided directory.

Note
----
Editing `downloader_git_test_repo.export` file manually is strongly discouraged,
especially editing any part of commit directives as that causes a change in the commit's hash.
Another problem devs could encounter when trying to manually edit that file
are editors that will use CRLF instead of LF for new line character(s) and therefore break it.

I also used `.gitattributes` to prevent autocrlf from breaking testrepo.

Also, if Git ever changes currently used SHA-1 to SHA-256 we will have to
update old hashes with new ones. But it's a small drawback,
when we can have human-readable version of repo.

Known limitations
-----------------
``git fast-export`` exports commits without GPG signs so this script disables it in repo's config.
This also means devs shouldn't use ``--gpg-sign`` flag in ``git commit`` within the test repo.

* tests(downloader): add git tests and test repo for them

Also added Markdown file that is even more clear than export file
on what the test repo contains.
This is manually created but can be automated on later date.

* test(downloader): add more tests related to RepoManager

These tests use expected output that is already guaranteed by git tests.

* chore(CODEOWNERS): add jack1142 to Downloader's folders

I know this doesn't actually give any benefit to people that don't have
write permission to the repo but I saw other big fella devs doing this,
so I think this might be advisable.

* enhance(downloader): allow easy schema updates in future

* enhance(downloader): more typing fixes, add comments for clarity

* feat(downloader): add python and bot version check to update process

follow-up on #2605, this commit fully fixes #1866

* chore(changelog): add towncrier entries

* fix(downloader): use `*args` instead of `commands.Greedy`

* fix(downloader): hot-reload issue - `InstallableType` now inherits from `IntEnum`

There's desync of `InstallableType` class types due to hot-reload
and `IntEnum` allows for equality check between different types

* enhance(downloader): ensure there's no cog with same name installed

should fix #2927

* fix(downloader): last few changes before marking as ready for review
2019-11-07 20:36:16 -05:00
aikaterna
d85fb260e7 [Audio] Expose FriendlyException on play command (#3085)
* [Audio] Expose FriendlyException on play command

* Add changelog
2019-11-06 17:41:18 -05:00
Jeremiah Boby
e79a08e392 Add autostart documentation for venv users (#3028)
* Add documentation for venv users

Resolves #3005

* Add changes to changelog.d

* Use "redenv" over "path/to/venv"
2019-11-06 12:29:01 -05:00
Toby Harradine
7e9b1b87e6 Allow keeping data in redbot-setup delete (#2965)
* Allow keeping data in `redbot-setup delete`

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Add changelog entry

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-11-06 12:24:54 -05:00
Michael H
b8cbaa2fa0 Merge 3.1.7 (#3098)
* uvloop + python3.8

* Lavalink bump to 3.2.1_846

* [Release] 3.1.7

- Handles a dependency issue for python3.8
- Updates the Lavalink jar used
  - This include's Nin's stat fix
  - Streaming from Soundcloud is working again, at least for now.

* 3.1.7
2019-11-05 08:13:32 -05:00
Michael H
ddd9c4c6b0 [Permissions] Ensure defaults are cleared when clearing all rules (#3041)
- fixes #3037
2019-11-04 23:09:01 +01:00
Kowlin
a729a474b1 Added documentation for PM2 (#2105)
* Added PM2 documentation

* Grammar fix

* Build error fix.

* Just work T_T

* Update docs/autostart_pm2.rst

Co-Authored-By: Vexed <51716387+Vexed01@users.noreply.github.com>

* Update docs/autostart_pm2.rst

Co-Authored-By: Vexed <51716387+Vexed01@users.noreply.github.com>

* Create 2105.docs.rst
2019-11-04 16:52:01 -05:00
palmtree5
911aed5fe2 [Docs] Getting Started Guide improvements (#3083)
* Add MS Azure to the list of hosting providers

* Fix some typos, wording, incorrect commands

* towncrier

* Update docs/getting_started.rst

Co-Authored-By: Michael H <michael@michaelhall.tech>
2019-10-23 23:45:25 -04:00
palmtree5
d50d9eba7e Merge remote-tracking branch 'release/V3/develop' into V3/develop 2019-10-23 18:45:53 -08:00
El Laggron
53606a4bbc [Docs] Do not overwrite rst_prolog (#3082)
* Do not overwrite rst_prolog

* Add towncrier entry
2019-10-23 17:51:35 -04:00
Ryan
3b653f93fc [Docs] Update Cog Creation guide install (#3021)
* Use stable instead of dev version

* changelog entry

* Rename 3020.docs.rst to 3021.docs.rst

* update wording in Getting Started

* add note about package layout

* fix some formatting

* spellcheck

* add cookiecutter note

* Update 3021.docs.rst
2019-10-23 17:51:12 -04:00
Michael H
17c8cbb057 Add support for accessing config by ids (#3022)
* Add support for accessing config by ids

* update-changelog with methods
2019-10-22 16:25:01 -04:00
El Laggron
ee293876d9 [RPC] Set custom port with flags (#2429)
* [RPC] Set custom port with flags

* Add changelog entry
2019-10-21 22:46:56 -04:00
jack1142
3b0fa0c05d [Core/Downloader] Add 3rd-party lib folder to sys.path before loading cogs (#3062)
* fix(core,downloader): add lib folder to sys.path before loading cogs

* chore(changelog): add towncrier entry

* fix(core): always append 3rd-party lib path to the end of `sys.path`
2019-10-21 22:43:00 -04:00
jack1142
8267ad9aab [Docs] Add missing descriptions for function returns (#3054)
* docs: add missing descriptions for function returns

* chore(changelog): add towncrier entry
2019-10-21 22:41:35 -04:00
jack1142
a3b6aafaca [Docs] Link to Getting started guide at the end of installation guides (#3032)
* docs: link to Getting started guide at the end of installation guides

* chore(changelog): add towncrier entry
2019-10-21 22:38:24 -04:00
jack1142
2dbed96be1 [Audio] Restart Lavalink after unexpected shutdown (#3034)
* fix(audio): lavalink not getting restarted after unexpected shutdown

* chore(changelog): add towncrier entry
2019-10-21 22:37:07 -04:00
Draper
2ba6fb17ca [Audio] Handle Missing SQL deps more gracefully (#3066)
* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

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

* Remove duplicated call in `[p]playlist update`

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

* Handle both ModuleNotFoundError and ImportError and pull a more complete error message to forward to the user and fix grammar.

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

* Since we aren't 100% certain of message length here due to using the error message for the raised error ... lets use pagify so this doesn't bite us in the future.

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

* .... Lets not reinvent the wheel

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

* Address Jack's review
2019-10-21 22:36:24 -04:00
Michael H
3723b4b1ea Merge 3.1.6 (#3069)
* Version Bump

 - d.py 1.2.3 -> 1.2.4
 - Red 3.1.5 -> 3.1.6

This fixes a critical issue with voice connections.

* Merge changelog
2019-10-18 17:54:27 -04:00
jack1142
172dd58903 [Docs] Change links to d.py docs to use pinned version instead of v1.0.1. (#3053)
* docs: change links to d.py docs to use stable version instead of v1.0.1

* chore(changelog): add towncrier entry

* docs: add |DPY_VERSION| substitution and :dpy_docs: role

* chore(changelog): update towncrier entries to reflect new changes
2019-10-17 07:58:39 -04:00
Draper
a9a547e56d [Audio] Formatting Sucks ... lets standardize it a little ahead of PR1.5 (#3059)
* Formatting Sucks ... lets standardize it a little ahead of PR1.5

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>

* Finish applying this logic to other instances where it is relevant + change logs

Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>
2019-10-16 22:43:57 -04:00
jack1142
d42a2d5140 [Core] Fix error message about guild-only command and add dm-only error message (#3057)
* enhance(core): fix guild-only error message, add dm-only error message

* chore(changelog): add towncrier entries
2019-10-16 08:17:04 -04:00
Flame442
428bf55480 Makes bot.send_filtered return the message that is sent (#3052)
* Makes bot.send_filtered return the sent msg

* Create 3052.enhance.rst

* :that:

Co-Authored-By: jack1142 <6032823+jack1142@users.noreply.github.com>
2019-10-13 19:32:24 -04:00
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
Flame442
9b60816ebd [Downloader] Catch OSErrors from invalid repo names (#3029)
* [Downloader] Ensure repo names only contain the characters stated

* Create 2827.bugfix.rst

* [Downloader] Catch OSErrors from invalid filenames

* Update 2827.bugfix.rst

* Style

* do the thing again

* Update 2827.bugfix.rst
2019-10-04 22:19:00 -04:00
El Laggron
ea77de5d56 [Docs] Getting started guide (#2659)
* Getting started guide

* Remove DigitalOcean referral link

* Fix typos and mispells, thanks to @Flame442

* Remove cogs.red hyperlink until it is finished

* Add towncrier entry

* Add prolog.txt

This is not necessary for this PR but all of the other cog guides rely on this file.
The cog guides are individual branches based on this one, which is why I'm adding this file right now.

* QA changes

* More QA changes

considering -> assuming
red is a girl, not non-binary
2019-10-03 12:47:46 -04:00
DiscordLiz
05eba603a5 Prevent error on empty install message. (#3024)
* Prevent error on empty install message.

* Create 3024.bugfix.rst
2019-10-02 20:38:52 -04:00
Vexed
37f27d8ae4 [Docs] Git Install Updates, note about launcher deprecation, capitalise some words (#2998)
* commit

* add launcher depricate to linix/mac

* sorry linux i still ignored you

capitalise some linix stuff i couldnt be bothered to do before

* remove mentions of launcher & update changelog enrty
2019-09-29 07:43:53 -04:00
Vexed
759ca3ba7e Quotes in helpset tagline for clarity (#3012)
* Add quotes to clarify helpset tagline

Add quotation marks to helpset tagline's response so two consecutive full stops don't appear.

* more commiting

* make travis/black happy

* for review

make no longer repeat tagline
2019-09-29 07:42:44 -04:00
jack1142
59e7d063a0 [Tunnel] Add use_cached and images_only kwargs to files_from_attach (#2887)
* feat(tunnel): add `use_cached` kwarg

re #2885

* feat(tunnel): add `images_only` kwarg

re #2885

* chore(changelog): add towncrier entry
2019-09-28 17:28:14 -04:00
jack1142
6e3ccc1a21 [Commands] cls parameter can now be passed to group like in d.py (#2881)
* fix(commands): ``cls`` parameter can now be passed like in d.py

* chore: add changelog entry

* Update changelog.d/2881.misc.rst

Co-Authored-By: Toby Harradine <Tobotimus@users.noreply.github.com>

* Rename 2881.misc.rst to 2881.enhance.rst
2019-09-28 17:24:49 -04:00
jack1142
80628a28a7 [Core] Give friendly error when provided instance name doesn't exist. (#2969)
* Update data_manager.py

* Towncrier entry
2019-09-28 17:22:45 -04:00
Aurorum
6bb1004bcd [Trivia] Greek Myth: Caduceus Typo (#2994)
* [Trivia] Greek Myth Typo

* Changelog entry
2019-09-28 17:18:58 -04:00
jack1142
6170a56648 [Docs] Link directly to installing Red from installing requirements using chocolatey section (#2995)
* docs: link directly to installing Red from installing using chocolatey

* chore(changelog): add towncrier entry
2019-09-28 17:18:08 -04:00
Draper
f3b6c4cf32 Fixes [p]trivia leaderboard not running. (#2983)
* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

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

* Adds `invoke_without_command` to `[p]trivial leaderboard`

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
2019-09-28 17:12:43 -04:00
Michael H
83483abfa5 Reserves command names (#2977)
* Reserves command names

  - Currently, only reserving ``cancel``
  - This should only impact matching command qualified names
  - This also checks aliases
  - This makes cc and alias use the new module constant with info about
  this
  - Module constant is available for use by 3rd party cogs which may
  dynamically create responses.

* Change misleading var name

* style

* Thanks Flame!

* Handles issues with CC
2019-09-28 16:58:40 -04:00
Michael H
e38c08ab12 fix uptime for uptime of less than a second (#3009)
* fix uptime for uptime of less than a second

* changelog

* More conclusive fix
2019-09-28 15:35:26 -04:00
Michael H
c288185a16 Fix a misplaced changelog file (#3019) 2019-09-28 09:59:21 -04:00
Flame442
b9ed8b84f5 [CustomCom] Use simple by default (#3013)
* Allow creating simple CCs by default

* Create 3013.rst

* Screwed up the filename
2019-09-28 02:46:40 -04:00
Michael H
ee162f6f9e Fixes an issue with allowed_by_whitelist_blacklist (#3017) 2019-09-27 16:12:29 -04:00
Friesi
101e977939 Change the hierarchy issue messages (#3016)
* Change the hierarchy issue messages,
because they are difficult to translate with the verb variable.

* Fix typos

* Add changelog entry

* Reformatting with black
2019-09-27 12:10:35 -04:00
Michael H
25614620db More privatization, and some error helpers (#2976)
* More privatization, and some error helpers

This makes a lot more things private. Continued from #2967, fixes #2984
Adds public methods for various things.

Below is a brief summary of things available elsewhere, though this
particular set of changes may warrant a detailed section in the release notes.

 - bot.db.locale -> redbot.core.i18n.get_locale
   - Note: This one already existed.
 - bot.db.help -> redbot.core.commands.help.HelpSettings
 - bot db whitelist/blaclist? -> bot.allowed_by_whitelist_blacklist
   - This has also been made a single cannonical function for this
   purpose including check usage
 - bot color? -> bot.get_embed_color/bot.get_embed_colour
 - bot.id.api_tokens? ->

   - bot.get_shared_api_tokens
   - bot.set_shared_api_tokens
   - bot.remove_shared_api_tokens

 -bot.db.prefix -> bot.get_valid_prefixes
   - (Note: This is a wrapper around bot.get_prefix)

 Other changes include
  - removing `bot.counter` as it was never used anywhere
  - Adding properties with helpful error messages for moved and renamed
  things
  - making bot.uptime a property with an error on set
  - adding a migration to the bot config for shared_api_tokens

* Remove overly encompassing message redaction, eval is a risk, dont run in dev if you cant manage it

* address Flame's feedback

* rephrase example

* changelog extras

* You saw nothing
2019-09-26 12:55:05 -04:00
Michael H
62dcebff94 [Downloader] findcog no longer attempts to find cogs for commands without them (#2970)
* findcog no longer attempts to find cogs for commands without them

* changelog

* full stop
2019-09-26 12:19:58 -04:00
Michael H
af97175839 Fix an issue with clearing permission rules (#3015)
Fixes #3014
2019-09-26 11:41:11 -04:00
Michael H
1ee5238ad7 Remove a specific f-string usage in the launcher. (#3002)
* @Kowlin I saw that error

* style
2019-09-23 16:52:48 +02:00
Draper
575e55cb0f Fixed crash originated in bank.set_balance (#2997)
* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

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

* Pushed the Fix for new issue introduced by #2926

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

* Changelogs

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

* Rename changelog to a misc

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

* Address Flame's review

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

* Correct an outdated reference in the docs

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

* Reword docstring for the RuntimeError

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
2019-09-22 02:05:27 -04:00
Michael H
ee3be8b633 Extra info (#3008)
* Extra info

* changelog
2019-09-20 16:55:46 +02:00
Flame442
7f22d27d51 Fixes the logic of MessagePredicate.greater and MessagePredicate.less (#3004)
* Fixes the logic of MessagePredicate.greater and MessagePredicate.less

* Create 3004.bugfix.rst
2019-09-18 17:15:24 -04:00
Michael H
4546ca9ba6 Clarify usage of humanize_timedelta (#3000)
- resolves #2986
2019-09-15 19:32:07 -04:00
Flame442
1e97597bc2 [Trivia] Overwatch - Remove a blank answer (#2996)
* Remove a blank answer

* Create 2996.bugfix.rst
2019-09-15 01:09:35 -04:00
DevilXD
77f1da30ea Tempban logging improvement (#2993)
* user and guild are logged now

* Added changelog entry

* Make sure Forbidden always triggers this as well
2019-09-13 18:40:54 -04:00
jack1142
682b86c193 [Permissions] Possible solution for clearing out usage of commands with <who_or_what> (#2992)
* style(permissions): clear out usage of commands with <who_or_what>

* chore(changelog): add towncrier entry

* style(permissions): fix black formatting
2019-09-09 15:29:19 -04:00
jack1142
cdb7a02cb8 [Core] Fix infinite typing for commands with cooldown (#2987)
* fix(core): cooldown error can't reinvoke command (infinite typing issue)

* chore(changelog): add towncrier entry
2019-09-07 19:25:02 -04:00
Flame442
0be3b1acd7 Fixes the strings of [p]set usebotcolor (#2974)
* Fixes the help text of `[p]set usebotcolor`

* Create 2974.bugfix.rst
2019-09-02 09:43:56 -04:00
Michael H
4f1f49d96f [Modlog] userinfo stops breaking with high numbers of roles on a user (#2971)
* Fixes it, I guess

* changelog

* reluctant handling of what the people want here

* mypy would have prevented this one
2019-09-02 09:38:19 -04:00
Michael H
6075c5bde0 Rename bot.db as bot._config (#2967)
* Rename `bot.db` as `bot._config`

  - Continues work towards strong version guarantees
  - Added methods for cog use for a few things which were previously
  only accessible via direct access.
  - Retained private use in a few internal use locations, though most
  methods were updated away from this.
  - Updated documentation for shared api token users

* changelog

* more detail

* docstring fixes

* Apparently, I forgot to commit something I had locally

  - + a copy/paste failue in the changelog

* *sigh*:

* *sigh*
2019-09-01 15:42:28 -04:00
Toby Harradine
d86cc7a854 Bump dependencies (no more SSL errors) (#2939)
Also made the Makefile work slightly nicer with other tools, e.g. IDE run configurations, by allowing the python executable to be set as an env var.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-09-01 09:29:54 +10:00
Toby Harradine
25fb389a7d [Docs] Update linux install docs, redo venv docs (#2920)
* [Docs] Update linux install docs, redo venv docs

Some of our pre-req installation docs needed updating on Windows - this adds new sections for Fedora Linux and Debian/Raspbian Buster, and also removes some unnecessary pre-requirements from other distributions. These have all been tested on fresh VPSes, installing Red both in venvs and with --user, and they all seem to work.

Also, apparently the venv docs were too scary before. These changes try to make it clear that it's easier to use than users may think.

This also includes a little note to stop users from accidentally installing Python with pyenv after installing pre-requirements on Ubuntu.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Add changelog entries

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Update officially supported platforms in README.md

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Combine sections and add openSUSE

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Include example of using `--user` flag

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Use `py -3.7` on Windows outside of venv

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Reorganise changelog entries

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-08-30 18:08:26 -04:00
Flame442
d9e774f079 [Docs] Typo fix (#2953)
* Fixed a typo

* Create 2953.misc.rst
2019-08-30 17:41:40 -04:00
Draper
7959e0c916 [Bank] Allow Bot Owner/Guild Owners to remove invalid users from the bank (#2845)
* Add command to remove dead members from bank

* Add a global check

* Added a FIXME so `bank_local_clean` is updated once bulk-update is implemented
Added a brief warning to warn devs not to use the `_get_base_group` as it can mess up their config files
Removed a redundant existence check

* Updated  commit to reflect changes requested in review

* Updated  commit to reflect changes requested in review

* 🤦

* Return command to run with user id so we don't worry about safeguarding the command agaisn't invalid formats

* Braaaainnn

Removed aliases that used old naming scheme

* TL:DR  Added global bank support, and rework permissions

Renamed `bank_local_clean` to `bank_prune`
Added support for global banks to `bank_prune`
`bank_prune` will now raise `BankPruneError` if trying to prune a local bank and `guild` is not supplied

Renamed `cleanup` subgroup to `prune`
`prune` subgroup will have 3 commands:
  `user`   :  Deletes the bank account for the specified member : Accepts `Union[discord.Member, discord.User, int]`
  `global` :  Prune global bank accounts for all users who no longer share a server with the bot
  `local`  :  Prune local bank accounts for all users who are no longer in the guild

Changed check for `prune` subgroup to be `@check_global_setting_admin()`
[p]bank prune local  : Can be run by Guild owners only
[p]bank prune global : Can be run by Bot Owner only
[p]bank prune user   : Can be run by Admins, Guild owners and Bot Owner

* Yikes ... Updated kwarg name

* Fixed unexpected unindent: docstring of redbot.core.bank.bank_prune:14:Field list ends without a blank line

* ...

* 3rd time lucky?

* 4th time lucky?

* Fix Docstring

* Initial commit to address review by Flame

* Updated code to reflect Flame's comments

* Skip pruning of unavailable guilds

* Fixed typo in string

* *sigh* black is the bane of my existence

* addressed Flames commends
Fixed [p]bank prune user, When run via DM it will now return an error message to the user (Thanks jack1142)

* Time to get some sleep

* 'DM' > 'DMs' in string

* Add towncrier entries

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

* Update to reflect Flame's review

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
2019-08-30 17:35:25 -04:00
DJtheRedstoner
ef3ac77bd8 Fix issues with embed_requested() (#2966)
* Fix issues with embed_requested()

* Update embed_requested()

Make embed_requested()'s user settings only affect DM's

* Towncrier for #2966
2019-08-30 17:20:34 -04:00
Flame442
efcf91e934 Catch discord.errors.Forbidden when DMing a user the invite message (#2948)
* Catch discord.errors.Forbidden when DMing a user the invite message

Uses the same error message as `[p]help`

* Create 2948.bugfix.rst

* You saw nothing
2019-08-29 21:19:19 -04:00
aikaterna
b8a7a66566 [Core] Inviteset public and perms help cleanup (#2963)
* [Core] Inviteset public and perms help cleanup

* Towncrier entry
2019-08-29 21:18:43 -04:00
Draper
e04eed4a89 [Bank] Allow bank managers to set the maximum allowed balance in the bank (#2926)
* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

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

* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

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

* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now
`[p]bankset maxbal` can be used to set the maximum bank balance

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

* Rename method 🤦

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

* Updated this to be aware of #2925

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

* Addressed Flames review + Fixed 1 bug in errors.py + `[p]leaderboard` and `[p]bank balance` will set the users balance to max balance if the bank maxbal is lower than the previous user balance

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

* Missed this clarification

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

* address Flames review

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
2019-08-29 21:05:31 -04:00
jack1142
b490942bcd [Core] Various fixes to redbot-setup delete (#2958)
- ``redbot-setup delete`` no longer errors about "unexpected keyword argument" (fix #2955)
- ``redbot-setup delete`` no longer prompts about backup when user passes ``--no-prompt`` option (fix #2956)
- ``--[no-]backup``, ``--[no-]drop-db`` and ``--[no-]remove-datapath`` in ``redbot-setup delete`` command are now on/off flags. ``--no-prompt`` was changed to flag too, but it doesn't have ``--prompt`` equivalent as there's no much point in having it
- ``redbot-setup`` now uses `click.confirm` for confirmation prompts and thy now also have default values for user convenience
2019-08-30 08:23:31 +10:00
jack1142
26cc85806e [Utils] Stop using : in backup's filename - Windows doesn't accept it (#2957) 2019-08-28 12:01:02 +10:00
Michael H
2c8152606c [Modlog] Reduce potential for bad API calls (#2945)
- brings a fix over from #2934
2019-08-28 08:45:56 +10:00
Draper
3c1b6ae4cf [Utils] Add humanize_number() function to chat formatting (#2836)
This adds babel as a dependency, and also includes `redbot.core.i18n.get_babel_locale()`
2019-08-28 08:44:52 +10:00
ZeLarpMaster
6c3a3fea66 Fixing typo in starwars trivia (#2903)
* Fixing typo in starwars trivia

Been pointed out to us by kDals#5653

* Add a changelog entry
2019-08-27 12:44:07 -04:00
jack1142
fb9fec282b [Mod] Fix recording username changes (#2919)
* fix(mod): past names are now properly recorded in `on_user_update` event

* chore(changelog): add towncrier entry

* chore(changelog): specify what commands were affected
2019-08-27 12:42:56 -04:00
Michael H
bbd30411fd Fixed small docstring inconsistency (#2924)
* Fixed docs inconsistency

* Added changelog entry

* Changed category from bugfix to misc
2019-08-27 12:40:36 -04:00
Draper
2056f9f8d0 [Bank] Check recipient balance before completing transfer (#2925)
* 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>

* Black.... you are supposed to trigger before commits

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

* Added `BalanceTooHigh` to the docstrings of `bank.transfer_credits()`

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

* Bring this in line with #2926 to reduce conflicts,`is_global()` already is called inside `get_currency_name` and as such it does not need to be called outside

Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com>
2019-08-27 12:40:11 -04:00
Michael H
68018c924e [Economy] House always wins in slots (#2875)
* [Economy] House always wins in slots

 - Expected payout is negative
 - No flat increase payouts, all payouts are multiplicative

* actually do math properly

* UX + Changelog

* How the hell did I mess that up?!
2019-08-27 12:38:56 -04:00
DiscordLiz
0c773134f2 [Mod] Fix modset deletedelay (#2943)
* [Mod] Fix modset deletedelay

  fixes #2942

* Style guide fix
2019-08-27 09:27:24 -04:00
DevilXD
0d16e27070 Changed category from bugfix to misc 2019-08-27 10:34:44 +02:00
Toby Harradine
326c53d6c4 Quick fix for postgres config details prompt (#2951)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-08-27 14:25:05 +10:00
Toby Harradine
d1a46acc9a PostgreSQL driver, tests against DB backends, and general drivers cleanup (#2723)
* PostgreSQL driver and general drivers cleanup

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Make tests pass

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Add black --target-version flag in make.bat

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Rewrite postgres driver

Most of the logic is now in PL/pgSQL.

This completely avoids the use of Python f-strings to format identifiers into queries. Although an SQL-injection attack would have been impossible anyway (only the owner would have ever had the ability to do that), using PostgreSQL's format() is more reliable for unusual identifiers. Performance-wise, I'm not sure whether this is an improvement, but I highly doubt that it's worse.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Reformat

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Fix PostgresDriver.delete_all_data()

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Clean up PL/pgSQL code

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* More PL/pgSQL cleanup

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* PL/pgSQL function optimisations

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Ensure compatibility with PostgreSQL 10 and below

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* More/better docstrings for PG functions

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Fix typo in docstring

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Return correct value on toggle()

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Use composite type for PG function parameters

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Fix JSON driver's Config.clear_all()

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Correct description for Mongo tox recipe

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Fix linting errors

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Update dep specification after merging bumpdeps

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Add towncrier entries

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Update from merge

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Mention [postgres] extra in install docs

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Support more connection options and use better defaults

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Actually pass PG env vars in tox

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Replace event trigger with manual DELETE queries

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-08-26 22:02:26 -04:00
Flame442
57fa29dd64 Rename two changelog files from fix to bugfix (#2949) 2019-08-27 11:42:31 +10:00
Michael H
bfa55922cf Add note for using towncrier with standalone PRs (#2915) 2019-08-27 11:39:51 +10:00
El Laggron
43da727a9f [Utils] Text to file (#2849) 2019-08-26 10:13:21 +10:00
jack1142
580c4429e8 Add pip-wheel-metadata folder to .gitignore (#2941) 2019-08-20 10:37:23 +10:00
PredaaA
3498f8ccb6 Remove commas for explanations about how to set API keys (#2905)
This removes commas in explanations about how to set API keys in streams, image and audio cogs, since it has been changed in #2692.
2019-08-18 09:50:48 +10:00
Aurorum
5e9b3d9190 [Trivia] Michael Jackson & Prince Lyrics (#2894)
I thought it'd be fun to offer lyrics as a trivia, but it can be tricky selecting songs which are known widely enough that a significant number of people have a reasonable chance of answering the questions correctly.

As such, to narrow down the scope, these add trivia questions for Michael Jackson and Prince, two of the most well-known artists across the world whose musical collections are extensive and popular enough to dedicate an entire trivia too.

They're comprised of a variety of lyrics, two lyrics for each song. Players need to name the song from the lyric. Most of the songs are well-known songs from the artist, but there are plenty of challenges too.
2019-08-18 09:31:32 +10:00
Flame442
dfb4212d43 [Docs] Fix user parameter to predicates being typed as discord.TextChannel (#2914) 2019-08-18 09:05:22 +10:00
Ianardo DiCaprio
b1ccfab6d2 [Mod] BugFix (#2932)
* Initial Commit

* Add files via upload
2019-08-13 14:33:03 -04:00
Michael H
984a97d958 [Core Commands] remove set owner and set token (#2928)
* remove set-owner and set token

* whoops, wrong number on the changelog

* whoops, git didnt detect the rename cause I didnt add the file
2019-08-13 13:57:16 -04:00
DevilXD
463546f102 Added changelog entry 2019-08-08 17:12:58 +02:00
DevilXD
477a17d0b9 Fixed docs inconsistency 2019-08-08 17:06:11 +02:00
Draper
556af32bb5 Stop saving JSON files with indents (#2921)
Stop saving json files with 4 indents, this will significantly reduce file size and improve `.set()` performance for config saves when using the JSON driver.

Signed-off-by: Draper <guyreis96@gmail.com>
2019-08-07 10:13:31 +10:00
Toby Harradine
ef8b9b81c3 [ModLog] Optimise get_next_case_number() (#2908)
* [ModLog] Optimise get_next_case_number()

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Address reviews

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Add changelog entry

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Delete get_next_case_number

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Add modlog.get_latest_case() and fix `[p]reason`

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-08-02 10:49:22 -04:00
Toby Harradine
9362dd9465 Merge V3/release/3.1.5 into V3/develop 2019-07-31 09:43:35 +10:00
Toby Harradine
6c9c57c14d Bump version to 3.1.5
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-31 09:23:18 +10:00
Toby Harradine
404c5f6dc0 [Audio] Bump Lavalink version to 3.2.1_823
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-31 09:22:16 +10:00
DiscordLiz
20091cc10a [ModLog API] Add default casetypes, remove need for a specific auditlog action (#2901)
* I know this needs a changelog entry and docs still

* update tests for new behavior

* update docs, filter; add changelog

* Ready for review

* stop fetching the same Audit logs when the bot is the mod

* I forgot to press save

* fix a comprehension

* Fix AttributeError

* And the other place that happens

* timing fixes
2019-07-27 15:37:29 -04:00
Michael H
6280fd9c28 disabled help hideaways (#2892)
* disabled help hideaways

* can_see fix
2019-07-27 02:36:21 -04:00
Toby Harradine
af096bc1cc Config locks (#2654)
* Config locks

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Add locks for all_XXX

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Remove a word

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Add acquire_lock kwarg for value context manager

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Add towncrier entry

Signed-off-by: Toby <tobyharradine@gmail.com>

* Fix issues with `get_custom_lock` and `get_members_lock`

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-23 16:50:07 -04:00
Michael H
a8091332b8 [Docs] Add version guarantees (#2882)
Make some version guarantees.

We also need to do a pass over the existing things this would cover and decide if any of them should be made private.
2019-07-19 11:50:33 +10:00
DevilXD
7ba50c91a2 Fixed remove_command error when trying to remove a non-existent command (#2889)
* Fixed remove_command error when trying to remove a non-existent command

* Added changelog entry
2019-07-18 05:49:40 -04:00
DevilXD
0ba9eaeccc Slowmode now accepts integer only inputs as seconds (#2884)
* Slowmode now accepts integer only inputs as seconds

* Added changelog entry
2019-07-17 08:47:37 -04:00
Michael H
d4b6fdea92 Add towncrier (#2873)
* Adds towncrier as our changelog system.
* Updates our contributor guidelines for this.

Resolves #2872
2019-07-17 11:12:43 +10:00
Toby Harradine
f0f274e1e1 Bump version to 3.1.4
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-16 11:26:25 +10:00
Toby Harradine
e9f014df07 Revert "Update Crowdin configuration file" (#2878)
This reverts commit 03e59ea9.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-16 10:44:49 +10:00
Toby Harradine
778eadd418 [ModLog] Fix get_case() and get_casetype() (#2877)
This fixes `[p]reason` and `[p]case` with cases that were created after 3.1.3.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-15 20:18:21 +10:00
Toby Harradine
3de9d15410 [CustomCom] Set Requires.ready_event before invoking CC (#2876)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-15 20:13:06 +10:00
Michael H
3b0567d261 bump (#2864) 2019-07-14 00:17:44 -04:00
Michael H
49a75b5f19 command translator quick fix (#2870)
* command translator quick fix

* command translator quick fix
2019-07-14 13:55:47 +10:00
Toby Harradine
8676dd3ce3 [i18n] Update translation catalogs (#2867)
* [i18n] Update translation catalogs

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Install redgettext 3.1 in travis crowdin deployment

Last time the catalog templates were updated, redgettext 3.0 was used. I'd rather Travis didn't upload them after extracting with an older version.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-13 23:52:18 -04:00
Toby Harradine
d5c412e3df [Permissions] Fix Requires.ready_event.set() on subcommands (#2868)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-13 23:05:07 -04:00
Toby Harradine
1d2980f8fa [Permissions] Send help on missing argument (#2865)
* [Permissions] Send help on missing argument

Resolves #2851.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* [Permissions] Use varargs instead of Greedy converter

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-13 21:52:28 -04:00
jack1142
3e80edcdfd [Docs] Add awaits and missing imports in usage examples (#2860)
Well, the modlog examples had to be changed a lot, because `await` obviously won't work in regular method.
2019-07-14 11:30:10 +10:00
aikaterna
be184b57dd [Audio] Update equalizer permissions (#2813)
* [Audio] Update equalizer permissions

* Reformat header on eq list for i18n

* Style
2019-07-13 20:48:13 -04:00
Toby Harradine
f83f378528 [Core] Make Requires.verify() wait until rules are loaded (#2857)
* Make Requires.verify() wait until rules are loaded

Also ensures `Requires` objects are reset when unloaded, particularly in case a `Command` object manages to stay in memory between cog unload and load, and its permissions rules change between those events.

Also, this PR re-ordered some of the event loop policy stuff, because it was required that the event loop policy be set before creating any `Requires` objects. This may or may not have an effect on other `get_event_loop()` calls elsewhere (either in our code, a dependency's, or asyncio's). Either way, these effects would be a *correction*, and any bugs that arise from it are likely to have been occurring silently beforehand.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Remove calls to `remove_listener()` in permissions

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Fix adding rules for permissions cog/commands itself

Also addresses feedback

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Clean up indentation when setting uvloop policy

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Use `set(walk_commands())` to traverse `Group` subcommands

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-13 20:47:40 -04:00
Michael H
21a6384ebf [Modlog] Fix get_case for missing cases (#2858)
Due to the modlog redesign, the detection for a missing case changed. This fixes `get_case` for this.

This resolves #2844.
2019-07-14 10:47:16 +10:00
Toby Harradine
03e0683dd7 [ModLog] Actually prevent duplicate kwarg error
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-13 14:39:50 +10:00
Michael H
ac2813012a [Core] Cog load fixes (#2854)
* split out some fixes from red#2853

* address feedback

* feedback
2019-07-12 22:11:06 -04:00
Toby Harradine
e34eca557b [ModLog] Prevent duplicate kwarg error (#2848)
* [ModLog] Prevent duplicate kwarg error

The `name` key used to be set in the Config for casetypes.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Don't mutate `data` argument

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-12 20:17:00 -04:00
Flame442
687b88ca6f Added some periods to some strings (#2852) 2019-07-12 18:29:27 -04:00
DevilXD
776c75ba86 Fixed [p] not being replaced in code blocks (#2846) 2019-07-09 03:22:52 -04:00
aikaterna
55ff9bedb7 [Audio] Check for player when not connected (#2842) 2019-07-08 13:17:50 +10:00
Toby Harradine
2bdc3ac10c [General] Fix KeyError in [p]urban
Resolves #2841.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-07 18:51:36 +10:00
Toby Harradine
f2039300c2 [Mongo] Use escaped identifiers to extract inner value (#2832)
This was causing a KeyError to be raised whenever a key containing $ or . was part of the identifiers path, even if the value was actually in the dict.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-07-05 18:02:18 -04:00
Cog-Creators Bot
03e59ea9d3 Update Crowdin configuration file 2019-07-05 20:21:25 +02:00
Draper
a89a156f8e [Context] Adds react_quietly method to context (#2834)
* [Context] Adds react_quietly method to context

This allows developers to emojis to command messages
This is a modified version of tick()

It accepts True/False for Tick/Cross emoji and in addition to that any other emoji the bot can see

* Removed True/False support from react quietly

* Stopped catching InvalidArgument on react_quietly so that is propagated to devs
2019-07-03 15:26:22 -04:00
Michael H
0eb22c84ff [Bank API] Add cost decorator (#2761) 2019-07-03 10:07:19 +10:00
aikaterna
d1593b8069 [Audio] Catch unhandled internal folder types (#2824)
The `folder:` and `localfolder:` prefixes are used internally with some localtracks strings to define what to do with the item when it's cycled through the search function. Users theoretically should have never seen this issue as [p]search is used on the user side for YouTube and Soundcloud searching and not local searching, but this handles the issue where a folder is being passed to these functions that doesn't exist.
2019-07-02 13:15:34 +10:00
aikaterna
93391d028c [Audio] _enqueue_tracks clarification/fix (#2822)
Resolves #2820.

Added some comments to clear up a little of the mystery in a couple places.
2019-07-02 13:11:44 +10:00
Flame442
142fb0ad08 [Trivia] Car list fixes (#2825) 2019-07-02 12:03:12 +10:00
jack1142
942dca43d3 [Downloader]: RepoManager: don't load repos in __init__ (#2829)
Loading repos is already done in initialize() method.
This could actually turn out badly
if both
of git processes would touch the same repo at the same time.

This also fixes create_backup in
setup.py - now it properly generates repos.json
2019-07-02 11:57:30 +10:00
jack1142
081bf663a4 test(core): ignore pylint's ImportErrors for distro package (#2830) 2019-07-01 21:54:56 -04:00
Toby Harradine
55e309125e Add uvloop as Posix+CPython dependency and tweak new Make recipes (#2819)
- uvloop is now a dependency on non-Windows CPython systems
- `make setupenv` renamed to `make newenv`
- `make syncenv` added to sync local venv to current dependencies
- `dev-requirements.txt` moved into `tools` directory
2019-07-02 11:53:38 +10:00
Flame442
7323e8eb67 [Trivia] Beethoven corrections for entertainment list (#2823) 2019-07-01 09:29:26 +10:00
Michael H
098540b9e5 [Core] Fix user output on cog load/reload (#2767)
* [Core] Fix user output on cog load/reload

  - Properly fixes the load/reload exception handling
  - Fixes some i18n use here to not make assumptions about other
  languages pluralization rules.

* Fix some typos

* Address Flame's Feedback

* It's important to save before committing ...

* formatting

* Fix some formats...
2019-06-29 12:16:28 -04:00
Elizabeth Sherrock
10412c4f51 Fix broken link in set color docstring (#2803) 2019-06-30 01:35:47 +10:00
PredaaA
2f8b1a21c7 [Audio] Fix config set in shuffle and repeat commands (#2812)
Resolves #2811 and also the same thing in repeat command.
2019-06-30 01:28:29 +10:00
PredaaA
03fe3ee720 [i18n] Fix some missing i18n strings in the whole bot (#2633) 2019-06-30 01:13:53 +10:00
Michael H
8a72840de0 [Utils] Modify chmod use in safe_delete (#2701)
- Takes a pessmisitc approach that it's possible chmod succeeds, but
 deletion fails and does not make the entire dir world writeable
2019-06-30 00:45:44 +10:00
Ryan
8bf86f33a3 [Readme] Update RTD and d.py URLs (#2771) 2019-06-30 00:33:41 +10:00
Michael H
8637f8a852 [Filter] Fix cache invalidation (#2810) 2019-06-30 00:30:09 +10:00
Toby Harradine
bff7e214ab Kill JsonIO and fix JSON driver caching issues (#2796)
* Kill JsonIO and fix JSON driver caching issues

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Ensure lock covers critical region in set()

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Make tests pass

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Use pickle over deepcopy in Config

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Fix temp instance creation

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Serialise value before doing anything in set()

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-06-27 14:49:45 -04:00
Toby Harradine
f3bbfdc64d Fix duplicate commands in fuzzy help (#2798)
* Fix duplicate commands in fuzzy help

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Use help command's filter for all fuzzy

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-06-27 13:31:44 -04:00
Toby Harradine
461f56bca1 Kill Pipfile, update dependencies, and add dep bumping tools (#2806)
### Replacement for pipenv's environment setup
First of all, there's a new Make recipe for all devs and contributors on both Windows and Posix, `make setupenv`, which is kind of a replacement for `pipenv install --dev`. It creates a virtual environment in `.venv` using the inbuilt `venv` module, clearing out any existing virtual environment if needed first. Then it installs all dev dependencies using our new `dev-requirements.txt` file. `CONTRIBUTING.md` has been updated to reflect all of this.

### Dependency version bumping tool
Secondly, I've added a python script, `tools/bumpdeps.py` to help with bumping dependency versions. It has its own Make recipe too, `make bumpdeps`. This script won't work on Windows (yet). It reads the `tools/primary_deps.ini` file, which contains the primary requirements of Red and its extras with loose version specifiers, and outputs all pinned dependencies, in `setup.cfg` format. It's not a foolproof dependency resolver, it's quite simple, but it's bound to help out a lot. It'll try to give warnings if there might be a version conflict, but updating `setup.cfg` with its output and then doing `pip install -r dev-requirements.txt` will allow pip to issue warnings if something is conflicting.

So to add a new dependency, add it to `tools/primary_deps.ini` in the appropriate place, and either use `make bumpdeps` to completely update all dependencies, or simply add it to `setup.cfg` manually with its sub-dependencies, and all versions pinned.

### Sphinx 2.1.2 (docs changes)
The sphinx update brought along the ability to disable type annotations being rendered in function and method signatures, and I have gladly gone and done that. Type annotations should already be specified under the "Parameters" section, and the way sphinx renders them in function signatures makes them much harder to read.

Also, documented classes will now display what classes they inherit from.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-06-28 00:16:14 +10:00
aikaterna
d1d4ec3e38 [Audio] No capitalization needed (#2801) 2019-06-27 08:13:01 -04:00
aikaterna
a0f34bbbd9 [Audio] Move DJ role check in [p]summon (#2799) 2019-06-27 10:48:06 +10:00
Toby Harradine
49819a2eeb [ModLog] Fix get_all_casetypes() (#2807)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-06-26 20:39:22 +10:00
Toby Harradine
f91d8610ae [ModLog] Use custom scopes for ModLog Config (#2766)
Modlog was the biggest culprit for seriously large documents in the MongoDB backend, since it stored all cases as nested dicts in the guild scope. So, for example, on the Fortnite server, the guild document for Kowlin's bot had exceeded 8MB. 

This commit gives each case its own document. It also does the same for casetypes. Not only does it remove the possibility of the document exceeding the maximum size in MongoDB, it's also just more efficient for all backends.

Other misc changes: Fixed a bunch of type-hints, and also added more support for when an object related to a case (user, moderator, channel etc.) can't be found (because it was deleted or something rather)

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-06-26 00:52:33 +10:00
kennnyshiwa
52f5d5cd6a [Mod] add role mentions to userinfo and reverse role sorting (#2759)
* add role mentions to userinfo and reverse role sorting

This small PR adds the role mentions to userinfo and reverses the sorting so that the top most role of a user is at the left of the embed

* Remove sorting as it's handled by d.py
2019-06-24 02:39:03 -04:00
aikaterna
870b615364 [Audio] Queue & misc cleanup (#2784)
* [Audio] Queue & misc cleanup
2019-06-24 01:09:04 -04:00
aikaterna
25ccc11dc4 [Audio] Add [p]summon (#2786) 2019-06-24 01:05:01 -04:00
aikaterna
f2b7ce9546 [Audio] Add equalizer (#2787)
* [Audio] Add equalizer

* [Audio] Add equalizer
2019-06-24 00:58:20 -04:00
DiscordLiz
6bdc9606f6 [Core] Multiple mod admin roles (#2783)
* Adds Schema versioning
  - Adds Migration tool
  - Adds tool to migrate to allow multiple admin and mod roles
  - Supports Multiple mod and admin roles

* Ensures migration is run prior to cog load and connection to discord

* Updates to not rely on singular mod/admin role id

* Update requires logic for multiple mod/admin roles

* Add new commands for managing mod/admin roles

* Feedback

Update strings
Update docstrings
Add aliases

* Use snowflakelist

* paginate

* Change variable name

* Fix mistake

* handle settings view fix

* Fix name error

* I'm bad at Ux

* style fix
2019-06-23 23:36:00 -04:00
Toby Harradine
71d0bd0d07 Various Config and Mongo Driver fixes (#2795)
- Fixes defaults being mixed into custom groups above the document level when doing `Group.all()`
- Fixes `Config.clear_all()` with Mongo driver
- Fixes `Group.set()` with Mongo driver on custom groups above the document level
- Fixes `IdentifierData.custom_group_data` being set to the wrong thing in `BaseDriver.import/export_data` (although this was an inconsequential bug)

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-06-24 12:55:49 +10:00
Michael H
6ae3040aac [Filter] Make name filtering behavior consistent (#2794)
- Uses the updated filter check
  - This is also a performance gain on large servers with filter names
  enabled.
2019-06-23 16:39:24 +10:00
aikaterna
065396abab [Audio] Change Lavalink.jar version checking (#2785) 2019-06-23 14:10:31 +10:00
Toby Harradine
1804314f45 [Audio] Improve Lavalink download/connection exception handling (#2764)
- More errors will be logged to the console with clearer messages when something goes wrong
- Downloading the Lavalink Jar will abort after 5 failed attempts. The connect task will also abort if an unhandled exception occurs whilst downloading or connecting to Lavalink. After this occurs, instead of responding "Connection to Lavalink has not yet been established" to commands, the bot will respond "Connection to Lavalink has failed". This has no effect on other commands which don't involve connecting to Lavalink (e.g. settings commands).
- Logs this message when Lavalink jar is successfully downloaded: `Successfully downloaded Lavalink.jar (<x> bytes written)`
- Uses [`tqdm`](https://github.com/tqdm/tqdm/) to display a progress bar whilst downloading Lavalink.jar.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-06-23 14:09:59 +10:00
DevilXD
ff894ecbe7 [Docs] Add warning about PATH on Windows (#2791) 2019-06-23 13:55:48 +10:00
jack1142
0bf54fae52 [Admin] Convert set to list because set is not JSON serializable (#2792) 2019-06-23 13:33:27 +10:00
MeatyChunks
3c66c602f6 [Help] Prevent spamming when a user blocks the bot (#2790)
Currently the bot sends an error message for each page of help, this should make it only send once.
2019-06-22 01:41:11 -04:00
aikaterna
e854716236 [Audio] Fix for escape character prefixes (#2789) 2019-06-21 22:19:57 -04:00
jack1142
beb16b81a9 docs(config): wrong code example in Value.__call__ (#2780)
fix #2775
2019-06-21 21:24:23 -04:00
jack1142
57d5c0870a style(modlog): Phrase information about reason command better (#2777) 2019-06-19 08:40:25 -04:00
Neuro Assassin
9d008d587a [V3 Core] Add checks to [p]command (#2770)
* Add checks to [p]command

* Change to privilege level
2019-06-18 21:35:56 -04:00
DiscordLiz
804d6eecea [Core] Fix DictConverterer error handling format (#2776) 2019-06-18 21:25:49 -04:00
Toby Harradine
cc927248f0 Revert custom Bot.process_commands behaviour (#2768)
This still preserves the new event, which was a welcome change. However, context still needs to be invoked when a command isn't found, so `on_command_error` can still catch `commands.CommandNotFound`.

Fixes broken fuzzy help.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-06-12 18:27:47 -04:00
Toby Harradine
d133598d80 [Audio] Fix OSErrors on mixed-filesystem environments (#2765)
Resolves the issue outlined [here](https://github.com/Cog-Creators/Red-DiscordBot/issues/2682#issuecomment-500185495).

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-06-09 16:04:13 +10:00
Michael H
682ee1a459 [Docs] Fixes the docs for commands.Command.error (#2760)
* docfix

* inline code for sphinx, not a ref
2019-06-04 20:21:30 +02:00
El Laggron
61b5730c48 [V3 Core] More features for the bot invite URL (#1847)
* Add inviteset group command

* Fix errors

* Fix line break

* Remove user bot support

* Fix docstrings line breaks

* Remove embed specific formatting

* Remove invite redirect

* Add self argument to _can_get_invite_url

* Remove unused import

* fix errors related to classes + double help

* Removed self bot support
2019-06-03 11:46:13 -04:00
DevilXD
463d8d6306 [Commands] Added optional default_unit to the TimedeltaConverter (#2753)
* Added default_unit to the TimedeltaConverter

* Fixed a possible converter crash

* Updated get_timedelta_converter to incorporate the new kwarg
2019-06-03 07:46:55 -04:00
aikaterna
da40511306 [Audio] Remove blacklisted architectures (#2755) 2019-06-02 18:56:41 -04:00
aikaterna
c2195ec576 [Audio] Add bot permission checks (#2756) 2019-06-02 18:55:44 -04:00
Michael H
16443c8cc0 [CI] Improve automated checks (#2702)
* same stuff, but with some more spurious error supression

* fix issue in permissions found in this

* fix a few more spurious errors

* fix another issue

* semi-spurious error fixes

* .

* formatting

* move this to properly log

* distutils import + virtualenv

* more fixes
2019-06-02 19:42:57 +02:00
Bakersbakebread
9116cd02e6 [Core] Pass exceptions on Cog Load to user. (#2754) 2019-06-02 11:57:43 -04:00
Flame442
652d9fe950 Update version number to 3.1.2 🎉 (#2751) 2019-05-31 17:58:04 -04:00
Ryan
e956e6e320 [Streams] Ignore lack of rerun info where not available (#2748) 2019-05-31 16:41:23 -04:00
aikaterna
2e58922d01 [Audio] Lavalink jar bump (#2750) 2019-05-31 16:26:20 -04:00
Michael H
33b7652b62 [Help] Detatch menu usage into a task (#2725)
* [Help] Detatch menu usage into a task

  - This resolves #2712
  - This is a minor API change. Conceptually, the difference is minor in
  nature `bot.send_help_for` returns when help has been sent, however
  this can now be prior to when the help menu (if one is in use) is
  closed.

  - This should not be considered breaking as there is and has been a
  a warning about this file's APIs being still up for unannounced modifications
  No developers should be currently relying on this behavior.

* operator precendence
2019-05-31 21:41:04 +02:00
Michael H
0e9086ca1f [Core] Fix error handling in loading extensions. (#2688)
* fixes 2687

* raise the right exception
2019-05-31 21:38:44 +02:00
Michael H
3ca2a9af28 [Help] Fix long cog helps (#2730)
* [Help] Fix long cog helps

  - Why do people thing a category help of over 250 characters is more
  useful than putting the help in relevent commands?!

* toss an MD fix in here too I guess
2019-05-31 21:37:50 +02:00
DiscordLiz
e7b615d921 [Commands] Adds support for non interactive use (#2746)
Adds assume_yes to context
Changes cleanup's 100+ check
Changes cog update.
2019-05-31 05:54:27 -04:00
Matan Kushner
2cb6e98092 [Readme] Anilist → AniList (#2747) 2019-05-31 05:39:14 -04:00
DiscordLiz
1ccc441aab [Core] Make contact use configured destinations (#2743)
* Make contact use configured destinations
2019-05-31 05:32:26 -04:00
DiscordLiz
8ddc5aa63e [Core] Add commands to manage owner notification destinations. (#2745) 2019-05-31 05:13:36 -04:00
DevilXD
f894b62bfe [CustomCom] Fixed KeyError on specific message edge-case (#2739)
fixes #2679
2019-05-29 22:36:32 -04:00
DevilXD
aac9369f3f [Mod] Add [p]slowmode (#2734)
Makes use of new timedelta converter
2019-05-29 10:01:27 +10:00
DiscordLiz
1581604f71 Add a timedelta converter (#2736)
* Add a timedelta converter

This reuses a lot of logic from @mikeshardmind 's scheduler cog with permission

It adds a timedelta converter
It keeps it generalized as requested
It keeps the function available for non converter use as requested

* Handle feedback

* style fix
2019-05-28 12:43:55 -04:00
DiscordLiz
56161c0a88 Send to owners (#2738)
* Add bot.send_to_owner

resolves #2665

Adds some basic methods and config entries.

Does not add commands for modifying this yet.

* Use send_to_owners in events

* handle feedback
2019-05-28 12:37:02 -04:00
DiscordLiz
242df83785 [Image] Fix some issues in strings (#2737) 2019-05-28 02:47:44 -04:00
Stonedestroyer
2338ad8223 [Image] Fix giphy api (#2653)
* Remove hardcoded Giphy key.

Allows you to set your own Giphy API key.

* Run black

* Fix Giphy name

On their website it's spelled GIPHY.
2019-05-27 21:04:15 -04:00
PredaaA
b4f4e080af [core_commands] Using humanize_timedelta for [p]uptime command (#2735)
* Update core_commands.py
2019-05-27 20:39:36 -04:00
Flame442
132545e057 [Audio] Clarity changes for the API info commands (#2733)
* Clarity changes for the API info commands

* Remove unnecessary f-string
2019-05-27 20:27:56 -04:00
Michael H
68590dfdb8 [Core] Improve API token converter (#2692)
* improve api converter

* make usage more clear
2019-05-25 23:58:14 +02:00
Neuro Assassin
2e271d695b Add respectable aliases for consistency (#2731)
* Add respectable aliases for consistency

* General command name for alias.py

* Forgot one for alias

* General command for filter

* General command for warnings

* Whoops

Resolves #1749
2019-05-24 18:22:17 -04:00
Stonedestroyer
cd745d35c2 [Core] Add debug info command (#2728)
Shows useful debug information for debugging.
2019-05-24 17:55:48 -04:00
Flame442
6928e2aca2 Fixes some issues with API help commands (#2729)
* Fixes some issues with `[p]streamset youtubekey/twitchtoken`

Lots of general formatting bugs and clarity issues.

* General formatting bugs and clarity issues
2019-05-24 17:52:43 -04:00
Kowlin
49e86614c5 Adding support for GitHub Funding (#2732)
(I'm lazy, I know I should use a fork... sorry <3)
2019-05-24 20:10:55 +02:00
palmtree5
b83c7bf453 Merge remote-tracking branch 'release/V3/develop' into V3/develop 2019-05-23 02:20:29 -08:00
jack1142
51dcf65fd7 [Downloader] Fix problem with copying directory tree. (#2690)
* fix(downloader): clear paths in `distutils.dir_util._path_created` before copying tree

fix #2685

* style(downloader): add comment about PR
2019-05-23 03:08:14 -04:00
Michael H
c6c0165214 [Help] Special case fixing for empty docstring (#2722)
Resolves #2415
2019-05-22 18:22:31 +10:00
PredaaA
342935de49 [Trivia] Remove bold on a box (#2716) 2019-05-21 17:50:51 -04:00
jack1142
ced5bb4631 docs(install): remove information about voice extra (#2717) 2019-05-21 17:49:25 -04:00
DiscordLiz
0a832cee9c [Utils] Allow functools.partial use with menu (#2720) 2019-05-21 16:52:44 -04:00
Brenden Campbell
1cfce8b72c Fix minor typo (#2713) 2019-05-20 13:24:05 -04:00
jack1142
cdcde26dfc [Setup] Fix: wrong var used for instance data in remove_instance (#2709) 2019-05-19 10:13:01 -04:00
Fixator10
1ffb79f852 [events] send help on BadUnionArgument exception (#2707)
* [events] send help on BadUnionArgument exception

* Update redbot/core/events.py

Co-Authored-By: Michael H <michael@michaelhall.tech>
2019-05-19 06:14:12 -04:00
Michael H
644aaf0c0e [Help] Continuing work and bug-fixes (#2676)
* [Help] Add settings for various things

  - Fixes a small issue in a previously not-exposed logic path
  - Fixes an issue with denied commands help invocation
  - Adds some global usage settings

* remove outdated comment

* improve intent of strings

* added punctuation

* Add DM forbidden handling

* use a slightly different method for shortening embed width specifically
2019-05-18 06:54:02 -04:00
Michael H
cdea03792d [Streams] Fix NameError (#2699)
- fixes #2696
 - My fault for just looking at the github diff and seeing the logic fix
 on this one, without verifying the name validity in surrounding
 context. (see my review in #2679)
2019-05-18 00:59:26 -08:00
Flame442
b190e7417e [Downloader] Adds ctx.typing() to [p]pipinstall (#2700) 2019-05-17 21:40:42 -04:00
zephyrkul
7dd3ff7c8d [Core] Strip commas in user input for load, reload, unload (#2693)
rstrip commas, closes #2695
2019-05-16 23:54:17 -04:00
palmtree5
21a253103e [V3 Streams] fix an issue with stream commands not dealing with reruns (#2679) 2019-05-16 22:28:26 -04:00
El Laggron
db3fb29b30 [docs] Fix typo (#2691) 2019-05-16 17:03:09 -04:00
Neuro Assassin
c5d2ae5831 Mention voice channel in [p]userinfo (#2680)
* Mention voice channels, due to new discord update

* Add ID part back in, as Discord doesn't have copy ID for it yet
2019-05-16 15:12:09 +02:00
jack1142
9d0ca00f89 [General]: shorten descriptions properly with disabled embeds in urban (#2684)
fix #2683
2019-05-16 02:06:46 -04:00
DevilXD
79e5d2c9d7 Fixed command doc formatting in code blocks (#2678) 2019-05-15 20:01:02 -04:00
Fixator10
3a62d392b4 [Mod] [p]names utilize consume-rest (#2675)
Currently, `[p]names` requires quotes, if "user" arg contains spaces
2019-05-15 10:33:10 -04:00
Michael H
f2858ea48c [Core] Fix update notification (#2677)
removes a problematic await
2019-05-15 16:31:00 +02:00
Michael H
3c78fb420b [Help] Fixes some issues with fuzzy help (#2674)
* Fixes some issues with fuzzy help

 - also cleans up some name shadowing which wasn't causing issues
 (curently)

* ver

* style
2019-05-14 23:52:06 -07:00
palmtree5
255444d7e1 Merge remote-tracking branch 'release/V3/develop' into V3/develop 2019-05-14 22:43:09 -08:00
Michael H
2d22ee7ccc 🎉 (#2673) 2019-05-14 21:30:52 -08:00
Michael H
9a243a1454 [Utils] Tools for marking things unsafe for general use (#2326)
* Tools for marking things unsafe for general use

* I'm facepalming so much...

Actually, make the two do something different 
instead of getting distracted writing different docs for both based on intended usage.

* local scopes mmkay + tests

* Move file to adress feedback

* typo fix

* Update __init__.py

* Fix issue with exported names in __init__

* changelog
2019-05-14 20:56:41 -07:00
Will
165e40c0db [Mongo] Unescape dict keys when rebuild (#2671)
* Unescape dict keys when rebuilding

* update pipfile

* Really update pipfile now
2019-05-14 23:50:51 -04:00
Michael H
7f1c2b475b [Core] Help Redesign (#2628)
* [Bot] Support new design

* [Context] use the new help in `ctx.send_help`

* [Commands] Update Cog and Group for help compat

- Removes a trap with all_commands, this isn't a good way to check this
- Adds a help property
- Fixes command parsing in invoke

* Redesigns red's help

* handle fuzzy help

* style

* handle a specific ugly hidden interaction

* fix bot-wide help grouping

* changelog

* remove no longer needed -
2019-05-14 20:49:51 -07:00
palmtree5
a5f38fa6e6 Add my 3.1 contributions (#2670) 2019-05-14 19:38:18 -08:00
Michael H
598968bf74 [Audio] Lavalink jar bump (#2669) 2019-05-14 20:35:09 -07:00
palmtree5
71955becb1 Merge remote-tracking branch 'release/V3/develop' into V3/develop 2019-05-14 19:32:27 -08:00
Will
ee661b0a9f [Docs] Add release notes for major functionality changes and instructions (#2668)
* Add release notes for major functionality changes and instructions

* Update docs/changelog_3_1_0.rst

Co-Authored-By: Michael H <michael@michaelhall.tech>
2019-05-14 20:30:17 -07:00
Michael H
7d7b3413bc Add contributions to changelog (#2666)
* Put some stuff in the changelog

* explicit note on d.py ver
2019-05-14 23:07:22 -04:00
Flame442
7400008384 Adds [p]datapath to print the bot's data path (#2652)
* Add `[p]datapath` to print the bot's datapath

* Adds 2652
2019-05-14 01:05:52 -04:00
TrustyJAID
4e564e8ce4 [V3 Economy] lookup users from the guild instead of using stored names (#2637)
* [V3 Economy] lookup users from the guild instead of using stored names

* Make user ID only appear when owner calls the leaderboard and the user is not in the guild also black formatting

* Slight optimizations in formatting and fix error when no banks exist
2019-05-14 00:52:08 -04:00
aikaterna
8691fdc533 [Docs] Update Mac install instructions for Java (#2663) 2019-05-14 06:46:30 +02:00
Flame442
7d103f1d32 [Docs] Typo fix (#2658) 2019-05-14 00:34:19 -04:00
Kowlin
3de1a265ea [Core] Added co-owners to outdated message warnings (#2664)
* Added co-owners to potentially DMd users

* Fetch the cached of every guild, see comments of #2664

* Ensure owners can be DM'd.

* Refactoring try block.
2019-05-14 00:33:05 -04:00
entchen66
3b6d4d9df6 [Cleanup] Fix for 3.1 (#2661)
cleanup before use get_message wich isn't possible anymore in dp 1.0.1. so I changed it to fetch_message
2019-05-13 05:39:28 -04:00
Michael H
e96e5374b4 Update intershpinx refs (#2660) 2019-05-13 12:02:57 +10:00
Toby Harradine
5c91709ac8 Remove vendoring note in README 2019-05-10 10:34:17 +10:00
NIXC
96a91b9d0e [Audio] Add ability to skip to a specified track by number (#2584)
* add skip to track functionality

* formatting

* Change some strings

* missing return, variable naming

* add variable names for translations

* change handling of skipping 1 track

* minor semantic adjustments

skiptotrack -> skip_to_track
2019-05-08 01:34:37 -04:00
aikaterna
65b88c09fb [Audio] Update for Red-Lavalink 0.3.x (#2547)
* [Audio] Update for Red-Lavalink #55

* Update setup.cfg

* Catch all the exceptions

* Update version range on Red-Lavalink

* Catch excepts on Spotify tracks/playlist upload

* No prefix needed
2019-05-08 00:56:40 -04:00
Flame442
80ff07f53d [Core] Fix two typos in API Token Converter (#2650) 2019-05-07 16:12:08 -04:00
Flame442
4f6485d1f9 [i18n] Changes default locale value from en to en-US (#2642)
* Changes default locale value to `en-US`

* Added this PR

Also fixed slight inconsistency in what is in a code block (if this is merged)
2019-05-06 12:19:57 -04:00
Flame442
d1c903f36f Fix grammar mistake by changing "to large" to "too large" (#2646) 2019-05-06 12:18:57 -04:00
Kowlin
516ebcfa2b Fixed missing imports. (#2643)
* Added changelog

* Fixed missing imports.
2019-05-03 10:22:06 -04:00
Kowlin
9414de24d4 [Mod] Added voice kicking. (#2639)
* Added voice kicking.

* Reversed exceptions.

* Lets use a check that works for this task 👀

* Update abc.py

* Black formatting
2019-05-03 10:08:25 -04:00
Michael H
16990071cb [V3 core] on_message_without_command dispatch (#2338)
* conveinience and performance addition with new dispatch

* I promise, I read the specs first.

* Really, I read the fucking specs.
2019-05-02 16:25:35 -04:00
James
52433d253f String format failure when set invoked without subcommand (#2638) 2019-05-02 15:44:33 -04:00
Will
bb6327d969 [Audio] Fix architecture blacklisting (#2634) 2019-04-30 12:32:16 -04:00
Toby Harradine
476f441c9b [Audio] Refactor internal Lavalink server management (#2495)
* Refactor internal Lavalink server management

Killing many birds with one stone here.
- Made server manager into class-based API with two public methods: `start()` and `shutdown()`. Must be re-instantiated each time it is restarted.
- Using V3 universal Lavalink.jar hosted on Cog-Creators/Lavalink-Jars repository.
- Uses output of `java -jar Lavalink.jar --version` to check if a new jar needs to be downloaded.
- `ServerManager.start()` won't return until server is ready, i.e. when "Started Launcher in X seconds" message is printed to STDOUT.
- `shlex.quote()` is used so spaces in path to Lavalink.jar don't cause issues.
- Enabling external Lavalink will cause internal server to be terminated.
- Disabling internal Lavalink will no longer reset settings in config - instead, hard-coded values will be used when connecting to an internal server.
- Internal server will now run both WS and REST servers on port 2333, meaning one less port will need to be taken up.
- Now using `asyncio.subprocess` module so waiting on and reading from subprocesses can be done asynchronously.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Don't use shlex.quote on Windows

Signed-off-by: Toby <tobyharradine@gmail.com>

* Don't use shlex.quote at all

I misread a note in the python docs and assumed it was best to use it. Turns out the note only applies to `asyncio.create_subprocess_shell`.

Signed-off-by: Toby <tobyharradine@gmail.com>

* Missed the port on the rebase

* Ignore invalid architectures and inform users when commands are used.

* Style fix
2019-04-29 21:31:28 -04:00
Neuro Assassin
c79b5e6179 [Core Commands] Add patch for invalid path with [p]backup (#2626)
* Add patch
2019-04-29 19:38:36 -04:00
aikaterna
c1bee3fee5 [V3 Audio] Add settable path for external Lavalink (#2480) 2019-04-29 18:34:09 -04:00
aikaterna
d786103d8d [Audio] Set cwd for localtracks on playlist start (#2534) 2019-04-29 18:28:44 -04:00
Neuro Assassin
b3850f6bb7 [Cleanup] Add [p]cleanup between (#2617)
* Add `[p]cleanup between`

At the moment, only deletes the messages in between, does not delete the messages specified when running the command.

* Create converter for message IDs; remove f-strings; use converters in a few extra commands

* Sacrifice to the style gods
2019-04-29 15:55:23 -04:00
PredaaA
e3db3c0341 [Core_commands] Fix some missing i18n strings (#2631)
* Update core_commands.py

* Update core_commands.py
2019-04-29 15:21:58 -04:00
jack1142
1ce3bc2870 [Downloader] Suppress commands.ExtensionNotLoaded for bot.unload_extension() call (#2625)
fix(downloader): suppress `commands.ExtensionNotLoaded` for `bot.unload_extension()` call (#2625)
2019-04-29 12:42:36 -04:00
jack1142
24ac111782 [V3 Downloader] Allow to specify minimum and maximum bot version in info.json (#2605)
* feat(downloader): add `min_bot_version` and `max_bot_version`

Adds actually working way of specifying minimum and maximum bot version and removes not working
`bot_version`

BREAKING CHANGE: - removal of `bot_version` attribute in `Installable`

* test(downloader): `Installable` tests fix for new bot version attributes

* docs(changelog): added changelog entries for this PR
2019-04-29 12:33:49 -04:00
Flame442
07f127ffe4 [Docs] Changelog entries for contributions by Flame (#2627) 2019-04-29 12:06:02 -04:00
Will
c58f566047 [CustomCom] Update command generator to work with dpy update (#2616)
* I think this works?

* Let's do it

Co-Authored-By: tekulvw <tekulvw@users.noreply.github.com>
2019-04-25 12:31:58 -04:00
Tyler Adam
f28d6dff32 [Streams] Add ability to exclude rerun streams from alerts (#2620)
* [Streams] Add ability to exclude rerun streams from alerts

* [Docs] Changelog entries for contributions by EgonSpengler

* Changelog entry for #2620 [Streams] Ignore Reruns In Alerts
2019-04-25 11:58:58 -04:00
Neuro Assassin
7e49ce9a7b [Mod] Remove error when unbanning an unknown user (#2619)
Fix for #2542
2019-04-25 11:57:26 -04:00
Will
59115cd1c7 [Commands] Fix built in check decorator order affecting permissions (#2621)
OOOoooooOOOoOOOooOoOOOOoooo
2019-04-25 11:51:02 -04:00
palmtree5
6e2aa825eb Merge remote-tracking branch 'release/V3/develop' into V3/develop 2019-04-25 02:25:25 -08:00
PredaaA
1d93fe4cf9 Update changelog_3_1_0.rst (#2615) 2019-04-25 05:44:34 +02:00
aikaterna
3f1f7640cb [Docs] Changelog entries for contributions by aikaterna (#2614) 2019-04-24 20:55:32 -04:00
TrustyJAID
83ee7c5e92 [V3 Dev] Fix repl command sanitize api keys (#2613) 2019-04-24 20:54:50 -04:00
jack1142
b95ddf18ba [Docs] Changelog entries for contributions by jack1142 (#2612)
* docs(changelog): [Mod] Allow admin to choose amount of repeats for "deleterepeats" #2437

* docs(changelog): Spelling correction of method name in Tunnel #2496

* docs(changelog): Tunnel fix - When tunnel closes, message should be sent to other end #2507

* docs(changelog): [V3 Downloader] Tell user how to load the cog after [p]cog install #2523

* docs(changelog): [V3 Audio] If bot has move members perm, it can join to user-limited channels #2525

* docs(changelog): [Trivia] Fix of dead image link (world flags) #2540

* docs(changelog): [V3 Test] Make sure that trivia test will use utf-8 encoding #2565

* docs(changelog): [V3 Core] Print actual version, when `--version` flag is used #2567

* docs(changelog): [V3 Downloader] Stop including subpackages in cog list #2590

* docs(changelog): [V3 Downloader] Uninstall multiple cogs #2592

* docs(changelog): [V3 Downloader] Always remove cog from installed in `[p]cog uninstall` #2595
2019-04-24 20:06:51 -04:00
Will
61d255726c [Docs] Add initial changelog document (#2611)
* Add initial changelog document

* add initial links

* oops
2019-04-24 17:08:55 -04:00
Will
05e2851c67 [Config] Migrate UUID for JSON backend (#2604)
* Migrate UUID for JSON backend

* Interesting...

* black

* Simplify UUID creation
2019-04-24 16:03:27 -04:00
Will
f20a174038 Fix the check to raise the correct message (#2603) 2019-04-24 21:24:31 +02:00
Michael H
ec108e7c02 [Alias] cleanup issues missed with #2587 (#2610) 2019-04-24 11:43:47 -04:00
Michael H
ad114295e7 Discord.py dep update 3.1 (#2587)
* Dependency update

discord.py==1.0.1
websockets<7

[style]
black==19.3b0

[Docs]
jinja==2.10.1
urllib3==1.24.2

Changes related to breaking changes from discord.py have also been made
to match

As of this commit, help formatter is back to discord.py's default
2019-04-23 21:40:38 -04:00
aikaterna
0ff7259bc3 [Audio] DJ role should ask for a role (#2606) 2019-04-23 18:41:46 -04:00
TrustyJAID
49af94334e Add Central API Key Documentation (#2574) 2019-04-23 23:51:37 +02:00
Flame442
22c318fda3 [Core] Adds a check to [p]set locale (#2553)
* Adds a check to [p]set locale

Fixes #2552
I would like a little bit of feedback on this change.
- Right now, `locale_name` is case sensitive. Should that remain case sensitive or should I allow it to accept it case insensitively?
- I made the invalid locale string an i18n string, however I don't know the process for how those are supposed to be made or if that will break anything. Should that remain an i18n string or should I make it a normal string?

* Case insensitivity and explicit en-US

-`[p]set locale` is now case insensitive
-`en-US` added to `[p]listlocales` instead of only existing in `[p]set locale`'s help text

* Remove spacing
2019-04-23 17:43:50 -04:00
jack1142
da5fd7699e [V3 Downloader] Always remove cog from installed when using cog uninstall (#2595) 2019-04-23 14:26:16 -04:00
Michael H
3d498a74ba [Docs] Update docs for redbot.core.humanize_list (#2598)
closes #2546
2019-04-23 14:16:15 -04:00
jack1142
460b4bb3f2 [Mod] Allow admins to choose amount of repeats for "deleterepeats" (#2437)
* feat(mod): configurable amount of repeats for "deleterepeats"

resolves #2267

* fix(mod): check user input instead of changing it if it's invalid

* fix(mod): only purge cache when argument is valid

* perf(mod): fetch repeats from config only when guild not in cache
2019-04-23 10:01:20 -04:00
Tyler Adam
47723cee33 [Mod] make [p]ban days parameter optional as per the doc (#2602) 2019-04-23 09:45:28 -04:00
Tyler Adam
a1b03be27e Add support for custom stream alert messages per guild (#2600) 2019-04-23 02:57:46 -08:00
Michael H
012d99c05c [Utils] Better error handling for humanize_list (#2597) 2019-04-22 22:34:38 -04:00
jack1142
2c8a425f87 [V3 Downloader] Uninstall multiple cogs (#2592)
* feat(downloader): Uninstall multiple cogs

* refactor(downloader): Put everything in one message
2019-04-22 20:17:30 -04:00
Flame442
8555f8c28c [Downloader] Pretties up the output when libraries fail to install (#2576)
* Pretties up the output when libraries fail to install

* Stupid double quote bullshit

* Added jack1142's suggestion

* I will never satisfy the eldritch being named black
2019-04-22 20:15:26 -04:00
jack1142
46413c2c52 [V3 Downloader] Stop including subpackages in cog list (#2590)
Before this change, `Repo.available_modules` would also contain subpackages, which is unintended
2019-04-22 20:07:52 -04:00
TrustyJAID
eaeaf9dd69 [V3 Streams] Change twitch and youtube top level commands (#2479) 2019-04-22 20:06:50 -04:00
zephyrkul
ee11d7da63 [V3 Audio] Cancel emptydisconnect when no longer alone (#2519)
* [audio] cancel disconnect when no longer alone

* [audio] fix modifying while iterating
2019-04-22 20:05:37 -04:00
NIXC
0ac93aacd5 [V3 Economy] Add default cooldown to slots (#2561)
* Add default cooldown to slots

To prevent abuse

* slight boost in time

1 second actually doesn't help, needs a bit more.
2019-04-22 19:44:05 -04:00
TrustyJAID
691d8af26d [V3 Warnings] Utilize modlog cases (#2471)
* [V3 Warnings] Utilize modlog cases

This update utilizes modlog cases for warnings and slightly improves usage of custom warnings if enabled.

* remove BadArgument error response

* Utilize Optional and consume-rest for points and reason

* black format

* Remove unnecessary imports, cleanup error handling, and improve docstrings
2019-04-22 19:34:36 -04:00
TrustyJAID
87c66b2423 [V3 Admin] Fix errors when hierarchy issues are raised (#2498)
* Fix addrole mentioning who we're trying to add the role to

* More things are broken, huh

* formatting...
2019-04-22 19:25:44 -04:00
aikaterna
005123a371 [Audio] Fix for playlist queue when not playing (#2586) 2019-04-22 19:17:44 -04:00
aikaterna
bb8ce43cc0 [Audio] Track search and append fixes (#2591)
* [Audio] Track search and append fixes

* Appeasing the style gods
2019-04-22 19:17:17 -04:00
palmtree5
13611e34d2 Fix extras in launcher (#2588)
Now non-existent voice extra was still hanging around in the launcher while the style extra was never added in
2019-04-22 14:46:07 -08:00
Neuro Assassin
b8190c44a8 [Dev] Sanitize API tokens in debug and eval (#2585)
* Sanitize API tokens in debug and eval

* Not sure what happened there
2019-04-22 18:43:02 -04:00
Will
95d5ec5f0e [Setup] Fix the mongo name to create instances using new driver (#2594)
* oops

* damn you
2019-04-22 18:33:52 -04:00
zephyrkul
874204bf18 [mongo setup] utilize getpass (#2593) 2019-04-21 07:04:08 -08:00
Will
6c296a9a17 [V3 Setup] Overhaul backend conversion process through setup scripts (#2579)
* swap to click for setup

* Initial changes

* expose some stuff to allow for per-driver optimizations

* overwrite base config

* add red log

* add one print juuuust in case

* fix this

* thanks kowlin

* damn

* oops

* fix thing

* partial commit

* Working mongo -> json conversion, it sucks tho

* remove unused line

* Wrote initial optimized json importer

* optimized json importer

* remove useless line

* update mongo to json converter

* lets try writing the correct entry

* oops

* style fix

* add some garbage data filters going from old mongo to json

* ignore garbage data in mongov2 conversions

* simplify code a bit and add a completion message

* missed one

* Update pipfile lock

* Lock click version
2019-04-20 20:10:44 -04:00
PredaaA
ad06b0e723 [Audio] Fix issue on audiostats command when more than 20 servers to display (#2533)
* Update audio.py

* Fix of pages counter.
2019-04-18 12:12:16 -04:00
palmtree5
62f15e52a0 Merge remote-tracking branch 'release/V3/develop' into V3/develop 2019-04-16 18:32:38 -08:00
Will
0652dd344b [V3 Mongo] Correct dictionary rebuilding process for global all case (#2581) 2019-04-13 15:51:49 -04:00
Will
8b3c3e89e9 [V3 Mongo] Fix all behavior (#2580) 2019-04-13 15:24:50 -04:00
Will
c82ac5ae68 Add some errors for backend conversions and only allow MongoV2 creation (#2570)
* Add some errors for conversions and only allow mongoV2 creation

* Add another message

* Fixed message to be more clear
2019-04-10 20:42:28 -04:00
jack1142
2776db0cf9 [V3 Core] Print actual version, when version flag is used (#2567) 2019-04-10 19:32:20 -04:00
Will
ba19179e4f [V3 Config] Record custom group information using cog_add event (#2550)
* Do things differently

* Uncomment critical lines

* Reduce, reuse, recycle

* Check groups on all new config objects after a cog loads

* I don't know why this is failing now or why we need the global keyword

* gotta fix this too
2019-04-09 22:02:50 -04:00
jack1142
e347ffa336 Bot can join voice channel with user limit if it has move members perm (#2525) 2019-04-09 20:33:19 -04:00
kennnyshiwa
c85af62401 Fix message when user hits max credits (#2563)
* Fix message when user hits max credits

Fixes the error message when a users issues the payday command when having max credits

* Update economy.py

Changed message when user hits max payday and bank is global to match message when bank is per server

* Update economy.py

made statements match
2019-04-09 18:54:44 -04:00
aikaterna
39b64b7570 [Audio] Fix for localtrack playing (#2557) 2019-04-09 17:13:57 -04:00
aikaterna
56b220b92e [Audio] Fix for prev command display (#2556) 2019-04-09 17:10:25 -04:00
jack1142
972fbecc94 [V3 Trivia] Make sure that test will use utf-8 encoding (#2565) 2019-04-09 17:03:34 -04:00
Michael H
136e781c7f Kill DataConverter (#2554)
* Kill DataConverter

* remove the tests
2019-04-09 17:01:04 -04:00
Will
0852d1be9f [V3 Config] Require custom group initialization before usage (#2545)
* Require custom group initialization before usage and write that data to disk

* Style

* add tests

* remove custom info update method from drivers

* clean up remnant

* Turn config objects into a singleton to deal with custom group identifiers

* Fix dumbassery

* Stupid stupid stupid
2019-04-04 21:47:08 -04:00
Will
fb722c79be [V3 ModLog] Change register_casetypes behavior (#2551)
* Ignore runtime error in register_casetypes

* Fix documentation
2019-04-04 18:59:14 -04:00
jack1142
c63d069f69 [Trivia] Fix of dead image link (world flags) (#2540)
* New image link for Sao Tome and Principe

Current link is dead

* Someone didn't pay attention on geography lessons
2019-04-03 11:40:20 -04:00
Will
1cd7e41f33 [V3 Config] Update Mongo document organization to bypass doc size restriction (#2536)
* modify config to use identifier data class and update json driver

* move identifier data attributes into read only properties

* Update mongo get and set methods

* Update get/set to use UUID separately, make clear work

* Remove not implemented and fix get_raw

* Update remaining untouched get/set/clear

* Fix get_raw

* Finally fix get_raw and set_raw

* style

* This is better

* Sorry guys

* Update get behavior to handle "all" calls as expected

* style again

* Why do you do this to me

* style once more

* Update mongo schema
2019-04-03 09:04:47 -04:00
zephyrkul
d6d6d14977 [V3 Alias] Customize Parameters (#2455)
* [alias] custom parameters

Signed-off-by: zephyrkul <zephyrkul@users.noreply.github.com>

* [alias] quoted words remain quoted

Signed-off-by: zephyrkul <zephyrkul@users.noreply.github.com>

* [alias] fix no-parameter aliases

Signed-off-by: zephyrkul <zephyrkul@users.noreply.github.com>

* [alias] remove unneeded error dispatch

it was expensive and did nothing anyway from my own testing
2019-04-02 23:08:28 -04:00
PredaaA
82cda4b57a Delete cooldown messages when expired (#2469) 2019-04-02 23:06:30 -04:00
Toby Harradine
301c800319 Logging enhancements and cleanup (#2502)
* Logging enhancements and cleanup

- Removed debug log messages every time `Config.get_conf` is used or a JSON file is read/saved. The basic configuration is now logged once with DEBUG when the bot starts up instead.
- Changed logging output format to reverse date order, include seconds, and use the logger's name instead of the module, function and line number.
- Log files are now kept in the `DATAPATH/core/logs` directory. Each time Red is restarted, a new log is created, and the old ones renamed in a rotating fashion. There can be a maximum of 9 logs in total.
- Each log file now has a smaller max size of 500KB before it will be split into multiple parts. There are also a maximum of 9 parts of each log.
- Discord.py logger now uses the same output formatter as red's loggers
- Moved logging setup code into `redbot.logging` module.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Reformat

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Implement discussed changes

- We maintain a red.log over multiple runtimes, alongside a latest.log and previous.log for individual runtimes.
- Naming convention changed a bit. E.g. when latest.log is just one part, it will be named latest.log. When it becomes two parts, they will both be named latest-part1.log and latest-part2.log.
- Rotation direction is reversed. This means as the files end up being named in chronological order.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-04-02 22:58:34 -04:00
DiscordLiz
30af83aa6a [Permissions] Allow for multiple IDs in permissions rule commands. (#2448)
Use `commands.Greedy` to apply multiple rules at once.

closes #2214
2019-04-02 22:53:07 -04:00
jack1142
0f9501f93a Tunnel - Send message after close (#2507) 2019-04-02 22:50:01 -04:00
Michael H
466b2b82d0 Fix localwhitelist/localblacklist add/remove (#2531) 2019-04-02 22:46:12 -04:00
Michael H
8ab39512d9 [Filter] Performance increases. (#2509)
* [Filter] Performance increases.

The filter was already using re to split words, this just does the entire search in re instead.

A further improvement to this would cache patterns used and update them if the wordlist changes.

* Add a pattern cache

* exit on no-words

* formatting pass

* keep the return type consistent, even though this doesnt break core since this is available to cogs

* ...

* Quit being an idiot

* Slight further improvements, a fix, and restructure

Moved actual set creation out of the inner portion.
Reduced config lookups in case of no filter.
Fixed channel wordlist fetching.

* I really should go back to using a pre-commit hook for the style stuff
2019-04-02 22:42:13 -04:00
jack1142
e08e95c04e [V3 Downloader] Tell user how to load the cog after [p]cog install (#2523)
* tell user how to load the cog after install

* use code block
2019-04-02 22:35:21 -04:00
FixedThink
e7b1fa5ab5 [V3 Trivia] Update "World" trivias to reflect legislative changes (#2526)
* Update worldmap.yaml

* Update worldflags.yaml

* Update worldcapitals.yaml
2019-04-02 22:33:40 -04:00
Flame442
14a2f98418 [Docs] Adds self recommendation to cog_data_path (#2539)
This change adds the help text of "If calling from a command or method of your cog, this should be self." from `bundled_data_path` to `cog_data_path`. This bit of text can help people who are unsure of what a "cog instance" is to understand how to use `cog_data_path`.
2019-04-02 22:24:20 -04:00
aikaterna
de7d08ee75 [Audio] Match v2 behavior for channel change (#2521)
* [Audio] Match v2 behavior for channel change

* Use move_to instead of connect
2019-04-02 22:22:57 -04:00
aikaterna
2a486cad66 [V3 Audio] Playlist info improvements (#2274)
* [V3 Audio] Playlist info improvements

* Add pagify import, reformat for Black

* Change from code block to embed with links
2019-04-02 21:48:21 -04:00
aikaterna
80fc639480 [V3 Audio] Queue clean and queue clear addition (#2476)
* [V3 Audio] Queue clean and queue clear addition

* Use DJ role and existing checks inst. of mod/admin

* Remove unneeded .format()
2019-04-02 21:12:55 -04:00
Michael H
c7608aeb17 [V3 Mod] Use a composite class for mod (#2501)
* [V3 Mod] Use a composite class for mod

This turns mod.py into acomposite class

I've split things in this up based on purpose, including `movetocore`

`movetocore` is a set of things which likely belong in the core bot and
not relying on mod being loaded.

This is part of #2500

Per discussion in discord, this should be the first thing in #2500
merged

* Move this back,
mod was importable,
and this was intended as non-breaking

* Prevent fix from being lost if merged before this.

see Cog-Creators/Red-DiscordBot#2510

* Move case creation to before sending

see #2515

* fix failed merge done in web
2019-04-01 03:34:27 -08:00
aikaterna
050300040c [V3 Audio] Add Spotify support (#2328)
* [V3 Audio] Add Spotify support

* [V3 Audio] Update LICENSE

* Appeasing the style gods

* Extra word removal on LICENSE

* Update for #2389

Thanks to TrustyJAID for the help.

* Playlist command support for Spotify URLs or codes

* Add exception for dc while loading Spotify tracks

* Allow Spotify urls by default in audioset restrict

Matches the behavior of Spotify codes already being allowed by default.

* Update audio.py

* .format() moving

* Added a character to try to make Travis behave
2019-03-28 13:41:17 -04:00
aikaterna
94c3a2fedd [Audio] Seek command can now seek to position (#2470)
- Seek can now seek to a specific position, formatted like 00:00:00 or 00:00. Using negative or positive ints still functions the same as previously and will seek ahead or behind by that value instead.
- This PR requires the `_time_convert` func added in #2465 and should be merged after that one.
2019-03-10 18:27:23 +11:00
aikaterna
421043d923 [Audio] Fix for audioset status (#2481)
Revert player.is_playing check added to the playing players list for audioset status in #2473. This addition would cause no status to be shown when a local track was played and skip was used.
2019-03-08 09:09:22 +11:00
aikaterna
1c22b212c2 [Audio] Add songs when search-queuing (#2513)
Searching for a song and pressing the reaction to queue a song would not add the song to the queue if `[p]audioset maxlength` was off. This was an omission from #2465.
2019-03-07 14:12:15 +11:00
Flame442
d52b3eaf21 Proper capitalization of [p]blacklist help text (#2511) 2019-03-07 08:57:06 +11:00
Flame442
30ca226e39 [Trivia] Fix typo in warcraft trivia (#2508)
teh -> the
2019-03-06 20:04:11 +11:00
aikaterna
15037013e7 [Audio] Add option for dc at queue end (#2472)
This addition adds a toggle for having the bot instantly disconnect when the queue ends. It takes precedence over the `[p]audioset emptydisconnect` setting as it disconnects immediately when the queue or single song is finished.
2019-03-04 14:16:26 +11:00
jack1142
30fa9303e8 Spelling correction of method name in Tunnel (#2496)
`Tunnel.files_from_attatch` was misspelled.

Kept old name as an alias for backwards compatibility.
2019-03-04 12:19:40 +11:00
Toby Harradine
b4753a02de Fix translations of multiline strings (#2504)
* Fix translations of multiline strings

Resolves #2408.

Also did a few little optimisations here and there, we're no longer just using copied code from another project.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>

* Reformat

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-03-04 00:45:15 +01:00
Toby Harradine
628073cbe1 Update Translations (#2486)
Also included a Makefile recipe which makes use of the Crowdin CLI's `crowdin download` command. This requires whoever is using it to provide the project's API key in an environment variable, but we may automate this at some point.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-03-04 08:55:01 +11:00
aikaterna
2755592175 [Audio] Playlist download addition (#2482)
Bot owners can use `[p]playlist download playlistname` to download a playlist file that is compatible with the playlist upload command. Songs can also be exported for v2 use by using `[p]playlist download playlistname True`, which strips out tracks besides YouTube or Soundcloud URLs.
2019-03-03 14:29:42 +11:00
entchen66
6051ccb23c [Filter] Dispatch event on_filter_message_delete (#2483) 2019-02-27 16:27:10 +11:00
El Laggron
46f9cae0ef [Audio] Sort local files (#2358) 2019-02-27 16:22:10 +11:00
Seputaes
16614168a7 [Downloader] Install SHARED_LIBRARY requirements (#2384)
SHARED_LIBRARY Installable types did not have the requirements as
defined in info.json automatically installed. This change updates the
installation of libraries to also install their requirements.

Resolves #2381
2019-02-27 16:07:05 +11:00
El Laggron
5a15939f08 Bump aiohttp-json-rpc to version 0.12.1 (#2489) 2019-02-27 09:55:20 +11:00
Toby Harradine
bb5aab16c9 [Permissions] Exclude @everyone role when retrieving rules (#2484)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-25 10:19:00 +11:00
Toby Harradine
b38ac1d025 Bump version to 3.0.2
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-25 08:54:50 +11:00
Toby Harradine
22cf8e940c [Permissions] Fix typo in cog_add event 2019-02-25 08:27:49 +11:00
aikaterna
b0ab6bd7e2 [Audio] Add track length restriction (#2465)
* Added `[p]audioset maxlength`, which takes seconds or 00:00-style of formatted input for restricting the player to songs that have a length under that threshold.
2019-02-24 11:44:58 +11:00
aikaterna
7b9d85c1b5 [Audio] Emptydisconnect and status refactor (#2473)
- Refactored disconnect_timer, the function behind audioset emptydisconnect, to be more appropriately responsive (thanks to TrustyJAID)
- Refactored status clearing/status changing when emptydisconnect or other Lavalink player statuses would have a TRACK_END event but no QUEUE_END event. This should clear or modify the bot's status event appropriately when [p]audioset status is on and the bot disconnects due to emptydisconnect and ideally play a little nicer with other cogs that set statuses.
2019-02-23 08:45:01 +11:00
DiscordLiz
3637804929 Fix Command.error decorator (#2478)
This shouldn't be a coroutine.
2019-02-23 07:13:22 +11:00
DiscordLiz
c70a44e0fe Make on_command_error recognise custom error handlers (#2458)
Adds detection of custom error handlers to the global error handler.

Adds documentation of the updated behaviour.

Closes #2276.
2019-02-22 20:12:00 +11:00
Toby Harradine
cf18b601e2 [Mod] Use consume-rest in [p]casesfor 2019-02-22 17:52:30 +11:00
Lionir
139cc07bda Resolve network issue due to certificate failure (#2442)
There seems to have been a recurring problem with certificates on Macs, this is how you solve it *apparently*. 😎
2019-02-22 06:24:39 +01:00
DiscordLiz
619c3f28f7 fix case generation for cases which were not logged to modlog channel (#2477)
Add documentation for failure cases
Prevent an expected failure case.
2019-02-22 03:33:45 +01:00
DiscordLiz
9966668307 adds function to modlg API to get cases by member (#2453)
adds command using this to view all of a member's cases demonstrating this

closes #2266
2019-02-22 03:31:37 +01:00
palmtree5
77a0a67029 [Streams] Remove communities support (#2223)
See [this blog post](https://blog.twitch.tv/introducing-tags-and-new-categories-33744ef7b04f), communities on Twitch have been discontinued.
2019-02-21 17:37:28 +11:00
ZeLarpMaster
b65466cebd [Trivia] Fix typo in cars.yaml (#2475) 2019-02-21 09:49:43 +11:00
Toby Harradine
f1873e32d6 [Docs] Use correct perms cmd for setting default rules 2019-02-20 16:08:25 +11:00
Michael H
b7b4e65d2d [Permissions] Remove p alias (#2467)
People can re-add it with the alias cog, but core red should not monopolize short aliases.

What about those servers that want `[p]play` even shorter?
2019-02-19 10:43:02 +11:00
aikaterna
16bb334fba [Audio] Add playlist copy (#2205)
Bot owners can now copy playlists from one server to another using server IDs.
2019-02-19 10:36:20 +11:00
TrustyJAID
3f1d416526 Allow central storage of API keys (#2389)
This creates a central location to store external API tokens that can be used between cogs without requiring each cog to be loaded for it to work.

A new set option for `[p]set api` is created to assist in forming bot readable API token locations.

This also updates the Streams cog to utilize the central database.

Tokens are moved from the old data locations in core cogs on load.
2019-02-19 10:22:44 +11:00
Michael H
722aaa225b Improve Cooldown UX (#2412)
This improves the feedback given on `command_on_cool_down`, as well as automatically handling cooldowns under 1 second for the user.
2019-02-19 10:19:49 +11:00
aikaterna
7e2e37ab3f [Audio] Play local folders via text command (#2457)
`[p]local folder` will now accept folder names in the command instead of having to navigate through the reaction menu. Also added an alias of `[p]local start` to help users coming from v2 audio.
2019-02-19 10:16:41 +11:00
aikaterna
83411d0fa4 [Audio] Change pause to a toggle (#2461) 2019-02-19 10:10:11 +11:00
aikaterna
d608dd953b [Audio] Remove aliases (#2462) 2019-02-19 10:08:59 +11:00
Flame442
e5e0a024f9 Remove unnecessary "or" from Config doc (#2464) 2019-02-17 14:14:40 -09:00
Caleb Johnson
8e6db0829c [Audio] Connect to lavalink in the background (#2460)
Also:
- restart and reconnect if connection settings change
  - shutdown and restart if not configured to use external
- show a message in [p]play et al. when the connection hasn't been made
- move the JAR download to manager so audio.py can access it
- only start if no process exists
- bump red-lavalink to 0.2.3

Resolves #2306
2019-02-17 09:22:55 +11:00
Toby Harradine
5359fec195 Bump version to 3.0.1
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-16 12:21:25 +11:00
Toby Harradine
f2daf0be9a Revert "[Audio] Connect to lavalink in the background (#2335)" (#2459)
This reverts commit b633a33137.
2019-02-16 11:52:03 +11:00
Caleb Johnson
b633a33137 [Audio] Connect to lavalink in the background (#2335)
Also:
- restart and reconnect if connection settings change
  - shutdown and restart if not configured to use external
- show a message in [p]play et al. when the connection hasn't been made
- move the JAR download to manager so audio.py can access it
- only start if no process exists

Resolves #2306
2019-02-16 11:35:21 +11:00
Michael H
d13bf37845 [Utils] Add filters for spoiler markdown (#2401)
This also wraps some fields of the modlog with the same sanitization, as well as the `[p]names` command.
2019-02-16 11:34:38 +11:00
Michael H
4b831a634a [Audio] Remove players which no longer have a guild. (#2414)
Cleanup players when the bot has one for a guild it leaves.

Bumps Red-Lavalink to v0.2.2
2019-02-16 11:15:33 +11:00
DiscordLiz
f91e0a6546 Prevent error when ctx.send_interactive prompt is deleted (#2447)
Supress excpetions which may occur when attempting to delete a prompt.

fixes #2380
2019-02-16 07:46:36 +11:00
ZeLarpMaster
2e2d669fdf [Trivia] Fix typo in cars.yaml (#2456) 2019-02-16 07:39:49 +11:00
Toby Harradine
7ecdf7a7be Remove asyncio.Event creation from module level in Audio (#2454)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-15 14:27:01 +11:00
Twentysix
9b940de854 [Downloader] [p]pipinstall: Handle no args 2019-02-14 23:39:38 +01:00
zephyrkul
82807ffe69 [Downloader] Use shlex for subprocesses (#2421) 2019-02-14 23:32:11 +01:00
Toby Harradine
b1066ad58f Guard module-level creation of Config objects (#2449)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-14 15:36:48 +11:00
Caleb Johnson
ac8b1fc108 [Filter] Filter based on words for non-phrases (#2262)
Filters based strictly on words (ignoring punctuation) if filter entry isn't a phrase.
2019-02-13 14:14:12 +11:00
zephyrkul
820be2a0ae [Core] Utilize consume rest, Union (#2407) 2019-02-13 14:11:22 +11:00
Toby Harradine
9869f95bd6 Update dependencies and copyright year (#2436)
- aiohttp 3.5
- websockets 7
- Rapptz/discord.py@700dbb5
- A few others

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-13 10:49:11 +11:00
zephyrkul
c87286d3c6 [README] Update support channel (#2445) 2019-02-12 21:24:14 +01:00
Twentysix
7028ca9df3 [Docs] Link bot account guide (#2440) 2019-02-11 20:17:39 +01:00
Toby Harradine
435fc141ae Default rules for subcommands precede supercommands (#2422)
This incorporates default rules into the same resolution techniques used by concrete rules.

Resolves #2313.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-11 14:14:29 +11:00
Twentysix
889fa63aff Sentry removal (#2439)
Resolves #2430.
2019-02-11 11:19:02 +11:00
Toby Harradine
dae75521d3 [Audio] Enable logging on Lavalink V3 (#2438)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-10 18:36:02 +11:00
Toby Harradine
b9d440f2f7 Utilise setup.cfg, move version info to redbot package (#2411)
* Utilise setup.cfg, move version info to redbot package

- `redbot.__init__` now is safe to import without installing dependencies.
- Now deploying binary wheel distribution from travis
- Include locale files in sub-packages of cog packages
- python_requires now has no upper limit

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-09 12:08:22 +11:00
Toby Harradine
ec4c325efd Guard parsing of CLI args in launcher, setup scripts (#2432)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-08 18:08:10 +11:00
Twentysix
b350ac38dc Allow [p]info customization (#2417)
* Allow [p]info customization

* Style

* Naming
2019-02-08 02:38:26 +01:00
PredaaA
e88c82e7e0 Update help_formatter.py (#2431) 2019-02-08 02:24:59 +01:00
Twentysix
99ad01ae0d [p]userinfo: Handle target w/ 'None' Member.joined_at (#2426) 2019-02-07 22:40:40 +01:00
Toby Harradine
8f8c52d8c4 [Docs] Add note about pyenv optimisation flag (#2428)
Signed-off-by: Toby <tobyharradine@gmail.com>
2019-02-07 21:54:14 +11:00
Toby Harradine
c56fa5a320 Minor changes to install docs (#2427)
- When creating a venv, use `python3.7` instead of `python3`
- Remove unnecessary dependency from pyenv pre-requirements on Debian
- Use curl over wget for get-pip on Xenial

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-07 10:51:17 +11:00
Toby Harradine
7d5bae5a50 Use V2 of RTD's configuration file (#2418)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-07 10:30:01 +11:00
bobloy
7c404082f8 [Help] Group Fields respect page_char_limit (#2281)
It would groups the cogs until it is **greater** than the set `page_char_limit` in helpset. This leads to inconsistent page sizes when a large cog was appended to something barely under the limit.

I think this commit will reign in the weirdness by adjusting the secondary grouping to aim for **less than** `page_char_limit` grouping.
2019-02-06 18:59:54 +11:00
Toby Harradine
dc8e61cbe5 Always tick Voice requirements on startup screen (#2413)
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-02-04 22:33:48 +11:00
zephyrkul
f2ebf52f6e Send help on empty [p]load/unload/reload (#2410)
Rather than attempting to load / reload / unload nothing, send command help on bare `[p]load`, etc. commands.
2019-02-04 17:39:03 +11:00
Redjumpman
fa223e22ed [Utils] Fix for MessagePredicate.lower_contained_in (#2399)
Added a missing str.lower() method when checking to see if the content is in the list.
2019-02-04 10:22:01 +11:00
PredaaA
6d22c8faa5 [Core cmds] [p]servers: handle message deletion (#2400) 2019-02-03 23:32:45 +01:00
Michael H
01ebf2835b Improve usability of warnings/unwarn\n resolves #2403 (#2404) 2019-02-03 02:37:01 -09:00
Michael H
3ef693a259 prevent traceback (#2406)
* prevent traceback related to  Rapptz/discord.py#1638

* formatting
2019-02-02 11:20:17 +01:00
Caleb Johnson
3a4d932d2b Use find_namespace_packages in setup.py (#2402) 2019-02-01 16:10:10 +11:00
Kowlin
571332ae18 Fixed our missing templates (#2398) 2019-01-31 10:36:14 +11:00
Toby Harradine
0607f5552a Use python-Levenshtein-wheels (#2393)
This removes the compiler detection logic in setup.py. python-Levenshtein-wheels includes pre-built wheels for virtually all operating systems and architectures we support.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2019-01-30 16:52:36 +11:00
Toby Harradine
016a6d3aa6 Bump minimum python version to 3.7.0 (#2394)
This is in anticipation of #2246, although I've written that PR to not break on 3.6, the feature itself is not usable on 3.6. So I think the best way forward is to simply require python 3.7. This also allows devs and cog creators to utilise all of the new features in 3.7, and it also updates the docs so all operating systems will have 3.7 installed.

Signed-off-by: Toby <tobyharradine@gmail.com>
2019-01-30 14:07:53 +11:00
Toby Harradine
c7d98f88e8 Update Travis badge in README to travis-ci.com (#2395)
Signed-off-by: Toby <tobyharradine@gmail.com>
2019-01-29 01:46:37 -09:00
Toby Harradine
b82756087a Merge V3/release/3.0.0 into V3/develop 2019-01-28 15:30:30 +11:00
El Laggron
3b62572c89 Graceful shutdown when SIGTERM is received (#2286)
Only works on Unix.
2019-01-23 10:28:30 +11:00
bobloy
abcf179042 [CogManager] Fix return type-hint (#2319) 2019-01-19 11:47:58 +11:00
Michael H
3dba09d19d [Docs] Chocolately/PowerShell install instructions for Windows (#2364) 2019-01-19 11:45:22 +11:00
Toby Harradine
e07408161a Merge branch 'V3/release/3.0.0' into V3/develop
# Conflicts:
#	redbot/cogs/mod/mod.py
2019-01-11 16:42:42 +11:00
Twentysix
937d2fe0f6 [Mod] Enhanced [p]hackban (#2164) 2019-01-05 03:29:05 +01:00
palmtree5
bcc50557a9 [V3 Readme] Update cog list links (#2347) 2018-12-23 00:17:20 -09:00
Toby Harradine
bdcb69ad37 Merge branch 'V3/release/3.0.0' into V3/develop
# Conflicts:
#	redbot/cogs/audio/audio.py
2018-12-21 13:37:32 +11:00
aikaterna
3b50ed8192 [V3 Audio] Restrict toggle for commercial sites (#2245)
* [V3 Audio] Restrict toggle for commercial sites

* Different url parsing

* Allow local tracks

* No self needed

* Change Twitch url
2018-12-13 18:31:24 +01:00
Michael H
30c3a4c7c1 [Docs] CentOS correct git version (#2309)
Resolves #2260.
2018-11-24 10:06:59 +11:00
FixedThink
6470bc1cda [Mod] [p]userinfo: Get avatar format with proper method (#2291) 2018-11-14 22:46:01 +01:00
Michael H
221b636f3f Prevent locking out owner from commands (#2257) 2018-11-06 11:32:40 +11:00
FixedThink
f7e41063bf [Trivia] Change Swaziland to its new name, eSwatini (#2275) 2018-11-06 09:52:25 +11:00
bobloy
d6cd959a2b [Docs] Update example cog to include base cog class (#2256) 2018-11-06 08:41:34 +11:00
aikaterna
57f078925e [V3] Update CODEOWNERS for audio (#2277) 2018-10-30 18:19:16 +01:00
Toby Harradine
8b4e12da81 Merge branch 'V3/release/3.0.0' into V3/develop
# Conflicts:
#	redbot/cogs/customcom/customcom.py
#	redbot/core/errors.py
2018-10-16 09:42:38 +11:00
Toby Harradine
5ed8be9998 Merge V3/release/3.0.0 into V3/develop 2018-10-12 08:59:14 +11:00
Michael H
4357fe1ba9 [Core] Fixes reload, and prompted reload on cog update (#2226)
Fix something missed when QAing #2207
2018-10-12 07:23:04 +11:00
El Laggron
a64db76b4d [Core] Specify an error message on package loading (#2207)
Allows cog creators to explain clearly why a cog cannot load by raising  `redbot.core.errors.CogLoadError`. Instead of having to check in the console what's wrong, the message will directly be sent in the context channel.
2018-10-11 11:57:11 +11:00
Twentysix
c464f5e7dc [CustomCommands] Add [p]cc show (#2204) 2018-10-10 10:27:25 +11:00
Toby Harradine
00bc3c86b1 Merge V3/release/3.0.0 into V3/develop (#2209) 2018-10-09 09:10:05 +11:00
Toby Harradine
ba605495ac [Dev] Use isawaitable over iscoroutine for [p]debug (#2202)
Allows for non-coroutine awaitables (such as config's `_ValueCtxManager`) to be awaited from debug.

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
2018-10-07 19:36:35 +11:00
Toby Harradine
748847d5bf Merge changes for RC1 into V3/develop 2018-10-07 12:06:28 +11:00
1165 changed files with 321247 additions and 54707 deletions

396
.bandit.yml Normal file
View File

@@ -0,0 +1,396 @@
### Bandit config file generated
### This config may optionally select a subset of tests to run or skip by
### filling out the 'tests' and 'skips' lists given below. If no tests are
### specified for inclusion then it is assumed all tests are desired. The skips
### set will remove specific tests from the include set. This can be controlled
### using the -t/-s CLI options. Note that the same test ID should not appear
### in both 'tests' and 'skips', this would be nonsensical and is detected by
### Bandit at runtime.
# Available tests:
# B101 : assert_used
# B102 : exec_used
# B103 : set_bad_file_permissions
# B104 : hardcoded_bind_all_interfaces
# B105 : hardcoded_password_string
# B106 : hardcoded_password_funcarg
# B107 : hardcoded_password_default
# B108 : hardcoded_tmp_directory
# B110 : try_except_pass
# B112 : try_except_continue
# B201 : flask_debug_true
# B301 : pickle
# B302 : marshal
# B303 : md5
# B304 : ciphers
# B305 : cipher_modes
# B306 : mktemp_q
# B307 : eval
# B308 : mark_safe
# B309 : httpsconnection
# B310 : urllib_urlopen
# B311 : random
# B312 : telnetlib
# B313 : xml_bad_cElementTree
# B314 : xml_bad_ElementTree
# B315 : xml_bad_expatreader
# B316 : xml_bad_expatbuilder
# B317 : xml_bad_sax
# B318 : xml_bad_minidom
# B319 : xml_bad_pulldom
# B320 : xml_bad_etree
# B321 : ftplib
# B322 : input
# B323 : unverified_context
# B324 : hashlib_new_insecure_functions
# B325 : tempnam
# B401 : import_telnetlib
# B402 : import_ftplib
# B403 : import_pickle
# B404 : import_subprocess
# B405 : import_xml_etree
# B406 : import_xml_sax
# B407 : import_xml_expat
# B408 : import_xml_minidom
# B409 : import_xml_pulldom
# B410 : import_lxml
# B411 : import_xmlrpclib
# B412 : import_httpoxy
# B413 : import_pycrypto
# B501 : request_with_no_cert_validation
# B502 : ssl_with_bad_version
# B503 : ssl_with_bad_defaults
# B504 : ssl_with_no_version
# B505 : weak_cryptographic_key
# B506 : yaml_load
# B507 : ssh_no_host_key_verification
# B601 : paramiko_calls
# B602 : subprocess_popen_with_shell_equals_true
# B603 : subprocess_without_shell_equals_true
# B604 : any_other_function_with_shell_equals_true
# B605 : start_process_with_a_shell
# B606 : start_process_with_no_shell
# B607 : start_process_with_partial_path
# B608 : hardcoded_sql_expressions
# B609 : linux_commands_wildcard_injection
# B610 : django_extra_used
# B611 : django_rawsql_used
# B701 : jinja2_autoescape_false
# B702 : use_of_mako_templates
# B703 : django_mark_safe
# (optional) list included test IDs here, eg '[B101, B406]':
tests:
# (optional) list skipped test IDs here, eg '[B101, B406]':
skips: ['B322']
### (optional) plugin settings - some test plugins require configuration data
### that may be given here, per-plugin. All bandit test plugins have a built in
### set of sensible defaults and these will be used if no configuration is
### provided. It is not necessary to provide settings for every (or any) plugin
### if the defaults are acceptable.
any_other_function_with_shell_equals_true:
no_shell:
- os.execl
- os.execle
- os.execlp
- os.execlpe
- os.execv
- os.execve
- os.execvp
- os.execvpe
- os.spawnl
- os.spawnle
- os.spawnlp
- os.spawnlpe
- os.spawnv
- os.spawnve
- os.spawnvp
- os.spawnvpe
- os.startfile
shell:
- os.system
- os.popen
- os.popen2
- os.popen3
- os.popen4
- popen2.popen2
- popen2.popen3
- popen2.popen4
- popen2.Popen3
- popen2.Popen4
- commands.getoutput
- commands.getstatusoutput
subprocess:
- subprocess.Popen
- subprocess.call
- subprocess.check_call
- subprocess.check_output
- subprocess.run
hardcoded_tmp_directory:
tmp_dirs:
- /tmp
- /var/tmp
- /dev/shm
linux_commands_wildcard_injection:
no_shell:
- os.execl
- os.execle
- os.execlp
- os.execlpe
- os.execv
- os.execve
- os.execvp
- os.execvpe
- os.spawnl
- os.spawnle
- os.spawnlp
- os.spawnlpe
- os.spawnv
- os.spawnve
- os.spawnvp
- os.spawnvpe
- os.startfile
shell:
- os.system
- os.popen
- os.popen2
- os.popen3
- os.popen4
- popen2.popen2
- popen2.popen3
- popen2.popen4
- popen2.Popen3
- popen2.Popen4
- commands.getoutput
- commands.getstatusoutput
subprocess:
- subprocess.Popen
- subprocess.call
- subprocess.check_call
- subprocess.check_output
- subprocess.run
ssl_with_bad_defaults:
bad_protocol_versions:
- PROTOCOL_SSLv2
- SSLv2_METHOD
- SSLv23_METHOD
- PROTOCOL_SSLv3
- PROTOCOL_TLSv1
- SSLv3_METHOD
- TLSv1_METHOD
ssl_with_bad_version:
bad_protocol_versions:
- PROTOCOL_SSLv2
- SSLv2_METHOD
- SSLv23_METHOD
- PROTOCOL_SSLv3
- PROTOCOL_TLSv1
- SSLv3_METHOD
- TLSv1_METHOD
start_process_with_a_shell:
no_shell:
- os.execl
- os.execle
- os.execlp
- os.execlpe
- os.execv
- os.execve
- os.execvp
- os.execvpe
- os.spawnl
- os.spawnle
- os.spawnlp
- os.spawnlpe
- os.spawnv
- os.spawnve
- os.spawnvp
- os.spawnvpe
- os.startfile
shell:
- os.system
- os.popen
- os.popen2
- os.popen3
- os.popen4
- popen2.popen2
- popen2.popen3
- popen2.popen4
- popen2.Popen3
- popen2.Popen4
- commands.getoutput
- commands.getstatusoutput
subprocess:
- subprocess.Popen
- subprocess.call
- subprocess.check_call
- subprocess.check_output
- subprocess.run
start_process_with_no_shell:
no_shell:
- os.execl
- os.execle
- os.execlp
- os.execlpe
- os.execv
- os.execve
- os.execvp
- os.execvpe
- os.spawnl
- os.spawnle
- os.spawnlp
- os.spawnlpe
- os.spawnv
- os.spawnve
- os.spawnvp
- os.spawnvpe
- os.startfile
shell:
- os.system
- os.popen
- os.popen2
- os.popen3
- os.popen4
- popen2.popen2
- popen2.popen3
- popen2.popen4
- popen2.Popen3
- popen2.Popen4
- commands.getoutput
- commands.getstatusoutput
subprocess:
- subprocess.Popen
- subprocess.call
- subprocess.check_call
- subprocess.check_output
- subprocess.run
start_process_with_partial_path:
no_shell:
- os.execl
- os.execle
- os.execlp
- os.execlpe
- os.execv
- os.execve
- os.execvp
- os.execvpe
- os.spawnl
- os.spawnle
- os.spawnlp
- os.spawnlpe
- os.spawnv
- os.spawnve
- os.spawnvp
- os.spawnvpe
- os.startfile
shell:
- os.system
- os.popen
- os.popen2
- os.popen3
- os.popen4
- popen2.popen2
- popen2.popen3
- popen2.popen4
- popen2.Popen3
- popen2.Popen4
- commands.getoutput
- commands.getstatusoutput
subprocess:
- subprocess.Popen
- subprocess.call
- subprocess.check_call
- subprocess.check_output
- subprocess.run
subprocess_popen_with_shell_equals_true:
no_shell:
- os.execl
- os.execle
- os.execlp
- os.execlpe
- os.execv
- os.execve
- os.execvp
- os.execvpe
- os.spawnl
- os.spawnle
- os.spawnlp
- os.spawnlpe
- os.spawnv
- os.spawnve
- os.spawnvp
- os.spawnvpe
- os.startfile
shell:
- os.system
- os.popen
- os.popen2
- os.popen3
- os.popen4
- popen2.popen2
- popen2.popen3
- popen2.popen4
- popen2.Popen3
- popen2.Popen4
- commands.getoutput
- commands.getstatusoutput
subprocess:
- subprocess.Popen
- subprocess.call
- subprocess.check_call
- subprocess.check_output
- subprocess.run
subprocess_without_shell_equals_true:
no_shell:
- os.execl
- os.execle
- os.execlp
- os.execlpe
- os.execv
- os.execve
- os.execvp
- os.execvpe
- os.spawnl
- os.spawnle
- os.spawnlp
- os.spawnlpe
- os.spawnv
- os.spawnve
- os.spawnvp
- os.spawnvpe
- os.startfile
shell:
- os.system
- os.popen
- os.popen2
- os.popen3
- os.popen4
- popen2.popen2
- popen2.popen3
- popen2.popen4
- popen2.Popen3
- popen2.Popen4
- commands.getoutput
- commands.getstatusoutput
subprocess:
- subprocess.Popen
- subprocess.call
- subprocess.check_call
- subprocess.check_output
- subprocess.run
try_except_continue:
check_typed_exception: false
try_except_pass:
check_typed_exception: false
weak_cryptographic_key:
weak_key_size_dsa_high: 1024
weak_key_size_dsa_medium: 2048
weak_key_size_ec_high: 160
weak_key_size_ec_medium: 224
weak_key_size_rsa_high: 1024
weak_key_size_rsa_medium: 2048

52
.codeclimate.yml Normal file
View File

@@ -0,0 +1,52 @@
version: "2" # required to adjust maintainability checks
checks:
argument-count:
config:
threshold: 8 # work on this later
complex-logic:
enabled: false # Disabled in favor of using Radon for this
config:
threshold: 4
file-lines:
enabled: false # enable after audio stuff...
config:
threshold: 2000 # I would set this lower if not for cogs as command containers.
method-complexity:
enabled: false # Disabled in favor of using Radon for this
config:
threshold: 5
method-count:
enabled: false # I would set this lower if not for cogs as command containers.
config:
threshold: 20
method-lines:
enabled: false
config:
threshold: 25 # I'm fine with long methods, cautious about the complexity of a single method.
nested-control-flow:
config:
threshold: 6
return-statements:
config:
threshold: 6
similar-code:
enabled: false
config:
threshold: # language-specific defaults. an override will affect all languages.
identical-code:
enabled: false
config:
threshold: # language-specific defaults. an override will affect all languages.
plugins:
bandit:
enabled: false
radon:
enabled: false
config:
threshold: "D"
duplication:
enabled: false
config:
languages:
python:
python_version: 3

15
.github/CODEOWNERS vendored
View File

@@ -9,16 +9,15 @@ redbot/core/config.py @tekulvw
redbot/core/cog_manager.py @tekulvw
redbot/core/core_commands.py @tekulvw
redbot/core/context.py @Tobotimus
redbot/core/commands/* @mikeshardmind
redbot/core/data_manager.py @tekulvw
redbot/core/dev_commands.py @tekulvw
redbot/core/drivers/* @tekulvw
redbot/core/events.py @tekulvw
redbot/core/global_checks.py @tekulvw
redbot/core/i18n.py @tekulvw
redbot/core/json_io.py @tekulvw
redbot/core/modlog.py @palmtree5
redbot/core/rpc.py @tekulvw
redbot/core/sentry_setup.py @Kowlin @tekulvw
redbot/core/utils/chat_formatting.py @tekulvw
redbot/core/utils/mod.py @palmtree5
redbot/core/utils/data_converter.py @mikeshardmind
@@ -26,15 +25,16 @@ redbot/core/utils/antispam.py @mikeshardmind
redbot/core/utils/tunnel.py @mikeshardmind
redbot/core/utils/caching.py @mikeshardmind
redbot/core/utils/common_filters.py @mikeshardmind
redbot/core/utils/dbtools.py @mikeshardmind
# Cogs
redbot/cogs/admin/* @tekulvw
redbot/cogs/alias/* @tekulvw
redbot/cogs/audio/* @aikaterna @atiwiex
redbot/cogs/audio/* @aikaterna @Drapersniper
redbot/cogs/bank/* @tekulvw
redbot/cogs/cleanup/* @palmtree5
redbot/cogs/customcom/* @palmtree5
redbot/cogs/downloader/* @tekulvw
redbot/cogs/downloader/* @tekulvw @jack1142
redbot/cogs/economy/* @palmtree5
redbot/cogs/filter/* @palmtree5
redbot/cogs/general/* @palmtree5
@@ -43,7 +43,6 @@ redbot/cogs/mod/* @palmtree5
redbot/cogs/modlog/* @palmtree5
redbot/cogs/streams/* @Twentysix26 @palmtree5
redbot/cogs/trivia/* @Tobotimus
redbot/cogs/dataconverter/* @mikeshardmind
redbot/cogs/reports/* @mikeshardmind
redbot/cogs/permissions/* @mikeshardmind
redbot/cogs/warnings/* @palmtree5
@@ -51,12 +50,16 @@ redbot/cogs/warnings/* @palmtree5
# Docs
docs/* @tekulvw @palmtree5
# Tests
tests/cogs/downloader/* @jack1142
# Setup, instance setup, and running the bot
setup.py @tekulvw
redbot/__init__.py @tekulvw
redbot/__main__.py @tekulvw
redbot/__main__.py @tekulvw @mikeshardmind
redbot/setup.py @tekulvw
# Others
.travis.yml @Kowlin
crowdin.yml @Kowlin
.github/workflows/* @Kowlin

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
patreon: Red_Devs

5
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,5 @@
<!--
Please be sure to use the correct template,
if your report doesn't have the correct template please open an issue describing your issue in detail
For support regarding the bot itself please visit the discord server over at https://discord.gg/red
-->

View File

@@ -1,3 +1,12 @@
---
name: Bug reports for commands
about: For bugs that involve commands found within Red
title: ''
labels: 'Type: Bug'
assignees: ''
---
# Command bugs
<!--

View File

@@ -1,3 +1,12 @@
---
name: Feature request
about: For feature requests regarding Red itself.
title: ''
labels: 'Type: Feature'
assignees: ''
---
# Feature request
<!-- This template is for feature requests. Please fill out the following: -->

View File

@@ -1,3 +1,12 @@
---
name: Bug report
about: For bugs that don't involve a command.
title: ''
labels: 'Type: Bug'
assignees: ''
---
# Other bugs
<!--
@@ -18,4 +27,4 @@ Did you find a bug with something other than a command? Fill out the following:
#### How can we reproduce this issue?
<!-- Replace with numbered steps to reproduce the issue -->
<!-- Replace with numbered steps to reproduce the issue -->

7
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,7 @@
### Type
- [ ] Bugfix
- [ ] Enhancement
- [ ] New feature
### Description of the changes

View File

@@ -1,6 +1,7 @@
# Bugfix request
<!--
THIS TEMPLATE IS CURRENTLY UNUSED DUE TO GITHUB LIMITATIONS!
To be used for pull requests that fix a bug
-->

View File

@@ -1,6 +1,7 @@
# Enhancement request
<!--
THIS TEMPLATE IS CURRENTLY UNUSED DUE TO GITHUB LIMITATIONS!
To be used for PRs which enhance existing features
-->
@@ -17,4 +18,4 @@ If adding commands, describe any restrictions on their usage.
<!-- To check a box, replace the space between the [] with a x -->
- [ ] Yes
- [ ] No
- [ ] No

View File

@@ -1,6 +1,7 @@
# New feature addition
<!--
THIS TEMPLATE IS CURRENTLY UNUSED DUE TO GITHUB LIMITATIONS!
To be used for PRs which add a new feature
Examples of this include new APIs, new core cogs, etc.
-->
@@ -18,4 +19,4 @@ Examples of this include new APIs, new core cogs, etc.
<!--
If you are adding a cog, describe its commands in detail (functionality, usage restrictions, etc).
If the new feature introduces new requirements, please try to explain why they are necessary.
-->
-->

View File

@@ -1,6 +1,7 @@
# New release
<!--
THIS TEMPLATE IS CURRENTLY UNUSED DUE TO GITHUB LIMITATIONS!
To be used by collaborators for doing releases.
Most contributors will not need to use this.
-->
@@ -13,4 +14,3 @@ Most contributors will not need to use this.
- [ ] Yes
- [ ] No

View File

@@ -1,5 +1,6 @@
# Translations update
<!--
THIS TEMPLATE IS CURRENTLY UNUSED DUE TO GITHUB LIMITATIONS!
Used for PRs updating translations from Crowdin
-->
-->

26
.github/workflows/auto_labeler.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Auto Labeler
on:
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Apply Triage Label
uses: actions/github-script@0.4.0
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const is_status_label = (label) => label.name.startsWith('Status: ');
if (context.payload.issue.labels.some(is_status_label)) {
console.log('Issue already has Status label, skipping...');
return;
}
github.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['Status: Needs Triage']
});

26
.github/workflows/lint_python.yaml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Lint Python
on:
pull_request:
push:
repository_dispatch:
types:
- dispatched_test
env:
ref: ${{ github.event.client_payload.ref || '' }}
jobs:
lint_python:
name: Lint Python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ env.ref }}
- uses: actions/setup-python@v1
with:
python_version: "3.8"
- run: "python -m pip install git+https://github.com/pycqa/pyflakes@1911c20#egg=pyflakes git+https://github.com/pycqa/pycodestyle@d219c68#egg=pycodestyle git+https://gitlab.com/pycqa/flake8@3.7.9#egg=flake8"
name: Install Flake8
- run: "python -m flake8 . --count --select=E9,F7,F82 --show-source"
name: Flake8 Linting

48
.github/workflows/publish_crowdin.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Publish to Crowdin
on:
schedule:
- cron: '0 12 * * THU'
repository_dispatch:
types: crowdin
env:
CROWDIN_API_KEY: ${{ secrets.crowdin_token}}
CROWDIN_PROJECT_ID: ${{ secrets.crowdin_identifier }}
jobs:
deploy:
if: github.repository == 'Cog-Creators/Red-DiscordBot'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.8'
- name: Install dependencies
run: |
curl https://artifacts.crowdin.com/repo/GPG-KEY-crowdin | sudo apt-key add -
echo "deb https://artifacts.crowdin.com/repo/deb/ /" | sudo tee -a /etc/apt/sources.list
sudo apt-get update -qq
sudo apt-get install -y crowdin
pip install redgettext==3.1
- name: Generate source files
run: |
make gettext
- name: Upload source files
run: |
make upload_translations
- name: Download translations
run: |
make download_translations
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Automated Crowdin downstream
title: "[i18n] Automated Crowdin downstream"
body: |
This is an automated PR.
Please ensure that there are no errors or invalid files are in the PR.
labels: "Automated PR, Category: i18n, Changelog Entry: Skipped"
branch: "automated/i18n"

27
.github/workflows/publish_pypi.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Publish to PyPI
on:
push:
tags:
- "*"
jobs:
deploy:
if: github.repository == 'Cog-Creators/Red-DiscordBot'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.8'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.pypi_token }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

85
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Tests
on:
pull_request:
push:
repository_dispatch:
types:
- dispatched_test
env:
ref: ${{ github.event.client_payload.ref || '' }}
jobs:
tox:
runs-on: ubuntu-latest
strategy:
matrix:
python_version:
- "3.8"
tox_env:
- py
- style
- docs
include:
- tox_env: py
friendly_name: Tests
- tox_env: style
friendly_name: Style
- tox_env: docs
friendly_name: Docs
fail-fast: false
name: Tox - ${{ matrix.friendly_name }}
steps:
- uses: actions/checkout@v2
with:
ref: ${{ env.ref }}
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python_version }}
- name: Install tox
run: |
python -m pip install --upgrade pip
pip install tox
- name: Tox test
env:
TOXENV: ${{ matrix.tox_env }}
run: tox
tox-postgres:
runs-on: ubuntu-latest
strategy:
matrix:
python_version:
- "3.8"
fail-fast: false
name: Tox - Postgres
services:
postgresql:
image: postgres:10
ports:
- 5432:5432
env:
POSTGRES_DB: red_db
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
steps:
- uses: actions/checkout@v2
with:
ref: ${{ env.ref }}
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python_version }}
- name: Install tox
run: |
python -m pip install --upgrade pip
pip install tox
- name: Tox test
env:
TOXENV: postgres
PGDATABASE: red_db
PGUSER: postgres
PGPASSWORD: postgres
PGPORT: 5432
run: tox

7
.gitignore vendored
View File

@@ -4,6 +4,9 @@
*.pot
.data
!/tests/cogs/dataconverter/data/**/*.json
Pipfile
Pipfile.lock
.directory
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
@@ -55,6 +58,7 @@ parts/
sdist/
var/
wheels/
pip-wheel-metadata/
*.egg-info/
.installed.cfg
*.egg
@@ -134,3 +138,6 @@ ENV/
# pytest
.pytest_cache/
# Pre-commit hooks
/.pre-commit-config.yaml

148
.pylintrc Normal file
View File

@@ -0,0 +1,148 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=pytest
# Pickle collected data for later comparisons.
persistent=no
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# DO NOT CHANGE THIS VALUE # Use multiple processes to speed up Pylint.
jobs=1
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=
# Allow optimization of some AST trees. This will activate a peephole AST
# optimizer, which will apply various small optimizations. For instance, it can
# be used to obtain the result of joining multiple strings with the addition
# operator. Joining a lot of strings can lead to a maximum recursion error in
# Pylint and this flag can prevent that. It has one side effect, the resulting
# AST will be different than the one from reality.
optimize-ast=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
enable=all
disable=C, # black is enforcing this for us already, incompatibly
W, # unbroaden this to the below specifics later on.
W0107, # uneccessary pass is stylisitc in most places
W0212, # Should likely refactor around protected access warnings later
W1203, # fstrings are too fast to care about enforcing this.
W0612, # unused vars can sometimes indicate an issue, but ...
W1401, # Should probably fix the reason this is disabled (start up screen)
W0511, # Nope, todos are fine for future people to see things to do.
W0613, # Too many places where we need to take unused args do to d.py ... also menus
W0221, # Overriden converters.
W0223, # abstractmethod not defined in mixins is expected
I, # ...
R # While some of these have merit, It's too large a burden to enable this right now.
[REPORTS]
output-format=parseable
files-output=no
reports=no
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# TODO: Write a plyint plugin to allow this with these mixin classes
# To use the abstractmethod we know will be defined in the final class.
ignored-classes=redbot.cogs.mod.movetocore.MoveToCore,
redbot.cogs.mod.kickban.KickBanMixin,
redbot.cogs.mod.mutes.MuteMixin,
redbot.cogs.mod.names.ModInfo,
redbot.cogs.mod.settings.ModSettings,
redbot.cogs.mod.events.Events
ignored-modules=distutils # https://github.com/PyCQA/pylint/issues/73
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=_$|dummy
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,__call__
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception,discord.DiscordException

View File

@@ -1,3 +1,5 @@
version: 2
formats:
- pdf
@@ -5,8 +7,10 @@ build:
image: latest
python:
version: 3.6
pip_install: true
extra_requirements:
- docs
- mongo
version: 3.8
install:
- requirements: docs/requirements.txt
- method: pip
path: .
extra_requirements:
- docs

View File

@@ -3,16 +3,12 @@ language: python
cache: pip
notifications:
email: false
sudo: true
python:
- 3.6.6
- 3.7
- 3.8.1
env:
global:
PIPENV_IGNORE_VIRTUALENVS=1
matrix:
TOXENV=py
- PIPENV_IGNORE_VIRTUALENVS=1
install:
- pip install --upgrade pip tox
@@ -22,46 +18,48 @@ script:
jobs:
include:
- python: 3.6.6
env: TOXENV=docs
- python: 3.6.6
env: TOXENV=style
- env: TOXENV=py
- env: TOXENV=docs
- env: TOXENV=style
- env: TOXENV=postgres
services: postgresql
addons:
postgresql: "10"
before_script:
- psql -c 'create database red_db;' -U postgres
# These jobs only occur on tag creation if the prior ones succeed
- stage: PyPi Deployment
if: tag IS present
python: 3.6.6
python: 3.8.1
env:
- DEPLOYING=true
- TOXENV=py36
- TOXENV=py38
deploy:
- provider: pypi
distributions: sdist bdist_wheel
user: Red-DiscordBot
password:
secure: Ty9vYnd/wCuQkVC/OsS4E2jT9LVDVfzsFrQc4U2hMYcTJnYbl/3omyObdCWCOBC40vUDkVHAQU8ULHzoCA+2KX9Ds/7/P5zCumAA0uJRR9Smw7OlRzSMxJI+/lGq4CwXKzxDZKuo5rsxXEbW5qmYjtO8Mk6KuLkvieb1vyr2DcqWEFzg/7TZNDfD1oP8et8ITQ26lLP1dtQx/jlAiIBzgK9wziuwj1Divb9A///VsGz43N8maZ+jfsDjYqrfUVWTy3ar7JPUplletenYCR1PmQ5C46XfV0kitKd1aITJ48YPAKyYgKy8AIT+Uz1JArTnqdzLSFRNELS57qS00lzgllbteCyWQ8Uzy0Zpxb/5DDH8/mL1n0MyJrF8qjZd2hLNAXg3z/k9bGXeiMLGwoxRlGXkL2XpiVgI93UKKyVyooGNMgPTc/QdSc7krjAWcOtX/HgLR34jxeLPFEdzJNAFIimfDD8N+XTFcNBw6EvOYm/n5MXkckNoX/G+ThNobHZ7VKSASltZ9zBRAJ2dDh35G3CYmVEk33U77RKbL9le/Za9QVBcAO8i6rqVGYkdO7thHHKHc/1CB1jNnjsFSDt0bURtNfAqfwKCurQC8487zbEzT+2fog3Wygv7g3cklaRg4guY8UjZuFWStYGqbroTsOCd9ATNqeO5B13pNhllSzU=
skip_cleanup: true
on:
repo: Cog-Creators/Red-DiscordBot
python: 3.6.6
tags: true
- stage: Crowdin Deployment
if: tag IS present
python: 3.6.6
if: tag IS present OR ENV(BUILD_CROWDIN)
python: 3.8.1
env:
- DEPLOYING=true
- TOXENV=py36
- TOXENV=py38
before_deploy:
- curl https://artifacts.crowdin.com/repo/GPG-KEY-crowdin | sudo apt-key add -
- echo "deb https://artifacts.crowdin.com/repo/deb/ /" | sudo tee -a /etc/apt/sources.list
- sudo apt-get update -qq
- sudo apt-get install -y crowdin
- pip install redgettext==2.2
- pip install redgettext==3.1
deploy:
- provider: script
script: make gettext
script: make upload_translations
skip_cleanup: true
on:
repo: Cog-Creators/Red-DiscordBot
python: 3.6.6
tags: true

View File

@@ -29,9 +29,8 @@ Red is an open source project. This means that each and every one of the develop
We love receiving contributions from our community. Any assistance you can provide with regards to bug fixes, feature enhancements, and documentation is more than welcome.
# 2. Ground Rules
We've made a point to use [ZenHub](https://www.zenhub.com/) (a plugin for GitHub) as our main source of collaboration and coordination. Your experience contributing to Red will be greatly improved if you go get that plugin.
1. Ensure cross compatibility for Windows, Mac OS and Linux.
2. Ensure all Python features used in contributions exist and work in Python 3.6 and above.
2. Ensure all Python features used in contributions exist and work in Python 3.8.1 and above.
3. Create new tests for code you add or bugs you fix. It helps us help you by making sure we don't accidentally break anything :grinning:
4. Create any issues for new features you'd like to implement and explain why this feature is useful to everyone and not just you personally.
5. Don't add new cogs unless specifically given approval in an issue discussing said cog idea.
@@ -53,33 +52,36 @@ Red's repository is configured to follow a particular development workflow, usin
### 4.1 Setting up your development environment
The following requirements must be installed prior to setting up:
- Python 3.6.2 or greater (3.6.6 or greater on Windows)
- Python 3.8.1 or greater
- git
- pip
- pipenv
If you're not on Windows, you can optionally install [pyenv](https://github.com/pyenv/pyenv), which will help you run tests for different python versions.
If you're not on Windows, you should also have GNU make installed, and you can optionally install [pyenv](https://github.com/pyenv/pyenv), which can help you run tests for different python versions.
1. Fork and clone the repository to a directory on your local machine.
2. Open a command line in that directory and execute the following commands:
2. Open a command line in that directory and execute the following command:
```bash
pip install pipenv
pipenv install --dev
make newenv
```
Red, its dependencies, and all required development tools, are now installed to a virtual environment. Red is installed in editable mode, meaning that edits you make to the source code in the repository will be reflected when you run Red.
3. Activate the new virtual environment with the command:
```bash
pipenv shell
```
From here onwards, we will assume you are executing commands from within this shell. Each time you open a new command line, you should execute this command first.
Red, its dependencies, and all required development tools, are now installed to a virtual environment located in the `.venv` subdirectory. Red is installed in editable mode, meaning that edits you make to the source code in the repository will be reflected when you run Red.
3. Activate the new virtual environment with one of the following commands:
- Posix:
```bash
source .venv/bin/activate
```
- Windows:
```powershell
.venv\Scripts\activate
```
Each time you open a new command line, you should execute this command first. From here onwards, we will assume you are executing commands from within this activated virtual environment.
Note: If you haven't used `pipenv` before but are comfortable with virtualenvs, just run `pip install pipenv` in the virtualenv you're already using and invoke the command above from the cloned Red repo. It will do the correct thing.
**Note:** If you're comfortable with setting up virtual environments yourself and would rather do it manually, just run `pip install -Ur tools/dev-requirements.txt` after setting it up.
### 4.2 Testing
We've recently started using [tox](https://github.com/tox-dev/tox) to run all of our tests. It's extremely simple to use, and if you followed the previous section correctly, it is already installed to your virtual environment.
We're using [tox](https://github.com/tox-dev/tox) to run all of our tests. It's extremely simple to use, and if you followed the previous section correctly, it is already installed to your virtual environment.
Currently, tox does the following, creating its own virtual environments for each stage:
- Runs all of our unit tests with [pytest](https://github.com/pytest-dev/pytest) on python 3.6 and 3.7 (test environments `py36` and `py37`)
- Runs all of our unit tests with [pytest](https://github.com/pytest-dev/pytest) on python 3.8 (test environment `py38`)
- Ensures documentation builds without warnings, and all hyperlinks have a valid destination (test environment `docs`)
- Ensures that the code meets our style guide with [black](https://github.com/ambv/black) (test environment `style`)
@@ -92,15 +94,21 @@ Your PR will not be merged until all of these tests pass.
### 4.3 Style
Our style checker of choice, [black](https://github.com/ambv/black), actually happens to be an auto-formatter. The checking functionality simply detects whether or not it would try to reformat something in your code, should you run the formatter on it. For this reason, we recommend using this tool as a formatter, regardless of any disagreements you might have with the style it enforces.
Use the command `black --help` to see how to use this tool. The full style guide is explained in detail on [black's GitHub repository](https://github.com/ambv/black). **There is one exception to this**, however, which is that we set the line length to 99, instead of black's default 88. When using `black` on the command line, simply use it like so: `black -l 99 -N <src>`.
Use the command `black --help` to see how to use this tool. The full style guide is explained in detail on [black's GitHub repository](https://github.com/ambv/black). **There is one exception to this**, however, which is that we set the line length to 99, instead of black's default 88. This is already set in `pyproject.toml` configuration file in the repo so you can simply format code with Black like so: `black <src>`.
### 4.4 Make
You may have noticed we have a `Makefile` and a `make.bat` in the top-level directory. For now, you can do two things with them:
You may have noticed we have a `Makefile` and a `make.bat` in the top-level directory. For now, you can do a few things with them:
1. `make reformat`: Reformat all python files in the project with Black
2. `make stylecheck`: Check if any `.py` files in the project need reformatting
3. `make newenv`: Set up a new virtual environment in the `.venv` subdirectory, and install Red and its dependencies. If one already exists, it is cleared out and replaced.
4. `make syncenv`: Sync your environment with Red's latest dependencies.
The other make recipes are most likely for project maintainers rather than contributors.
You can specify the Python executable used in the make recipes with the `PYTHON` environment variable, e.g. `make PYTHON=/usr/bin/python3.8 newenv`.
### 4.5 Keeping your dependencies up to date
Whenever you pull from upstream (V3/develop on the main repository) and you notice the file `Pipfile.lock` has been changed, it usually means one of the package dependencies have been updated, added or removed. To make sure you're testing and formatting with the most up-to-date versions of our dependencies, run `pipenv install --dev` again.
Whenever you pull from upstream (V3/develop on the main repository) and you notice either of the files `setup.cfg` or `tools/dev-requirements.txt` have been changed, it can often mean some package dependencies have been updated, added or removed. To make sure you're testing and formatting with the most up-to-date versions of our dependencies, run `make syncenv`. You could also simply do `make newenv` to install them to a clean new virtual environment.
### 4.6 To contribute changes
@@ -109,6 +117,10 @@ Whenever you pull from upstream (V3/develop on the main repository) and you noti
3. If you like the changes and think the main Red project could use it:
* Run tests with `tox` to ensure your code is up to scratch
* Create a Pull Request on GitHub with your changes
- If you are contributing a behavior change, please keep in mind that behavior changes
are conditional on them being appropriate for the project's current goals.
If you would like to reduce the risk of putting in effort for something we aren't
going to use, open an issue discussing it first.
### 4.7 How To Report A Bug
Please see our **ISSUES.MD** for more information.
@@ -132,7 +144,7 @@ Pull requests are evaluated by their quality and how effectively they solve thei
1. A pull request is submitted
2. Core team members will review and test the pull request (usually within a week)
3. After a majority of the core team approves your pull request:
3. After a member of the core team approves your pull request:
* If your pull request is considered an improvement or enhancement the project owner will have 1 day to veto or approve your pull request.
* If your pull request is considered a new feature the project owner will have 1 week to veto or approve your pull request.
4. If any feedback is given we expect a response within 1 week or we may decide to close the PR.

32
LICENSE
View File

@@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Red - A fully customizable Discord bot
Copyright (C) 2015-2018 Twentysix
Copyright (C) 2015-2020 Twentysix
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Red-DiscordBot Copyright (C) 2015-2018 Twentysix
Red-DiscordBot Copyright (C) 2015-2020 Twentysix
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -672,3 +672,31 @@ may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
The Red-DiscordBot project contains subcomponents in audio.py that have a
separate copyright notice and license terms. Your use of the source code for
these subcomponents is subject to the terms and conditions of the following
licenses.
This product bundles methods from https://github.com/Just-Some-Bots/MusicBot/
blob/master/musicbot/spotify.py which are available under an MIT license.
Copyright (c) 2015-2018 Just-Some-Bots (https://github.com/Just-Some-Bots)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,13 +1,29 @@
reformat:
black -l 99 -N `git ls-files "*.py"`
stylecheck:
black --check -l 99 -N `git ls-files "*.py"`
gettext:
redgettext --command-docstrings --verbose --recursive redbot --exclude-files "redbot/pytest/**/*"
crowdin upload
PYTHON ?= python3.8
REF?=rewrite
update_vendor:
pip install --upgrade --no-deps -t . https://github.com/Rapptz/discord.py/archive/$(REF).tar.gz#egg=discord.py
rm -r discord.py*.egg-info
$(MAKE) reformat
# Python Code Style
reformat:
$(PYTHON) -m black `git ls-files "*.py"`
stylecheck:
$(PYTHON) -m black --check `git ls-files "*.py"`
stylediff:
$(PYTHON) -m black --check --diff `git ls-files "*.py"`
# Translations
gettext:
$(PYTHON) -m redgettext --command-docstrings --verbose --recursive redbot --exclude-files "redbot/pytest/**/*"
upload_translations:
crowdin upload sources
download_translations:
crowdin download
# Dependencies
bumpdeps:
$(PYTHON) tools/bumpdeps.py
# Development environment
newenv:
$(PYTHON) -m venv --clear .venv
.venv/bin/pip install -U pip setuptools
$(MAKE) syncenv
syncenv:
.venv/bin/pip install -Ur ./tools/dev-requirements.txt

11
Pipfile
View File

@@ -1,11 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
"e1839a8" = { path = ".", editable = true, extras = ['mongo', 'voice'] }
[dev-packages]
tox = "*"
"e1839a9" = { path = ".", editable = true, extras = ['docs', 'test', 'style'] }

771
Pipfile.lock generated
View File

@@ -1,771 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "57184ef83392116db24a1966022ad358f54048bb43d428d47a6e31f1a88fc434"
},
"pipfile-spec": 6,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"aiohttp": {
"hashes": [
"sha256:0419705a36b43c0ac6f15469f9c2a08cad5c939d78bd12a5c23ea167c8253b2b",
"sha256:1812fc4bc6ac1bde007daa05d2d0f61199324e0cc893b11523e646595047ca08",
"sha256:2214b5c0153f45256d5d52d1e0cafe53f9905ed035a142191727a5fb620c03dd",
"sha256:275909137f0c92c61ba6bb1af856a522d5546f1de8ea01e4e726321c697754ac",
"sha256:3983611922b561868428ea1e7269e757803713f55b53502423decc509fef1650",
"sha256:51afec6ffa50a9da4cdef188971a802beb1ca8e8edb40fa429e5e529db3475fa",
"sha256:589f2ec8a101a0f340453ee6945bdfea8e1cd84c8d88e5be08716c34c0799d95",
"sha256:789820ddc65e1f5e71516adaca2e9022498fa5a837c79ba9c692a9f8f916c330",
"sha256:7a968a0bdaaf9abacc260911775611c9a602214a23aeb846f2eb2eeaa350c4dc",
"sha256:7aeefbed253f59ea39e70c5848de42ed85cb941165357fc7e87ab5d8f1f9592b",
"sha256:7b2eb55c66512405103485bd7d285a839d53e7fdc261ab20e5bcc51d7aaff5de",
"sha256:87bc95d3d333bb689c8d755b4a9d7095a2356108002149523dfc8e607d5d32a4",
"sha256:9d80e40db208e29168d3723d1440ecbb06054d349c5ece6a2c5a611490830dd7",
"sha256:a1b442195c2a77d33e4dbee67c9877ccbdd3a1f686f91eb479a9577ed8cc326b",
"sha256:ab3d769413b322d6092f169f316f7b21cd261a7589f7e31db779d5731b0480d8",
"sha256:b066d3dec5d0f5aee6e34e5765095dc3d6d78ef9839640141a2b20816a0642bd",
"sha256:b24e7845ae8de3e388ef4bcfcf7f96b05f52c8e633b33cf8003a6b1d726fc7c2",
"sha256:c59a953c3f8524a7c86eaeaef5bf702555be12f5668f6384149fe4bb75c52698",
"sha256:cf2cc6c2c10d242790412bea7ccf73726a9a44b4c4b073d2699ef3b48971fd95",
"sha256:e0c9c8d4150ae904f308ff27b35446990d2b1dfc944702a21925937e937394c6",
"sha256:f1839db4c2b08a9c8f9788112644f8a8557e8e0ecc77b07091afabb941dc55d0",
"sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07"
],
"version": "==3.4.4"
},
"aiohttp-json-rpc": {
"hashes": [
"sha256:00d72f40edfc7271578d545a8c47874c0e23cc5d3201ed8128481f6a4af47e32",
"sha256:02d83b6998f8a0b7e59b46f0cb8a96b475bbf82600b1f9527df47135353f1ca8"
],
"version": "==0.11.2"
},
"appdirs": {
"hashes": [
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
],
"version": "==1.4.3"
},
"async-timeout": {
"hashes": [
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
"version": "==3.0.1"
},
"attrs": {
"hashes": [
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
],
"version": "==18.2.0"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"colorama": {
"hashes": [
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
],
"version": "==0.4.1"
},
"distro": {
"hashes": [
"sha256:224041cef9600e72d19ae41ba006e71c05c4dc802516da715d7fda55ba3d8742",
"sha256:6ec8e539cf412830e5ccf521aecf879f2c7fcf60ce446e33cd16eef1ed8a0158"
],
"version": "==1.3.0"
},
"dnspython": {
"hashes": [
"sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01",
"sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"
],
"version": "==1.16.0"
},
"e1839a8": {
"editable": true,
"extras": [
"mongo",
"voice"
],
"path": "."
},
"fuzzywuzzy": {
"hashes": [
"sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254",
"sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62"
],
"version": "==0.17.0"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"idna-ssl": {
"hashes": [
"sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"
],
"version": "==1.1.0"
},
"motor": {
"hashes": [
"sha256:462fbb824f4289481c158227a2579d6adaf1ec7c70cf7ebe60ed6ceb321e5869",
"sha256:d035c09ab422bc50bf3efb134f7405694cae76268545bd21e14fb22e2638f84e"
],
"version": "==2.0.0"
},
"multidict": {
"hashes": [
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
],
"version": "==4.5.2"
},
"pymongo": {
"hashes": [
"sha256:025f94fc1e1364f00e50badc88c47f98af20012f23317234e51a11333ef986e6",
"sha256:02aa7fb282606331aefbc0586e2cf540e9dbe5e343493295e7f390936ad2738e",
"sha256:057210e831573e932702cf332012ed39da78edf0f02d24a3f0b213264a87a397",
"sha256:0d946b79c56187fe139276d4c8ed612a27a616966c8b9779d6b79e2053587c8b",
"sha256:104790893b928d310aae8a955e0bdbaa442fb0ac0a33d1bbb0741c791a407778",
"sha256:15527ef218d95a8717486106553b0d54ff2641e795b65668754e17ab9ca6e381",
"sha256:1826527a0b032f6e20e7ac7f72d7c26dd476a5e5aa82c04aa1c7088a59fded7d",
"sha256:22e3aa4ce1c3eebc7f70f9ca7fd4ce1ea33e8bdb7b61996806cd312f08f84a3a",
"sha256:244e1101e9a48615b9a16cbd194f73c115fdfefc96894803158608115f703b26",
"sha256:24b8c04fdb633a84829d03909752c385faef249c06114cc8d8e1700b95aae5c8",
"sha256:2c276696350785d3104412cbe3ac70ab1e3a10c408e7b20599ee41403a3ed630",
"sha256:2d8474dc833b1182b651b184ace997a7bd83de0f51244de988d3c30e49f07de3",
"sha256:3119b57fe1d964781e91a53e81532c85ed1701baaddec592e22f6b77a9fdf3df",
"sha256:3bee8e7e0709b0fcdaa498a3e513bde9ffc7cd09dbceb11e425bd91c89dbd5b6",
"sha256:436c071e01a464753d30dbfc8768dd93aecf2a8e378e5314d130b95e77b4d612",
"sha256:46635e3f19ad04d5a7d7cf23d232388ddbfccf46d9a3b7436b6abadda4e84813",
"sha256:4772e0b679717e7ac4608d996f57b6f380748a919b457cb05bb941467b888b22",
"sha256:4e2cd80e16f481a62c3175b607373200e714ed29025f21559ebf7524f295689f",
"sha256:52732960efa0e003ca1c092dc0a3c65276e897681287a788a01ca78dda3b41f0",
"sha256:55a7de51ec7d1731b2431886d0349146645f2816e5b8eb982d7c49f89472c9f3",
"sha256:5f8ed5934197a2d4b2087646e98de3e099a237099dcf498b9e38dd3465f74ef4",
"sha256:64b064124fcbc8eb04a155117dc4d9a336e3cda3f069958fbc44fe70c3c3d1e9",
"sha256:65958b8e4319f992e85dad59d8081888b97fcdbde5f0d14bc28f2848b92d3ef1",
"sha256:7683428862e20c6a790c19e64f8ccf487f613fbc83d47e3d532df9c81668d451",
"sha256:78566d5570c75a127c2491e343dc006798a384f06be588fe9b0cbe5595711559",
"sha256:7d1cb00c093dbf1d0b16ccf123e79dee3b82608e4a2a88947695f0460eef13ff",
"sha256:8c74e2a9b594f7962c62cef7680a4cb92a96b4e6e3c2f970790da67cc0213a7e",
"sha256:8e60aa7699170f55f4b0f56ee6f8415229777ac7e4b4b1aa41fc61eec08c1f1d",
"sha256:9447b561529576d89d3bf973e5241a88cf76e45bd101963f5236888713dea774",
"sha256:970055bfeb0be373f2f5299a3db8432444bad3bc2f198753ee6c2a3a781e0959",
"sha256:a6344b8542e584e140dc3c651d68bde51270e79490aa9320f9e708f9b2c39bd5",
"sha256:ce309ca470d747b02ba6069d286a17b7df8e9c94d10d727d9cf3a64e51d85184",
"sha256:cfbd86ed4c2b2ac71bbdbcea6669bf295def7152e3722ddd9dda94ac7981f33d",
"sha256:d7929c513732dff093481f4a0954ed5ff16816365842136b17caa0b4992e49d3"
],
"version": "==3.7.2"
},
"python-levenshtein": {
"hashes": [
"sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1"
],
"version": "==0.12.0"
},
"pyyaml": {
"hashes": [
"sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",
"sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf",
"sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a",
"sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3",
"sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1",
"sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1",
"sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613",
"sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04",
"sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f",
"sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537",
"sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"
],
"version": "==3.13"
},
"raven": {
"hashes": [
"sha256:3fa6de6efa2493a7c827472e984ce9b020797d0da16f1db67197bcc23c8fae54",
"sha256:44a13f87670836e153951af9a3c80405d36b43097db869a36e92809673692ce4"
],
"version": "==6.10.0"
},
"raven-aiohttp": {
"hashes": [
"sha256:1444a49c93a85b8bb57c6ee649e512368dce7a26ad64ac3a01d86aa5669d77f3",
"sha256:6a34b6a9841ad0fd827eeb158edb5826c5c5bd7babe2cde2a3f23eb85313af04"
],
"version": "==0.7.0"
},
"red-lavalink": {
"hashes": [
"sha256:6a1a34471ccf4630eee537049568dd87e8e93614f1d1ce355dd74e5b10079782"
],
"version": "==0.1.2"
},
"schema": {
"hashes": [
"sha256:d994b0dc4966000037b26898df638e3e2a694cc73636cb2050e652614a350687",
"sha256:fa1a53fe5f3b6929725a4e81688c250f46838e25d8c1885a10a590c8c01a7b74"
],
"version": "==0.6.8"
},
"websockets": {
"hashes": [
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
],
"version": "==6.0"
},
"yarl": {
"hashes": [
"sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9",
"sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f",
"sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb",
"sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320",
"sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842",
"sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0",
"sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829",
"sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310",
"sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4",
"sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8",
"sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"
],
"version": "==1.3.0"
}
},
"develop": {
"aiohttp": {
"hashes": [
"sha256:0419705a36b43c0ac6f15469f9c2a08cad5c939d78bd12a5c23ea167c8253b2b",
"sha256:1812fc4bc6ac1bde007daa05d2d0f61199324e0cc893b11523e646595047ca08",
"sha256:2214b5c0153f45256d5d52d1e0cafe53f9905ed035a142191727a5fb620c03dd",
"sha256:275909137f0c92c61ba6bb1af856a522d5546f1de8ea01e4e726321c697754ac",
"sha256:3983611922b561868428ea1e7269e757803713f55b53502423decc509fef1650",
"sha256:51afec6ffa50a9da4cdef188971a802beb1ca8e8edb40fa429e5e529db3475fa",
"sha256:589f2ec8a101a0f340453ee6945bdfea8e1cd84c8d88e5be08716c34c0799d95",
"sha256:789820ddc65e1f5e71516adaca2e9022498fa5a837c79ba9c692a9f8f916c330",
"sha256:7a968a0bdaaf9abacc260911775611c9a602214a23aeb846f2eb2eeaa350c4dc",
"sha256:7aeefbed253f59ea39e70c5848de42ed85cb941165357fc7e87ab5d8f1f9592b",
"sha256:7b2eb55c66512405103485bd7d285a839d53e7fdc261ab20e5bcc51d7aaff5de",
"sha256:87bc95d3d333bb689c8d755b4a9d7095a2356108002149523dfc8e607d5d32a4",
"sha256:9d80e40db208e29168d3723d1440ecbb06054d349c5ece6a2c5a611490830dd7",
"sha256:a1b442195c2a77d33e4dbee67c9877ccbdd3a1f686f91eb479a9577ed8cc326b",
"sha256:ab3d769413b322d6092f169f316f7b21cd261a7589f7e31db779d5731b0480d8",
"sha256:b066d3dec5d0f5aee6e34e5765095dc3d6d78ef9839640141a2b20816a0642bd",
"sha256:b24e7845ae8de3e388ef4bcfcf7f96b05f52c8e633b33cf8003a6b1d726fc7c2",
"sha256:c59a953c3f8524a7c86eaeaef5bf702555be12f5668f6384149fe4bb75c52698",
"sha256:cf2cc6c2c10d242790412bea7ccf73726a9a44b4c4b073d2699ef3b48971fd95",
"sha256:e0c9c8d4150ae904f308ff27b35446990d2b1dfc944702a21925937e937394c6",
"sha256:f1839db4c2b08a9c8f9788112644f8a8557e8e0ecc77b07091afabb941dc55d0",
"sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07"
],
"version": "==3.4.4"
},
"aiohttp-json-rpc": {
"hashes": [
"sha256:00d72f40edfc7271578d545a8c47874c0e23cc5d3201ed8128481f6a4af47e32",
"sha256:02d83b6998f8a0b7e59b46f0cb8a96b475bbf82600b1f9527df47135353f1ca8"
],
"version": "==0.11.2"
},
"alabaster": {
"hashes": [
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
],
"version": "==0.7.12"
},
"appdirs": {
"hashes": [
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
],
"version": "==1.4.3"
},
"async-timeout": {
"hashes": [
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
"version": "==3.0.1"
},
"atomicwrites": {
"hashes": [
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
],
"version": "==1.2.1"
},
"attrs": {
"hashes": [
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
],
"version": "==18.2.0"
},
"babel": {
"hashes": [
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
],
"version": "==2.6.0"
},
"black": {
"hashes": [
"sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
"sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"
],
"version": "==18.9b0"
},
"certifi": {
"hashes": [
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
],
"version": "==2018.11.29"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"version": "==7.0"
},
"colorama": {
"hashes": [
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
],
"version": "==0.4.1"
},
"distro": {
"hashes": [
"sha256:224041cef9600e72d19ae41ba006e71c05c4dc802516da715d7fda55ba3d8742",
"sha256:6ec8e539cf412830e5ccf521aecf879f2c7fcf60ce446e33cd16eef1ed8a0158"
],
"version": "==1.3.0"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
],
"version": "==0.14"
},
"e1839a9": {
"editable": true,
"extras": [
"docs",
"test",
"style"
],
"path": "."
},
"filelock": {
"hashes": [
"sha256:b8d5ca5ca1c815e1574aee746650ea7301de63d87935b3463d26368b76e31633",
"sha256:d610c1bb404daf85976d7a82eb2ada120f04671007266b708606565dd03b5be6"
],
"version": "==3.0.10"
},
"fuzzywuzzy": {
"hashes": [
"sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254",
"sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62"
],
"version": "==0.17.0"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"idna-ssl": {
"hashes": [
"sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"
],
"version": "==1.1.0"
},
"imagesize": {
"hashes": [
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
],
"version": "==1.1.0"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markupsafe": {
"hashes": [
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
"sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
"sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
"sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
"sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
"sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
"sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
"sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
"sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
"sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
"sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
"sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
"sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
"sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
"sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
"sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
"sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
"sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
"sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
"sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
"sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
"sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
"sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
"sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
"sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
"sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
"sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
"sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
],
"version": "==1.1.0"
},
"more-itertools": {
"hashes": [
"sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4",
"sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc",
"sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"
],
"version": "==5.0.0"
},
"multidict": {
"hashes": [
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
],
"version": "==4.5.2"
},
"packaging": {
"hashes": [
"sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807",
"sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9"
],
"version": "==18.0"
},
"pluggy": {
"hashes": [
"sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616",
"sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a"
],
"version": "==0.8.1"
},
"py": {
"hashes": [
"sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
"sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
],
"version": "==1.7.0"
},
"pygments": {
"hashes": [
"sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a",
"sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d"
],
"version": "==2.3.1"
},
"pyparsing": {
"hashes": [
"sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b",
"sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592"
],
"version": "==2.3.0"
},
"pytest": {
"hashes": [
"sha256:3e65a22eb0d4f1bdbc1eacccf4a3198bf8d4049dea5112d70a0c61b00e748d02",
"sha256:5924060b374f62608a078494b909d341720a050b5224ff87e17e12377486a71d"
],
"version": "==4.1.0"
},
"pytest-asyncio": {
"hashes": [
"sha256:9fac5100fd716cbecf6ef89233e8590a4ad61d729d1732e0a96b84182df1daaf",
"sha256:d734718e25cfc32d2bf78d346e99d33724deeba774cc4afdf491530c6184b63b"
],
"version": "==0.10.0"
},
"python-levenshtein": {
"hashes": [
"sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1"
],
"version": "==0.12.0"
},
"pytz": {
"hashes": [
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
"sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
],
"version": "==2018.9"
},
"pyyaml": {
"hashes": [
"sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",
"sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf",
"sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a",
"sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3",
"sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1",
"sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1",
"sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613",
"sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04",
"sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f",
"sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537",
"sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"
],
"version": "==3.13"
},
"raven": {
"hashes": [
"sha256:3fa6de6efa2493a7c827472e984ce9b020797d0da16f1db67197bcc23c8fae54",
"sha256:44a13f87670836e153951af9a3c80405d36b43097db869a36e92809673692ce4"
],
"version": "==6.10.0"
},
"raven-aiohttp": {
"hashes": [
"sha256:1444a49c93a85b8bb57c6ee649e512368dce7a26ad64ac3a01d86aa5669d77f3",
"sha256:6a34b6a9841ad0fd827eeb158edb5826c5c5bd7babe2cde2a3f23eb85313af04"
],
"version": "==0.7.0"
},
"requests": {
"hashes": [
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
],
"version": "==2.21.0"
},
"schema": {
"hashes": [
"sha256:d994b0dc4966000037b26898df638e3e2a694cc73636cb2050e652614a350687",
"sha256:fa1a53fe5f3b6929725a4e81688c250f46838e25d8c1885a10a590c8c01a7b74"
],
"version": "==0.6.8"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.12.0"
},
"snowballstemmer": {
"hashes": [
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
],
"version": "==1.2.1"
},
"sphinx": {
"hashes": [
"sha256:429e3172466df289f0f742471d7e30ba3ee11f3b5aecd9a840480d03f14bcfe5",
"sha256:c4cb17ba44acffae3d3209646b6baec1e215cad3065e852c68cc569d4df1b9f8"
],
"version": "==1.8.3"
},
"sphinx-rtd-theme": {
"hashes": [
"sha256:02f02a676d6baabb758a20c7a479d58648e0f64f13e07d1b388e9bb2afe86a09",
"sha256:d0f6bc70f98961145c5b0e26a992829363a197321ba571b31b24ea91879e0c96"
],
"version": "==0.4.2"
},
"sphinxcontrib-asyncio": {
"hashes": [
"sha256:96627b1ec4eba08d09ad577ff9416c131910333ef37a2c82a2716e59646739f0"
],
"version": "==0.2.0"
},
"sphinxcontrib-websupport": {
"hashes": [
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
],
"version": "==1.1.0"
},
"toml": {
"hashes": [
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
],
"version": "==0.10.0"
},
"tox": {
"hashes": [
"sha256:04f8f1aa05de8e76d7a266ccd14e0d665d429977cd42123bc38efa9b59964e9e",
"sha256:25ef928babe88c71e3ed3af0c464d1160b01fca2dd1870a5bb26c2dea61a17fc"
],
"index": "pypi",
"version": "==3.7.0"
},
"urllib3": {
"hashes": [
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
],
"version": "==1.24.1"
},
"virtualenv": {
"hashes": [
"sha256:34b9ae3742abed2f95d3970acf4d80533261d6061b51160b197f84e5b4c98b4c",
"sha256:fa736831a7b18bd2bfeef746beb622a92509e9733d645952da136b0639cd40cd"
],
"version": "==16.2.0"
},
"websockets": {
"hashes": [
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
],
"version": "==6.0"
},
"yarl": {
"hashes": [
"sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9",
"sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f",
"sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb",
"sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320",
"sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842",
"sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0",
"sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829",
"sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310",
"sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4",
"sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8",
"sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"
],
"version": "==1.3.0"
}
}
}

View File

@@ -16,21 +16,21 @@
<img src="https://img.shields.io/badge/Support-Red!-yellow.svg" alt="Support Red on Patreon!">
</a>
<a href="https://www.python.org/downloads/">
<img src="https://img.shields.io/badge/Made%20With-Python%203-blue.svg?style=for-the-badge" alt="Made with Python 3">
<img src="https://img.shields.io/badge/Made%20With-Python%203.8-blue.svg?style=for-the-badge" alt="Made with Python 3.8">
</a>
<a href="https://crowdin.com/project/red-discordbot">
<img src="https://d322cqt584bo4o.cloudfront.net/red-discordbot/localized.svg" alt="Localized with Crowdin">
</a>
<a href="https://github.com/Rapptz/discord.py/tree/rewrite">
<a href="https://github.com/Rapptz/discord.py/">
<img src="https://img.shields.io/badge/discord-py-blue.svg" alt="discord.py">
</a>
</p>
<p align="center">
<a href="https://travis-ci.org/Cog-Creators/Red-DiscordBot">
<img src="https://api.travis-ci.org/Cog-Creators/Red-DiscordBot.svg?branch=V3/develop" alt="Travis CI">
<a href="https://github.com/Cog-Creators/Red-DiscordBot/actions">
<img src="https://github.com/Cog-Creators/Red-DiscordBot/workflows/Tests/badge.svg" alt="GitHub Actions">
</a>
<a href="http://red-discordbot.readthedocs.io/en/v3-develop/?badge=v3-develop">
<img src="https://readthedocs.org/projects/red-discordbot/badge/?version=v3-develop" alt="Red on readthedocs.org">
<a href="http://red-discordbot.readthedocs.io/en/stable/?badge=stable">
<img src="https://readthedocs.org/projects/red-discordbot/badge/?version=stable" alt="Red on readthedocs.org">
</a>
<a href="https://github.com/ambv/black">
<img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code Style: Black">
@@ -45,7 +45,7 @@
<a href="#installation">Installation</a>
<a href="http://red-discordbot.readthedocs.io/en/v3-develop/index.html">Documentation</a>
<a href="http://red-discordbot.readthedocs.io/en/stable/index.html">Documentation</a>
<a href="#plugins">Plugins</a>
@@ -83,19 +83,12 @@ community of cog repositories.**
**The following platforms are officially supported:**
- [Windows](https://red-discordbot.readthedocs.io/en/v3-develop/install_windows.html)
- [MacOS](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
- [Ubuntu](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
- [Debian Stretch](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
- [CentOS 7](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
- [Arch Linux](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
- [Raspbian Stretch](https://red-discordbot.readthedocs.io/en/v3-develop/install_linux_mac.html)
Already using **Red** V2? Take a look at the [Data Converter](https://red-discordbot.readthedocs.io/en/v3-develop/cog_dataconverter.html)
to import your data to V3.
- [Windows](https://red-discordbot.readthedocs.io/en/stable/install_windows.html)
- [MacOS](https://red-discordbot.readthedocs.io/en/stable/install_linux_mac.html)
- [Most major linux distributions](https://red-discordbot.readthedocs.io/en/stable/install_linux_mac.html)
If after reading the guide you are still experiencing issues, feel free to join the
[Official Discord Server](https://discord.gg/red) and ask in the **#v3-support** channel for help.
[Official Discord Server](https://discord.gg/red) and ask in the **#support** channel for help.
# Plugins
@@ -108,18 +101,18 @@ plugins directly from Discord! A few examples are:
- Casino
- Reaction roles
- Slow Mode
- Anilist
- AniList
- And much, much more!
Feel free to take a [peek](https://github.com/Cog-Creators/Red-DiscordBot/issues/1398) at a list of
Feel free to take a [peek](https://cogboard.red/t/approved-repositories/210) at a list of
available 3rd party cogs!
# Join the community!
**Red** is in continuous development, and its supported by an active community which produces new
content (cogs/plugins) for everyone to enjoy. New features are constantly added. If you cant
[find](https://github.com/Cog-Creators/Red-DiscordBot/issues/1398) the cog youre looking for,
consult our [guide](https://red-discordbot.readthedocs.io/en/v3-develop/guide_cog_creation.html) on
[find](https://cogboard.red/t/approved-repositories/210) the cog youre looking for,
consult our [guide](https://red-discordbot.readthedocs.io/en/stable/guide_cog_creation.html) on
building your own cogs!
Join us on our [Official Discord Server](https://discord.gg/red)!
@@ -128,11 +121,6 @@ Join us on our [Official Discord Server](https://discord.gg/red)!
Released under the [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html) license.
This project vendors the
[discord.py library by Rapptz](https://github.com/Rapptz/discord.py/tree/rewrite), which is
licensed under the [MIT License](https://opensource.org/licenses/MIT). This amounts to everything
within the *discord* folder of this repository.
Red is named after the main character of "Transistor", a video game by
[Super Giant Games](https://www.supergiantgames.com/games/transistor/).

View File

@@ -1,5 +1,9 @@
api_key_env: CROWDIN_API_KEY
project_identifier_env: CROWDIN_PROJECT_ID
base_path: ./redbot/
preserve_hierarchy: true
files:
- source: /redbot/**/*.pot
- source: cogs/**/messages.pot
translation: /%original_path%/%locale%.po
- source: core/**/messages.pot
translation: /%original_path%/%locale%.po

View File

@@ -1,64 +0,0 @@
# -*- coding: utf-8 -*-
"""
Discord API Wrapper
~~~~~~~~~~~~~~~~~~~
A basic wrapper for the Discord API.
:copyright: (c) 2015-2017 Rapptz
:license: MIT, see LICENSE for more details.
"""
__title__ = "discord"
__author__ = "Rapptz"
__license__ = "MIT"
__copyright__ = "Copyright 2015-2017 Rapptz"
__version__ = "1.0.0a"
from collections import namedtuple
import logging
from .client import Client, AppInfo
from .user import User, ClientUser, Profile
from .emoji import Emoji, PartialEmoji
from .activity import *
from .channel import *
from .guild import Guild
from .relationship import Relationship
from .member import Member, VoiceState
from .message import Message, Attachment
from .errors import *
from .calls import CallMessage, GroupCall
from .permissions import Permissions, PermissionOverwrite
from .role import Role
from .file import File
from .colour import Color, Colour
from .invite import Invite
from .object import Object
from .reaction import Reaction
from . import utils, opus, abc
from .enums import *
from .embeds import Embed
from .shard import AutoShardedClient
from .player import *
from .webhook import *
from .voice_client import VoiceClient
from .audit_logs import AuditLogChanges, AuditLogEntry, AuditLogDiff
from .raw_models import *
VersionInfo = namedtuple("VersionInfo", "major minor micro releaselevel serial")
version_info = VersionInfo(major=1, minor=0, micro=0, releaselevel="alpha", serial=0)
try:
from logging import NullHandler
except ImportError:
class NullHandler(logging.Handler):
def emit(self, record):
pass
logging.getLogger(__name__).addHandler(NullHandler())

View File

@@ -1,337 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import argparse
import sys
from pathlib import Path
import discord
def core(parser, args):
pass
bot_template = """#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
import config
class Bot(commands.{base}):
def __init__(self, **kwargs):
super().__init__(command_prefix=commands.when_mentioned_or('{prefix}'), **kwargs)
for cog in config.cogs:
try:
self.load_extension(cog)
except Exception as exc:
print('Could not load extension {{0}} due to {{1.__class__.__name__}}: {{1}}'.format(cog, exc))
async def on_ready(self):
print('Logged on as {{0}} (ID: {{0.id}})'.format(self.user))
bot = Bot()
# write general commands here
bot.run(config.token)
"""
gitignore_template = """# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Our configuration files
config.py
"""
cog_template = '''# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
class {name}:
"""The description for {name} goes here."""
def __init__(self, bot):
self.bot = bot
{extra}
def setup(bot):
bot.add_cog({name}(bot))
'''
cog_extras = """
def __unload(self):
# clean up logic goes here
pass
async def __local_check(self, ctx):
# checks that apply to every command in here
return True
async def __global_check(self, ctx):
# checks that apply to every command to the bot
return True
async def __global_check_once(self, ctx):
# check that apply to every command but is guaranteed to be called only once
return True
async def __error(self, ctx, error):
# error handling to every command in here
pass
async def __before_invoke(self, ctx):
# called before a command is called here
pass
async def __after_invoke(self, ctx):
# called after a command is called here
pass
"""
# certain file names and directory names are forbidden
# see: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
# although some of this doesn't apply to Linux, we might as well be consistent
_base_table = {
"<": "-",
">": "-",
":": "-",
'"': "-",
# '/': '-', these are fine
# '\\': '-',
"|": "-",
"?": "-",
"*": "-",
}
#
_base_table.update((chr(i), None) for i in range(32))
translation_table = str.maketrans(_base_table)
def to_path(parser, name, *, replace_spaces=False):
if isinstance(name, Path):
return name
if sys.platform == "win32":
forbidden = (
"CON",
"PRN",
"AUX",
"NUL",
"COM1",
"COM2",
"COM3",
"COM4",
"COM5",
"COM6",
"COM7",
"COM8",
"COM9",
"LPT1",
"LPT2",
"LPT3",
"LPT4",
"LPT5",
"LPT6",
"LPT7",
"LPT8",
"LPT9",
)
if len(name) <= 4 and name.upper() in forbidden:
parser.error("invalid directory name given, use a different one")
name = name.translate(translation_table)
if replace_spaces:
name = name.replace(" ", "-")
return Path(name)
def newbot(parser, args):
if sys.version_info < (3, 5):
parser.error("python version is older than 3.5, consider upgrading.")
new_directory = to_path(parser, args.directory) / to_path(parser, args.name)
# as a note exist_ok for Path is a 3.5+ only feature
# since we already checked above that we're >3.5
try:
new_directory.mkdir(exist_ok=True, parents=True)
except OSError as exc:
parser.error("could not create our bot directory ({})".format(exc))
cogs = new_directory / "cogs"
try:
cogs.mkdir(exist_ok=True)
init = cogs / "__init__.py"
init.touch()
except OSError as exc:
print("warning: could not create cogs directory ({})".format(exc))
try:
with open(str(new_directory / "config.py"), "w", encoding="utf-8") as fp:
fp.write('token = "place your token here"\ncogs = []\n')
except OSError as exc:
parser.error("could not create config file ({})".format(exc))
try:
with open(str(new_directory / "bot.py"), "w", encoding="utf-8") as fp:
base = "Bot" if not args.sharded else "AutoShardedBot"
fp.write(bot_template.format(base=base, prefix=args.prefix))
except OSError as exc:
parser.error("could not create bot file ({})".format(exc))
if not args.no_git:
try:
with open(str(new_directory / ".gitignore"), "w", encoding="utf-8") as fp:
fp.write(gitignore_template)
except OSError as exc:
print("warning: could not create .gitignore file ({})".format(exc))
print("successfully made bot at", new_directory)
def newcog(parser, args):
if sys.version_info < (3, 5):
parser.error("python version is older than 3.5, consider upgrading.")
cog_dir = to_path(parser, args.directory)
try:
cog_dir.mkdir(exist_ok=True)
except OSError as exc:
print("warning: could not create cogs directory ({})".format(exc))
directory = cog_dir / to_path(parser, args.name)
directory = directory.with_suffix(".py")
try:
with open(str(directory), "w", encoding="utf-8") as fp:
extra = cog_extras if args.full else ""
if args.class_name:
name = args.class_name
else:
name = str(directory.stem)
if "-" in name:
name = name.replace("-", " ").title().replace(" ", "")
else:
name = name.title()
fp.write(cog_template.format(name=name, extra=extra))
except OSError as exc:
parser.error("could not create cog file ({})".format(exc))
else:
print("successfully made cog at", directory)
def add_newbot_args(subparser):
parser = subparser.add_parser("newbot", help="creates a command bot project quickly")
parser.set_defaults(func=newbot)
parser.add_argument("name", help="the bot project name")
parser.add_argument(
"directory",
help="the directory to place it in (default: .)",
nargs="?",
default=Path.cwd(),
)
parser.add_argument(
"--prefix", help="the bot prefix (default: $)", default="$", metavar="<prefix>"
)
parser.add_argument("--sharded", help="whether to use AutoShardedBot", action="store_true")
parser.add_argument(
"--no-git", help="do not create a .gitignore file", action="store_true", dest="no_git"
)
def add_newcog_args(subparser):
parser = subparser.add_parser("newcog", help="creates a new cog template quickly")
parser.set_defaults(func=newcog)
parser.add_argument("name", help="the cog name")
parser.add_argument(
"directory",
help="the directory to place it in (default: cogs)",
nargs="?",
default=Path("cogs"),
)
parser.add_argument(
"--class-name", help="the class name of the cog (default: <name>)", dest="class_name"
)
parser.add_argument("--full", help="add all special methods as well", action="store_true")
def parse_args():
parser = argparse.ArgumentParser(
prog="discord", description="Tools for helping with discord.py"
)
version = "discord.py v{0.__version__} for Python {1[0]}.{1[1]}.{1[2]}".format(
discord, sys.version_info
)
parser.add_argument(
"-v", "--version", action="version", version=version, help="shows the library version"
)
parser.set_defaults(func=core)
subparser = parser.add_subparsers(dest="subcommand", title="subcommands")
add_newbot_args(subparser)
add_newcog_args(subparser)
return parser, parser.parse_args()
def main():
parser, args = parse_args()
args.func(parser, args)
main()

File diff suppressed because it is too large Load Diff

View File

@@ -1,613 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import datetime
from .enums import ActivityType, try_enum
from .colour import Colour
__all__ = ["Activity", "Streaming", "Game", "Spotify"]
"""If curious, this is the current schema for an activity.
It's fairly long so I will document it here:
All keys are optional.
state: str (max: 128),
details: str (max: 128)
timestamps: dict
start: int (min: 1)
end: int (min: 1)
assets: dict
large_image: str (max: 32)
large_text: str (max: 128)
small_image: str (max: 32)
small_text: str (max: 128)
party: dict
id: str (max: 128),
size: List[int] (max-length: 2)
elem: int (min: 1)
secrets: dict
match: str (max: 128)
join: str (max: 128)
spectate: str (max: 128)
instance: bool
application_id: str
name: str (max: 128)
url: str
type: int
sync_id: str
session_id: str
flags: int
There are also activity flags which are mostly uninteresting for the library atm.
t.ActivityFlags = {
INSTANCE: 1,
JOIN: 2,
SPECTATE: 4,
JOIN_REQUEST: 8,
SYNC: 16,
PLAY: 32
}
"""
class _ActivityTag:
__slots__ = ()
class Activity(_ActivityTag):
"""Represents an activity in Discord.
This could be an activity such as streaming, playing, listening
or watching.
For memory optimisation purposes, some activities are offered in slimmed
down versions:
- :class:`Game`
- :class:`Streaming`
Attributes
------------
application_id: :class:`str`
The application ID of the game.
name: :class:`str`
The name of the activity.
url: :class:`str`
A stream URL that the activity could be doing.
type: :class:`ActivityType`
The type of activity currently being done.
state: :class:`str`
The user's current state. For example, "In Game".
details: :class:`str`
The detail of the user's current activity.
timestamps: :class:`dict`
A dictionary of timestamps. It contains the following optional keys:
- ``start``: Corresponds to when the user started doing the
activity in milliseconds since Unix epoch.
- ``end``: Corresponds to when the user will finish doing the
activity in milliseconds since Unix epoch.
assets: :class:`dict`
A dictionary representing the images and their hover text of an activity.
It contains the following optional keys:
- ``large_image``: A string representing the ID for the large image asset.
- ``large_text``: A string representing the text when hovering over the large image asset.
- ``small_image``: A string representing the ID for the small image asset.
- ``small_text``: A string representing the text when hovering over the small image asset.
party: :class:`dict`
A dictionary representing the activity party. It contains the following optional keys:
- ``id``: A string representing the party ID.
- ``size``: A list of up to two integer elements denoting (current_size, maximum_size).
"""
__slots__ = (
"state",
"details",
"timestamps",
"assets",
"party",
"flags",
"sync_id",
"session_id",
"type",
"name",
"url",
"application_id",
)
def __init__(self, **kwargs):
self.state = kwargs.pop("state", None)
self.details = kwargs.pop("details", None)
self.timestamps = kwargs.pop("timestamps", {})
self.assets = kwargs.pop("assets", {})
self.party = kwargs.pop("party", {})
self.application_id = kwargs.pop("application_id", None)
self.name = kwargs.pop("name", None)
self.url = kwargs.pop("url", None)
self.flags = kwargs.pop("flags", 0)
self.sync_id = kwargs.pop("sync_id", None)
self.session_id = kwargs.pop("session_id", None)
self.type = try_enum(ActivityType, kwargs.pop("type", -1))
def to_dict(self):
ret = {}
for attr in self.__slots__:
value = getattr(self, attr, None)
if value is None:
continue
if isinstance(value, dict) and len(value) == 0:
continue
ret[attr] = value
ret["type"] = int(self.type)
return ret
@property
def start(self):
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable."""
try:
return datetime.datetime.utcfromtimestamp(self.timestamps["start"] / 1000)
except KeyError:
return None
@property
def end(self):
"""Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable."""
try:
return datetime.datetime.utcfromtimestamp(self.timestamps["end"] / 1000)
except KeyError:
return None
@property
def large_image_url(self):
"""Optional[:class:`str`]: Returns a URL pointing to the large image asset of this activity if applicable."""
if self.application_id is None:
return None
try:
large_image = self.assets["large_image"]
except KeyError:
return None
else:
return "https://cdn.discordapp.com/app-assets/{0}/{1}.png".format(
self.application_id, large_image
)
@property
def small_image_url(self):
"""Optional[:class:`str`]: Returns a URL pointing to the small image asset of this activity if applicable."""
if self.application_id is None:
return None
try:
small_image = self.assets["small_image"]
except KeyError:
return None
else:
return "https://cdn.discordapp.com/app-assets/{0}/{1}.png".format(
self.application_id, small_image
)
@property
def large_image_text(self):
"""Optional[:class:`str`]: Returns the large image asset hover text of this activity if applicable."""
return self.assets.get("large_text", None)
@property
def small_image_text(self):
"""Optional[:class:`str`]: Returns the small image asset hover text of this activity if applicable."""
return self.assets.get("small_text", None)
class Game(_ActivityTag):
"""A slimmed down version of :class:`Activity` that represents a Discord game.
This is typically displayed via **Playing** on the official Discord client.
.. container:: operations
.. describe:: x == y
Checks if two games are equal.
.. describe:: x != y
Checks if two games are not equal.
.. describe:: hash(x)
Returns the game's hash.
.. describe:: str(x)
Returns the game's name.
Parameters
-----------
name: :class:`str`
The game's name.
start: Optional[:class:`datetime.datetime`]
A naive UTC timestamp representing when the game started. Keyword-only parameter. Ignored for bots.
end: Optional[:class:`datetime.datetime`]
A naive UTC timestamp representing when the game ends. Keyword-only parameter. Ignored for bots.
Attributes
-----------
name: :class:`str`
The game's name.
"""
__slots__ = ("name", "_end", "_start")
def __init__(self, name, **extra):
self.name = name
try:
timestamps = extra["timestamps"]
except KeyError:
self._extract_timestamp(extra, "start")
self._extract_timestamp(extra, "end")
else:
self._start = timestamps.get("start", 0)
self._end = timestamps.get("end", 0)
def _extract_timestamp(self, data, key):
try:
dt = data[key]
except KeyError:
setattr(self, "_" + key, 0)
else:
setattr(self, "_" + key, dt.timestamp() * 1000.0)
@property
def type(self):
"""Returns the game's type. This is for compatibility with :class:`Activity`.
It always returns :attr:`ActivityType.playing`.
"""
return ActivityType.playing
@property
def start(self):
"""Optional[:class:`datetime.datetime`]: When the user started playing this game in UTC, if applicable."""
if self._start:
return datetime.datetime.utcfromtimestamp(self._start / 1000)
return None
@property
def end(self):
"""Optional[:class:`datetime.datetime`]: When the user will stop playing this game in UTC, if applicable."""
if self._end:
return datetime.datetime.utcfromtimestamp(self._end / 1000)
return None
def __str__(self):
return str(self.name)
def __repr__(self):
return "<Game name={0.name!r}>".format(self)
def to_dict(self):
timestamps = {}
if self._start:
timestamps["start"] = self._start
if self._end:
timestamps["end"] = self._end
return {
"type": ActivityType.playing.value,
"name": str(self.name),
"timestamps": timestamps,
}
def __eq__(self, other):
return isinstance(other, Game) and other.name == self.name
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self.name)
class Streaming(_ActivityTag):
"""A slimmed down version of :class:`Activity` that represents a Discord streaming status.
This is typically displayed via **Streaming** on the official Discord client.
.. container:: operations
.. describe:: x == y
Checks if two streams are equal.
.. describe:: x != y
Checks if two streams are not equal.
.. describe:: hash(x)
Returns the stream's hash.
.. describe:: str(x)
Returns the stream's name.
Attributes
-----------
name: :class:`str`
The stream's name.
url: :class:`str`
The stream's URL. Currently only twitch.tv URLs are supported. Anything else is silently
discarded.
details: Optional[:class:`str`]
If provided, typically the game the streamer is playing.
assets: :class:`dict`
A dictionary comprising of similar keys than those in :attr:`Activity.assets`.
"""
__slots__ = ("name", "url", "details", "assets")
def __init__(self, *, name, url, **extra):
self.name = name
self.url = url
self.details = extra.pop("details", None)
self.assets = extra.pop("assets", {})
@property
def type(self):
"""Returns the game's type. This is for compatibility with :class:`Activity`.
It always returns :attr:`ActivityType.streaming`.
"""
return ActivityType.streaming
def __str__(self):
return str(self.name)
def __repr__(self):
return "<Streaming name={0.name!r}>".format(self)
@property
def twitch_name(self):
"""Optional[:class:`str`]: If provided, the twitch name of the user streaming.
This corresponds to the ``large_image`` key of the :attr:`Streaming.assets`
dictionary if it starts with ``twitch:``. Typically set by the Discord client.
"""
try:
name = self.assets["large_image"]
except KeyError:
return None
else:
return name[7:] if name[:7] == "twitch:" else None
def to_dict(self):
ret = {
"type": ActivityType.streaming.value,
"name": str(self.name),
"url": str(self.url),
"assets": self.assets,
}
if self.details:
ret["details"] = self.details
return ret
def __eq__(self, other):
return isinstance(other, Streaming) and other.name == self.name and other.url == self.url
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self.name)
class Spotify:
"""Represents a Spotify listening activity from Discord. This is a special case of
:class:`Activity` that makes it easier to work with the Spotify integration.
.. container:: operations
.. describe:: x == y
Checks if two activities are equal.
.. describe:: x != y
Checks if two activities are not equal.
.. describe:: hash(x)
Returns the activity's hash.
.. describe:: str(x)
Returns the string 'Spotify'.
"""
__slots__ = (
"_state",
"_details",
"_timestamps",
"_assets",
"_party",
"_sync_id",
"_session_id",
)
def __init__(self, **data):
self._state = data.pop("state", None)
self._details = data.pop("details", None)
self._timestamps = data.pop("timestamps", {})
self._assets = data.pop("assets", {})
self._party = data.pop("party", {})
self._sync_id = data.pop("sync_id")
self._session_id = data.pop("session_id")
@property
def type(self):
"""Returns the activity's type. This is for compatibility with :class:`Activity`.
It always returns :attr:`ActivityType.listening`.
"""
return ActivityType.listening
@property
def colour(self):
"""Returns the Spotify integration colour, as a :class:`Colour`.
There is an alias for this named :meth:`color`"""
return Colour(0x1DB954)
@property
def color(self):
"""Returns the Spotify integration colour, as a :class:`Colour`.
There is an alias for this named :meth:`colour`"""
return self.colour
def to_dict(self):
return {
"flags": 48, # SYNC | PLAY
"name": "Spotify",
"assets": self._assets,
"party": self._party,
"sync_id": self._sync_id,
"session_id": self._session_id,
"timestamps": self._timestamps,
"details": self._details,
"state": self._state,
}
@property
def name(self):
""":class:`str`: The activity's name. This will always return "Spotify"."""
return "Spotify"
def __eq__(self, other):
return isinstance(other, Spotify) and other._session_id == self._session_id
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self._session_id)
def __str__(self):
return "Spotify"
def __repr__(self):
return "<Spotify title={0.title!r} artist={0.artist!r} track_id={0.track_id!r}>".format(
self
)
@property
def title(self):
""":class:`str`: The title of the song being played."""
return self._details
@property
def artists(self):
"""List[:class:`str`]: The artists of the song being played."""
return self._state.split("; ")
@property
def artist(self):
""":class:`str`: The artist of the song being played.
This does not attempt to split the artist information into
multiple artists. Useful if there's only a single artist.
"""
return self._state
@property
def album(self):
""":class:`str`: The album that the song being played belongs to."""
return self._assets.get("large_text", "")
@property
def album_cover_url(self):
""":class:`str`: The album cover image URL from Spotify's CDN."""
large_image = self._assets.get("large_image", "")
if large_image[:8] != "spotify:":
return ""
album_image_id = large_image[8:]
return "https://i.scdn.co/image/" + album_image_id
@property
def track_id(self):
""":class:`str`: The track ID used by Spotify to identify this song."""
return self._sync_id
@property
def start(self):
""":class:`datetime.datetime`: When the user started playing this song in UTC."""
return datetime.datetime.utcfromtimestamp(self._timestamps["start"] / 1000)
@property
def end(self):
""":class:`datetime.datetime`: When the user will stop playing this song in UTC."""
return datetime.datetime.utcfromtimestamp(self._timestamps["end"] / 1000)
@property
def duration(self):
""":class:`datetime.timedelta`: The duration of the song being played."""
return self.end - self.start
@property
def party_id(self):
""":class:`str`: The party ID of the listening party."""
return self._party.get("id", "")
def create_activity(data):
if not data:
return None
game_type = try_enum(ActivityType, data.get("type", -1))
if game_type is ActivityType.playing:
if "application_id" in data or "session_id" in data:
return Activity(**data)
return Game(**data)
elif game_type is ActivityType.streaming:
if "url" in data:
return Streaming(**data)
return Activity(**data)
elif game_type is ActivityType.listening and "sync_id" in data and "session_id" in data:
return Spotify(**data)
return Activity(**data)

View File

@@ -1,366 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from . import utils, enums
from .object import Object
from .permissions import PermissionOverwrite, Permissions
from .colour import Colour
from .invite import Invite
def _transform_verification_level(entry, data):
return enums.try_enum(enums.VerificationLevel, data)
def _transform_default_notifications(entry, data):
return enums.try_enum(enums.NotificationLevel, data)
def _transform_explicit_content_filter(entry, data):
return enums.try_enum(enums.ContentFilter, data)
def _transform_permissions(entry, data):
return Permissions(data)
def _transform_color(entry, data):
return Colour(data)
def _transform_snowflake(entry, data):
return int(data)
def _transform_channel(entry, data):
if data is None:
return None
channel = entry.guild.get_channel(int(data)) or Object(id=data)
return channel
def _transform_owner_id(entry, data):
if data is None:
return None
return entry._get_member(int(data))
def _transform_inviter_id(entry, data):
if data is None:
return None
return entry._get_member(int(data))
def _transform_overwrites(entry, data):
overwrites = []
for elem in data:
allow = Permissions(elem["allow"])
deny = Permissions(elem["deny"])
ow = PermissionOverwrite.from_pair(allow, deny)
ow_type = elem["type"]
ow_id = int(elem["id"])
if ow_type == "role":
target = entry.guild.get_role(ow_id)
else:
target = entry._get_member(ow_id)
if target is None:
target = Object(id=ow_id)
overwrites.append((target, ow))
return overwrites
class AuditLogDiff:
def __len__(self):
return len(self.__dict__)
def __iter__(self):
return iter(self.__dict__.items())
def __repr__(self):
return "<AuditLogDiff attrs={0!r}>".format(tuple(self.__dict__))
class AuditLogChanges:
TRANSFORMERS = {
"verification_level": (None, _transform_verification_level),
"explicit_content_filter": (None, _transform_explicit_content_filter),
"allow": (None, _transform_permissions),
"deny": (None, _transform_permissions),
"permissions": (None, _transform_permissions),
"id": (None, _transform_snowflake),
"color": ("colour", _transform_color),
"owner_id": ("owner", _transform_owner_id),
"inviter_id": ("inviter", _transform_inviter_id),
"channel_id": ("channel", _transform_channel),
"afk_channel_id": ("afk_channel", _transform_channel),
"system_channel_id": ("system_channel", _transform_channel),
"widget_channel_id": ("widget_channel", _transform_channel),
"permission_overwrites": ("overwrites", _transform_overwrites),
"splash_hash": ("splash", None),
"icon_hash": ("icon", None),
"avatar_hash": ("avatar", None),
"rate_limit_per_user": ("slowmode_delay", None),
"default_message_notifications": (
"default_notifications",
_transform_default_notifications,
),
}
def __init__(self, entry, data):
self.before = AuditLogDiff()
self.after = AuditLogDiff()
for elem in data:
attr = elem["key"]
# special cases for role add/remove
if attr == "$add":
self._handle_role(self.before, self.after, entry, elem["new_value"])
continue
elif attr == "$remove":
self._handle_role(self.after, self.before, entry, elem["new_value"])
continue
transformer = self.TRANSFORMERS.get(attr)
if transformer:
key, transformer = transformer
if key:
attr = key
try:
before = elem["old_value"]
except KeyError:
before = None
else:
if transformer:
before = transformer(entry, before)
setattr(self.before, attr, before)
try:
after = elem["new_value"]
except KeyError:
after = None
else:
if transformer:
after = transformer(entry, after)
setattr(self.after, attr, after)
# add an alias
if hasattr(self.after, "colour"):
self.after.color = self.after.colour
self.before.color = self.before.colour
def _handle_role(self, first, second, entry, elem):
if not hasattr(first, "roles"):
setattr(first, "roles", [])
data = []
g = entry.guild
for e in elem:
role_id = int(e["id"])
role = g.get_role(role_id)
if role is None:
role = Object(id=role_id)
role.name = e["name"]
data.append(role)
setattr(second, "roles", data)
class AuditLogEntry:
r"""Represents an Audit Log entry.
You retrieve these via :meth:`Guild.audit_logs`.
Attributes
-----------
action: :class:`AuditLogAction`
The action that was done.
user: :class:`abc.User`
The user who initiated this action. Usually a :class:`Member`\, unless gone
then it's a :class:`User`.
id: :class:`int`
The entry ID.
target: Any
The target that got changed. The exact type of this depends on
the action being done.
reason: Optional[:class:`str`]
The reason this action was done.
extra: Any
Extra information that this entry has that might be useful.
For most actions, this is ``None``. However in some cases it
contains extra information. See :class:`AuditLogAction` for
which actions have this field filled out.
"""
def __init__(self, *, users, data, guild):
self._state = guild._state
self.guild = guild
self._users = users
self._from_data(data)
def _from_data(self, data):
self.action = enums.AuditLogAction(data["action_type"])
self.id = int(data["id"])
# this key is technically not usually present
self.reason = data.get("reason")
self.extra = data.get("options")
if self.extra:
if self.action is enums.AuditLogAction.member_prune:
# member prune has two keys with useful information
self.extra = type(
"_AuditLogProxy", (), {k: int(v) for k, v in self.extra.items()}
)()
elif self.action is enums.AuditLogAction.message_delete:
channel_id = int(self.extra["channel_id"])
elems = {
"count": int(self.extra["count"]),
"channel": self.guild.get_channel(channel_id) or Object(id=channel_id),
}
self.extra = type("_AuditLogProxy", (), elems)()
elif self.action.name.startswith("overwrite_"):
# the overwrite_ actions have a dict with some information
instance_id = int(self.extra["id"])
the_type = self.extra.get("type")
if the_type == "member":
self.extra = self._get_member(instance_id)
else:
role = self.guild.get_role(instance_id)
if role is None:
role = Object(id=instance_id)
role.name = self.extra.get("role_name")
self.extra = role
# this key is not present when the above is present, typically.
# It's a list of { new_value: a, old_value: b, key: c }
# where new_value and old_value are not guaranteed to be there depending
# on the action type, so let's just fetch it for now and only turn it
# into meaningful data when requested
self._changes = data.get("changes", [])
self.user = self._get_member(utils._get_as_snowflake(data, "user_id"))
self._target_id = utils._get_as_snowflake(data, "target_id")
def _get_member(self, user_id):
return self.guild.get_member(user_id) or self._users.get(user_id)
def __repr__(self):
return "<AuditLogEntry id={0.id} action={0.action} user={0.user!r}>".format(self)
@utils.cached_property
def created_at(self):
"""Returns the entry's creation time in UTC."""
return utils.snowflake_time(self.id)
@utils.cached_property
def target(self):
try:
converter = getattr(self, "_convert_target_" + self.action.target_type)
except AttributeError:
return Object(id=self._target_id)
else:
return converter(self._target_id)
@utils.cached_property
def category(self):
"""Optional[:class:`AuditLogActionCategory`]: The category of the action, if applicable."""
return self.action.category
@utils.cached_property
def changes(self):
""":class:`AuditLogChanges`: The list of changes this entry has."""
obj = AuditLogChanges(self, self._changes)
del self._changes
return obj
@utils.cached_property
def before(self):
""":class:`AuditLogDiff`: The target's prior state."""
return self.changes.before
@utils.cached_property
def after(self):
""":class:`AuditLogDiff`: The target's subsequent state."""
return self.changes.after
def _convert_target_guild(self, target_id):
return self.guild
def _convert_target_channel(self, target_id):
ch = self.guild.get_channel(target_id)
if ch is None:
return Object(id=target_id)
return ch
def _convert_target_user(self, target_id):
return self._get_member(target_id)
def _convert_target_role(self, target_id):
role = self.guild.get_role(target_id)
if role is None:
return Object(id=target_id)
return role
def _convert_target_invite(self, target_id):
# invites have target_id set to null
# so figure out which change has the full invite data
changeset = (
self.before if self.action is enums.AuditLogAction.invite_delete else self.after
)
fake_payload = {
"max_age": changeset.max_age,
"max_uses": changeset.max_uses,
"code": changeset.code,
"temporary": changeset.temporary,
"channel": changeset.channel,
"uses": changeset.uses,
"guild": self.guild,
}
obj = Invite(state=self._state, data=fake_payload)
try:
obj.inviter = changeset.inviter
except AttributeError:
pass
return obj
def _convert_target_emoji(self, target_id):
return self._state.get_emoji(target_id) or Object(id=target_id)
def _convert_target_message(self, target_id):
return self._get_member(target_id)

View File

@@ -1,86 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import time
import random
class ExponentialBackoff:
"""An implementation of the exponential backoff algorithm
Provides a convenient interface to implement an exponential backoff
for reconnecting or retrying transmissions in a distributed network.
Once instantiated, the delay method will return the next interval to
wait for when retrying a connection or transmission. The maximum
delay increases exponentially with each retry up to a maximum of
2^10 * base, and is reset if no more attempts are needed in a period
of 2^11 * base seconds.
Parameters
----------
base: int
The base delay in seconds. The first retry-delay will be up to
this many seconds.
integral: bool
Set to True if whole periods of base is desirable, otherwise any
number in between may be returned.
"""
def __init__(self, base=1, *, integral=False):
self._base = base
self._exp = 0
self._max = 10
self._reset_time = base * 2 ** 11
self._last_invocation = time.monotonic()
# Use our own random instance to avoid messing with global one
rand = random.Random()
rand.seed()
self._randfunc = rand.randrange if integral else rand.uniform
def delay(self):
"""Compute the next delay
Returns the next delay to wait according to the exponential
backoff algorithm. This is a value between 0 and base * 2^exp
where exponent starts off at 1 and is incremented at every
invocation of this method up to a maximum of 10.
If a period of more than base * 2^11 has passed since the last
retry, the exponent is reset to 1.
"""
invocation = time.monotonic()
interval = invocation - self._last_invocation
self._last_invocation = invocation
if interval > self._reset_time:
self._exp = 0
self._exp = min(self._exp + 1, self._max)
return self._randfunc(0, self._base * 2 ** self._exp)

View File

@@ -1,157 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import datetime
from . import utils
from .enums import VoiceRegion, try_enum
from .member import VoiceState
class CallMessage:
"""Represents a group call message from Discord.
This is only received in cases where the message type is equivalent to
:attr:`MessageType.call`.
Attributes
-----------
ended_timestamp: Optional[datetime.datetime]
A naive UTC datetime object that represents the time that the call has ended.
participants: List[:class:`User`]
The list of users that are participating in this call.
message: :class:`Message`
The message associated with this call message.
"""
def __init__(self, message, **kwargs):
self.message = message
self.ended_timestamp = utils.parse_time(kwargs.get("ended_timestamp"))
self.participants = kwargs.get("participants")
@property
def call_ended(self):
""":obj:`bool`: Indicates if the call has ended."""
return self.ended_timestamp is not None
@property
def channel(self):
r""":class:`GroupChannel`\: The private channel associated with this message."""
return self.message.channel
@property
def duration(self):
"""Queries the duration of the call.
If the call has not ended then the current duration will
be returned.
Returns
---------
datetime.timedelta
The timedelta object representing the duration.
"""
if self.ended_timestamp is None:
return datetime.datetime.utcnow() - self.message.created_at
else:
return self.ended_timestamp - self.message.created_at
class GroupCall:
"""Represents the actual group call from Discord.
This is accompanied with a :class:`CallMessage` denoting the information.
Attributes
-----------
call: :class:`CallMessage`
The call message associated with this group call.
unavailable: :obj:`bool`
Denotes if this group call is unavailable.
ringing: List[:class:`User`]
A list of users that are currently being rung to join the call.
region: :class:`VoiceRegion`
The guild region the group call is being hosted on.
"""
def __init__(self, **kwargs):
self.call = kwargs.get("call")
self.unavailable = kwargs.get("unavailable")
self._voice_states = {}
for state in kwargs.get("voice_states", []):
self._update_voice_state(state)
self._update(**kwargs)
def _update(self, **kwargs):
self.region = try_enum(VoiceRegion, kwargs.get("region"))
lookup = {u.id: u for u in self.call.channel.recipients}
me = self.call.channel.me
lookup[me.id] = me
self.ringing = list(filter(None, map(lookup.get, kwargs.get("ringing", []))))
def _update_voice_state(self, data):
user_id = int(data["user_id"])
# left the voice channel?
if data["channel_id"] is None:
self._voice_states.pop(user_id, None)
else:
self._voice_states[user_id] = VoiceState(data=data, channel=self.channel)
@property
def connected(self):
"""A property that returns the :obj:`list` of :class:`User` that are currently in this call."""
ret = [u for u in self.channel.recipients if self.voice_state_for(u) is not None]
me = self.channel.me
if self.voice_state_for(me) is not None:
ret.append(me)
return ret
@property
def channel(self):
r""":class:`GroupChannel`\: Returns the channel the group call is in."""
return self.call.channel
def voice_state_for(self, user):
"""Retrieves the :class:`VoiceState` for a specified :class:`User`.
If the :class:`User` has no voice state then this function returns
``None``.
Parameters
------------
user: :class:`User`
The user to retrieve the voice state for.
Returns
--------
Optional[:class:`VoiceState`]
The voice state associated with this user.
"""
return self._voice_states.get(user.id)

View File

@@ -1,986 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import time
import asyncio
import discord.abc
from .permissions import Permissions
from .enums import ChannelType, try_enum
from .mixins import Hashable
from . import utils
from .errors import ClientException, NoMoreItems
from .webhook import Webhook
__all__ = [
"TextChannel",
"VoiceChannel",
"DMChannel",
"CategoryChannel",
"GroupChannel",
"_channel_factory",
]
async def _single_delete_strategy(messages):
for m in messages:
await m.delete()
class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
"""Represents a Discord guild text channel.
.. container:: operations
.. describe:: x == y
Checks if two channels are equal.
.. describe:: x != y
Checks if two channels are not equal.
.. describe:: hash(x)
Returns the channel's hash.
.. describe:: str(x)
Returns the channel's name.
Attributes
-----------
name: :class:`str`
The channel name.
guild: :class:`Guild`
The guild the channel belongs to.
id: :class:`int`
The channel ID.
category_id: :class:`int`
The category channel ID this channel belongs to.
topic: Optional[:class:`str`]
The channel's topic. None if it doesn't exist.
position: :class:`int`
The position in the channel list. This is a number that starts at 0. e.g. the
top channel is position 0.
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this channel. A value of `0` denotes that it is disabled.
Bots and users with :attr:`~Permissions.manage_channels` or
:attr:`~Permissions.manage_messages` bypass slowmode.
"""
__slots__ = (
"name",
"id",
"guild",
"topic",
"_state",
"nsfw",
"category_id",
"position",
"slowmode_delay",
"_overwrites",
)
def __init__(self, *, state, guild, data):
self._state = state
self.id = int(data["id"])
self._update(guild, data)
def __repr__(self):
return "<TextChannel id={0.id} name={0.name!r} position={0.position}>".format(self)
def _update(self, guild, data):
self.guild = guild
self.name = data["name"]
self.category_id = utils._get_as_snowflake(data, "parent_id")
self.topic = data.get("topic")
self.position = data["position"]
self.nsfw = data.get("nsfw", False)
# Does this need coercion into `int`? No idea yet.
self.slowmode_delay = data.get("rate_limit_per_user", 0)
self._fill_overwrites(data)
async def _get_channel(self):
return self
def permissions_for(self, member):
base = super().permissions_for(member)
# text channels do not have voice related permissions
denied = Permissions.voice()
base.value &= ~denied.value
return base
permissions_for.__doc__ = discord.abc.GuildChannel.permissions_for.__doc__
@property
def members(self):
"""Returns a :class:`list` of :class:`Member` that can see this channel."""
return [m for m in self.guild.members if self.permissions_for(m).read_messages]
def is_nsfw(self):
"""Checks if the channel is NSFW."""
n = self.name
return self.nsfw or n == "nsfw" or n[:5] == "nsfw-"
async def edit(self, *, reason=None, **options):
"""|coro|
Edits the channel.
You must have the :attr:`~Permissions.manage_channels` permission to
use this.
Parameters
----------
name: :class:`str`
The new channel name.
topic: :class:`str`
The new channel's topic.
position: :class:`int`
The new channel's position.
nsfw: :class:`bool`
To mark the channel as NSFW or not.
sync_permissions: :class:`bool`
Whether to sync permissions with the channel's new or pre-existing
category. Defaults to ``False``.
category: Optional[:class:`CategoryChannel`]
The new category for this channel. Can be ``None`` to remove the
category.
slowmode_delay: :class:`int`
Specifies the slowmode rate limit for user in this channel. A value of
`0` disables slowmode. The maximum value possible is `120`.
reason: Optional[:class:`str`]
The reason for editing this channel. Shows up on the audit log.
Raises
------
InvalidArgument
If position is less than 0 or greater than the number of channels.
Forbidden
You do not have permissions to edit the channel.
HTTPException
Editing the channel failed.
"""
await self._edit(options, reason=reason)
async def delete_messages(self, messages):
"""|coro|
Deletes a list of messages. This is similar to :meth:`Message.delete`
except it bulk deletes multiple messages.
As a special case, if the number of messages is 0, then nothing
is done. If the number of messages is 1 then single message
delete is done. If it's more than two, then bulk delete is used.
You cannot bulk delete more than 100 messages or messages that
are older than 14 days old.
You must have the :attr:`~Permissions.manage_messages` permission to
use this.
Usable only by bot accounts.
Parameters
-----------
messages: Iterable[:class:`abc.Snowflake`]
An iterable of messages denoting which ones to bulk delete.
Raises
------
ClientException
The number of messages to delete was more than 100.
Forbidden
You do not have proper permissions to delete the messages or
you're not using a bot account.
HTTPException
Deleting the messages failed.
"""
if not isinstance(messages, (list, tuple)):
messages = list(messages)
if len(messages) == 0:
return # do nothing
if len(messages) == 1:
message_id = messages[0].id
await self._state.http.delete_message(self.id, message_id)
return
if len(messages) > 100:
raise ClientException("Can only bulk delete messages up to 100 messages")
message_ids = [m.id for m in messages]
await self._state.http.delete_messages(self.id, message_ids)
async def purge(
self,
*,
limit=100,
check=None,
before=None,
after=None,
around=None,
reverse=False,
bulk=True
):
"""|coro|
Purges a list of messages that meet the criteria given by the predicate
``check``. If a ``check`` is not provided then all messages are deleted
without discrimination.
You must have the :attr:`~Permissions.manage_messages` permission to
delete messages even if they are your own (unless you are a user
account). The :attr:`~Permissions.read_message_history` permission is
also needed to retrieve message history.
Internally, this employs a different number of strategies depending
on the conditions met such as if a bulk delete is possible or if
the account is a user bot or not.
Parameters
-----------
limit: int
The number of messages to search through. This is not the number
of messages that will be deleted, though it can be.
check: predicate
The function used to check if a message should be deleted.
It must take a :class:`Message` as its sole parameter.
before
Same as ``before`` in :meth:`history`.
after
Same as ``after`` in :meth:`history`.
around
Same as ``around`` in :meth:`history`.
reverse
Same as ``reverse`` in :meth:`history`.
bulk: bool
If True, use bulk delete. bulk=False is useful for mass-deleting
a bot's own messages without manage_messages. When True, will fall
back to single delete if current account is a user bot, or if
messages are older than two weeks.
Raises
-------
Forbidden
You do not have proper permissions to do the actions required.
HTTPException
Purging the messages failed.
Examples
---------
Deleting bot's messages ::
def is_me(m):
return m.author == client.user
deleted = await channel.purge(limit=100, check=is_me)
await channel.send('Deleted {} message(s)'.format(len(deleted)))
Returns
--------
list
The list of messages that were deleted.
"""
if check is None:
check = lambda m: True
iterator = self.history(
limit=limit, before=before, after=after, reverse=reverse, around=around
)
ret = []
count = 0
minimum_time = int((time.time() - 14 * 24 * 60 * 60) * 1000.0 - 1420070400000) << 22
strategy = self.delete_messages if self._state.is_bot and bulk else _single_delete_strategy
while True:
try:
msg = await iterator.next()
except NoMoreItems:
# no more messages to poll
if count >= 2:
# more than 2 messages -> bulk delete
to_delete = ret[-count:]
await strategy(to_delete)
elif count == 1:
# delete a single message
await ret[-1].delete()
return ret
else:
if count == 100:
# we've reached a full 'queue'
to_delete = ret[-100:]
await strategy(to_delete)
count = 0
await asyncio.sleep(1)
if check(msg):
if msg.id < minimum_time:
# older than 14 days old
if count == 1:
await ret[-1].delete()
elif count >= 2:
to_delete = ret[-count:]
await strategy(to_delete)
count = 0
strategy = _single_delete_strategy
count += 1
ret.append(msg)
async def webhooks(self):
"""|coro|
Gets the list of webhooks from this channel.
Requires :attr:`~.Permissions.manage_webhooks` permissions.
Raises
-------
Forbidden
You don't have permissions to get the webhooks.
Returns
--------
List[:class:`Webhook`]
The webhooks for this channel.
"""
data = await self._state.http.channel_webhooks(self.id)
return [Webhook.from_state(d, state=self._state) for d in data]
async def create_webhook(self, *, name, avatar=None):
"""|coro|
Creates a webhook for this channel.
Requires :attr:`~.Permissions.manage_webhooks` permissions.
Parameters
-------------
name: str
The webhook's name.
avatar: Optional[bytes]
A :term:`py:bytes-like object` representing the webhook's default avatar.
This operates similarly to :meth:`~ClientUser.edit`.
Raises
-------
HTTPException
Creating the webhook failed.
Forbidden
You do not have permissions to create a webhook.
Returns
--------
:class:`Webhook`
The created webhook.
"""
if avatar is not None:
avatar = utils._bytes_to_base64_data(avatar)
data = await self._state.http.create_webhook(self.id, name=str(name), avatar=avatar)
return Webhook.from_state(data, state=self._state)
class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
"""Represents a Discord guild voice channel.
.. container:: operations
.. describe:: x == y
Checks if two channels are equal.
.. describe:: x != y
Checks if two channels are not equal.
.. describe:: hash(x)
Returns the channel's hash.
.. describe:: str(x)
Returns the channel's name.
Attributes
-----------
name: :class:`str`
The channel name.
guild: :class:`Guild`
The guild the channel belongs to.
id: :class:`int`
The channel ID.
category_id: :class:`int`
The category channel ID this channel belongs to.
position: :class:`int`
The position in the channel list. This is a number that starts at 0. e.g. the
top channel is position 0.
bitrate: :class:`int`
The channel's preferred audio bitrate in bits per second.
user_limit: :class:`int`
The channel's limit for number of members that can be in a voice channel.
"""
__slots__ = (
"name",
"id",
"guild",
"bitrate",
"user_limit",
"_state",
"position",
"_overwrites",
"category_id",
)
def __init__(self, *, state, guild, data):
self._state = state
self.id = int(data["id"])
self._update(guild, data)
def __repr__(self):
return "<VoiceChannel id={0.id} name={0.name!r} position={0.position}>".format(self)
def _get_voice_client_key(self):
return self.guild.id, "guild_id"
def _get_voice_state_pair(self):
return self.guild.id, self.id
def _update(self, guild, data):
self.guild = guild
self.name = data["name"]
self.category_id = utils._get_as_snowflake(data, "parent_id")
self.position = data["position"]
self.bitrate = data.get("bitrate")
self.user_limit = data.get("user_limit")
self._fill_overwrites(data)
@property
def members(self):
"""Returns a list of :class:`Member` that are currently inside this voice channel."""
ret = []
for user_id, state in self.guild._voice_states.items():
if state.channel.id == self.id:
member = self.guild.get_member(user_id)
if member is not None:
ret.append(member)
return ret
def permissions_for(self, member):
base = super().permissions_for(member)
# voice channels cannot be edited by people who can't connect to them
# It also implicitly denies all other voice perms
if not base.connect:
denied = Permissions.voice()
denied.update(manage_channels=True, manage_roles=True)
base.value &= ~denied.value
return base
permissions_for.__doc__ = discord.abc.GuildChannel.permissions_for.__doc__
async def edit(self, *, reason=None, **options):
"""|coro|
Edits the channel.
You must have the :attr:`~Permissions.manage_channels` permission to
use this.
Parameters
----------
name: str
The new channel's name.
bitrate: int
The new channel's bitrate.
user_limit: int
The new channel's user limit.
position: int
The new channel's position.
sync_permissions: bool
Whether to sync permissions with the channel's new or pre-existing
category. Defaults to ``False``.
category: Optional[:class:`CategoryChannel`]
The new category for this channel. Can be ``None`` to remove the
category.
reason: Optional[str]
The reason for editing this channel. Shows up on the audit log.
Raises
------
Forbidden
You do not have permissions to edit the channel.
HTTPException
Editing the channel failed.
"""
await self._edit(options, reason=reason)
class CategoryChannel(discord.abc.GuildChannel, Hashable):
"""Represents a Discord channel category.
These are useful to group channels to logical compartments.
.. container:: operations
.. describe:: x == y
Checks if two channels are equal.
.. describe:: x != y
Checks if two channels are not equal.
.. describe:: hash(x)
Returns the category's hash.
.. describe:: str(x)
Returns the category's name.
Attributes
-----------
name: :class:`str`
The category name.
guild: :class:`Guild`
The guild the category belongs to.
id: :class:`int`
The category channel ID.
position: :class:`int`
The position in the category list. This is a number that starts at 0. e.g. the
top category is position 0.
"""
__slots__ = ("name", "id", "guild", "nsfw", "_state", "position", "_overwrites", "category_id")
def __init__(self, *, state, guild, data):
self._state = state
self.id = int(data["id"])
self._update(guild, data)
def __repr__(self):
return "<CategoryChannel id={0.id} name={0.name!r} position={0.position}>".format(self)
def _update(self, guild, data):
self.guild = guild
self.name = data["name"]
self.category_id = utils._get_as_snowflake(data, "parent_id")
self.nsfw = data.get("nsfw", False)
self.position = data["position"]
self._fill_overwrites(data)
def is_nsfw(self):
"""Checks if the category is NSFW."""
n = self.name
return self.nsfw or n == "nsfw" or n[:5] == "nsfw-"
async def edit(self, *, reason=None, **options):
"""|coro|
Edits the channel.
You must have the :attr:`~Permissions.manage_channels` permission to
use this.
Parameters
----------
name: str
The new category's name.
position: int
The new category's position.
nsfw: bool
To mark the category as NSFW or not.
reason: Optional[str]
The reason for editing this category. Shows up on the audit log.
Raises
------
InvalidArgument
If position is less than 0 or greater than the number of categories.
Forbidden
You do not have permissions to edit the category.
HTTPException
Editing the category failed.
"""
try:
position = options.pop("position")
except KeyError:
pass
else:
await self._move(position, reason=reason)
self.position = position
if options:
data = await self._state.http.edit_channel(self.id, reason=reason, **options)
self._update(self.guild, data)
@property
def channels(self):
"""List[:class:`abc.GuildChannel`]: Returns the channels that are under this category.
These are sorted by the official Discord UI, which places voice channels below the text channels.
"""
def comparator(channel):
return (not isinstance(channel, TextChannel), channel.position)
ret = [c for c in self.guild.channels if c.category_id == self.id]
ret.sort(key=comparator)
return ret
class DMChannel(discord.abc.Messageable, Hashable):
"""Represents a Discord direct message channel.
.. container:: operations
.. describe:: x == y
Checks if two channels are equal.
.. describe:: x != y
Checks if two channels are not equal.
.. describe:: hash(x)
Returns the channel's hash.
.. describe:: str(x)
Returns a string representation of the channel
Attributes
----------
recipient: :class:`User`
The user you are participating with in the direct message channel.
me: :class:`ClientUser`
The user presenting yourself.
id: :class:`int`
The direct message channel ID.
"""
__slots__ = ("id", "recipient", "me", "_state")
def __init__(self, *, me, state, data):
self._state = state
self.recipient = state.store_user(data["recipients"][0])
self.me = me
self.id = int(data["id"])
async def _get_channel(self):
return self
def __str__(self):
return "Direct Message with %s" % self.recipient
def __repr__(self):
return "<DMChannel id={0.id} recipient={0.recipient!r}>".format(self)
@property
def created_at(self):
"""Returns the direct message channel's creation time in UTC."""
return utils.snowflake_time(self.id)
def permissions_for(self, user=None):
"""Handles permission resolution for a :class:`User`.
This function is there for compatibility with other channel types.
Actual direct messages do not really have the concept of permissions.
This returns all the Text related permissions set to true except:
- send_tts_messages: You cannot send TTS messages in a DM.
- manage_messages: You cannot delete others messages in a DM.
Parameters
-----------
user: :class:`User`
The user to check permissions for. This parameter is ignored
but kept for compatibility.
Returns
--------
:class:`Permissions`
The resolved permissions.
"""
base = Permissions.text()
base.send_tts_messages = False
base.manage_messages = False
return base
class GroupChannel(discord.abc.Messageable, Hashable):
"""Represents a Discord group channel.
.. container:: operations
.. describe:: x == y
Checks if two channels are equal.
.. describe:: x != y
Checks if two channels are not equal.
.. describe:: hash(x)
Returns the channel's hash.
.. describe:: str(x)
Returns a string representation of the channel
Attributes
----------
recipients: :class:`list` of :class:`User`
The users you are participating with in the group channel.
me: :class:`ClientUser`
The user presenting yourself.
id: :class:`int`
The group channel ID.
owner: :class:`User`
The user that owns the group channel.
icon: Optional[:class:`str`]
The group channel's icon hash if provided.
name: Optional[:class:`str`]
The group channel's name if provided.
"""
__slots__ = ("id", "recipients", "owner", "icon", "name", "me", "_state")
def __init__(self, *, me, state, data):
self._state = state
self.id = int(data["id"])
self.me = me
self._update_group(data)
def _update_group(self, data):
owner_id = utils._get_as_snowflake(data, "owner_id")
self.icon = data.get("icon")
self.name = data.get("name")
try:
self.recipients = [self._state.store_user(u) for u in data["recipients"]]
except KeyError:
pass
if owner_id == self.me.id:
self.owner = self.me
else:
self.owner = utils.find(lambda u: u.id == owner_id, self.recipients)
async def _get_channel(self):
return self
def __str__(self):
if self.name:
return self.name
if len(self.recipients) == 0:
return "Unnamed"
return ", ".join(map(lambda x: x.name, self.recipients))
def __repr__(self):
return "<GroupChannel id={0.id} name={0.name!r}>".format(self)
@property
def icon_url(self):
"""Returns the channel's icon URL if available or an empty string otherwise."""
if self.icon is None:
return ""
return "https://cdn.discordapp.com/channel-icons/{0.id}/{0.icon}.jpg".format(self)
@property
def created_at(self):
"""Returns the channel's creation time in UTC."""
return utils.snowflake_time(self.id)
def permissions_for(self, user):
"""Handles permission resolution for a :class:`User`.
This function is there for compatibility with other channel types.
Actual direct messages do not really have the concept of permissions.
This returns all the Text related permissions set to true except:
- send_tts_messages: You cannot send TTS messages in a DM.
- manage_messages: You cannot delete others messages in a DM.
This also checks the kick_members permission if the user is the owner.
Parameters
-----------
user: :class:`User`
The user to check permissions for.
Returns
--------
:class:`Permissions`
The resolved permissions for the user.
"""
base = Permissions.text()
base.send_tts_messages = False
base.manage_messages = False
base.mention_everyone = True
if user.id == self.owner.id:
base.kick_members = True
return base
async def add_recipients(self, *recipients):
r"""|coro|
Adds recipients to this group.
A group can only have a maximum of 10 members.
Attempting to add more ends up in an exception. To
add a recipient to the group, you must have a relationship
with the user of type :attr:`RelationshipType.friend`.
Parameters
-----------
\*recipients: :class:`User`
An argument list of users to add to this group.
Raises
-------
HTTPException
Adding a recipient to this group failed.
"""
# TODO: wait for the corresponding WS event
req = self._state.http.add_group_recipient
for recipient in recipients:
await req(self.id, recipient.id)
async def remove_recipients(self, *recipients):
r"""|coro|
Removes recipients from this group.
Parameters
-----------
\*recipients: :class:`User`
An argument list of users to remove from this group.
Raises
-------
HTTPException
Removing a recipient from this group failed.
"""
# TODO: wait for the corresponding WS event
req = self._state.http.remove_group_recipient
for recipient in recipients:
await req(self.id, recipient.id)
async def edit(self, **fields):
"""|coro|
Edits the group.
Parameters
-----------
name: Optional[str]
The new name to change the group to.
Could be ``None`` to remove the name.
icon: Optional[bytes]
A :term:`py:bytes-like object` representing the new icon.
Could be ``None`` to remove the icon.
Raises
-------
HTTPException
Editing the group failed.
"""
try:
icon_bytes = fields["icon"]
except KeyError:
pass
else:
if icon_bytes is not None:
fields["icon"] = utils._bytes_to_base64_data(icon_bytes)
data = await self._state.http.edit_group(self.id, **fields)
self._update_group(data)
async def leave(self):
"""|coro|
Leave the group.
If you are the only one in the group, this deletes it as well.
Raises
-------
HTTPException
Leaving the group failed.
"""
await self._state.http.leave_group(self.id)
def _channel_factory(channel_type):
value = try_enum(ChannelType, channel_type)
if value is ChannelType.text:
return TextChannel, value
elif value is ChannelType.voice:
return VoiceChannel, value
elif value is ChannelType.private:
return DMChannel, value
elif value is ChannelType.category:
return CategoryChannel, value
elif value is ChannelType.group:
return GroupChannel, value
else:
return None, value

File diff suppressed because it is too large Load Diff

View File

@@ -1,234 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import colorsys
class Colour:
"""Represents a Discord role colour. This class is similar
to an (red, green, blue) :class:`tuple`.
There is an alias for this called Color.
.. container:: operations
.. describe:: x == y
Checks if two colours are equal.
.. describe:: x != y
Checks if two colours are not equal.
.. describe:: hash(x)
Return the colour's hash.
.. describe:: str(x)
Returns the hex format for the colour.
Attributes
------------
value: :class:`int`
The raw integer colour value.
"""
__slots__ = ("value",)
def __init__(self, value):
if not isinstance(value, int):
raise TypeError(
"Expected int parameter, received %s instead." % value.__class__.__name__
)
self.value = value
def _get_byte(self, byte):
return (self.value >> (8 * byte)) & 0xFF
def __eq__(self, other):
return isinstance(other, Colour) and self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return "#{:0>6x}".format(self.value)
def __repr__(self):
return "<Colour value=%s>" % self.value
def __hash__(self):
return hash(self.value)
@property
def r(self):
"""Returns the red component of the colour."""
return self._get_byte(2)
@property
def g(self):
"""Returns the green component of the colour."""
return self._get_byte(1)
@property
def b(self):
"""Returns the blue component of the colour."""
return self._get_byte(0)
def to_rgb(self):
"""Returns an (r, g, b) tuple representing the colour."""
return (self.r, self.g, self.b)
@classmethod
def from_rgb(cls, r, g, b):
"""Constructs a :class:`Colour` from an RGB tuple."""
return cls((r << 16) + (g << 8) + b)
@classmethod
def from_hsv(cls, h, s, v):
"""Constructs a :class:`Colour` from an HSV tuple."""
rgb = colorsys.hsv_to_rgb(h, s, v)
return cls.from_rgb(*(int(x * 255) for x in rgb))
@classmethod
def default(cls):
"""A factory method that returns a :class:`Colour` with a value of 0."""
return cls(0)
@classmethod
def teal(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x1abc9c``."""
return cls(0x1ABC9C)
@classmethod
def dark_teal(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x11806a``."""
return cls(0x11806A)
@classmethod
def green(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x2ecc71``."""
return cls(0x2ECC71)
@classmethod
def dark_green(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x1f8b4c``."""
return cls(0x1F8B4C)
@classmethod
def blue(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x3498db``."""
return cls(0x3498DB)
@classmethod
def dark_blue(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x206694``."""
return cls(0x206694)
@classmethod
def purple(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x9b59b6``."""
return cls(0x9B59B6)
@classmethod
def dark_purple(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x71368a``."""
return cls(0x71368A)
@classmethod
def magenta(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0xe91e63``."""
return cls(0xE91E63)
@classmethod
def dark_magenta(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0xad1457``."""
return cls(0xAD1457)
@classmethod
def gold(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0xf1c40f``."""
return cls(0xF1C40F)
@classmethod
def dark_gold(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0xc27c0e``."""
return cls(0xC27C0E)
@classmethod
def orange(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0xe67e22``."""
return cls(0xE67E22)
@classmethod
def dark_orange(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0xa84300``."""
return cls(0xA84300)
@classmethod
def red(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0xe74c3c``."""
return cls(0xE74C3C)
@classmethod
def dark_red(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x992d22``."""
return cls(0x992D22)
@classmethod
def lighter_grey(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x95a5a6``."""
return cls(0x95A5A6)
@classmethod
def dark_grey(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x607d8b``."""
return cls(0x607D8B)
@classmethod
def light_grey(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x979c9f``."""
return cls(0x979C9F)
@classmethod
def darker_grey(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x546e7a``."""
return cls(0x546E7A)
@classmethod
def blurple(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x7289da``."""
return cls(0x7289DA)
@classmethod
def greyple(cls):
"""A factory method that returns a :class:`Colour` with a value of ``0x99aab5``."""
return cls(0x99AAB5)
Color = Colour

View File

@@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import asyncio
def _typing_done_callback(fut):
# just retrieve any exception and call it a day
try:
fut.exception()
except Exception:
pass
class Typing:
def __init__(self, messageable):
self.loop = messageable._state.loop
self.messageable = messageable
async def do_typing(self):
try:
channel = self._channel
except AttributeError:
channel = await self.messageable._get_channel()
typing = channel._state.http.send_typing
while True:
await typing(channel.id)
await asyncio.sleep(5)
def __enter__(self):
self.task = asyncio.ensure_future(self.do_typing(), loop=self.loop)
self.task.add_done_callback(_typing_done_callback)
return self
def __exit__(self, exc_type, exc, tb):
self.task.cancel()
async def __aenter__(self):
self._channel = channel = await self.messageable._get_channel()
await channel._state.http.send_typing(channel.id)
return self.__enter__()
async def __aexit__(self, exc_type, exc, tb):
self.task.cancel()

View File

@@ -1,492 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import datetime
from . import utils
from .colour import Colour
class _EmptyEmbed:
def __bool__(self):
return False
def __repr__(self):
return "Embed.Empty"
EmptyEmbed = _EmptyEmbed()
class EmbedProxy:
def __init__(self, layer):
self.__dict__.update(layer)
def __len__(self):
return len(self.__dict__)
def __repr__(self):
return "EmbedProxy(%s)" % ", ".join(
("%s=%r" % (k, v) for k, v in self.__dict__.items() if not k.startswith("_"))
)
def __getattr__(self, attr):
return EmptyEmbed
class Embed:
"""Represents a Discord embed.
The following attributes can be set during creation
of the object:
Certain properties return an ``EmbedProxy``. Which is a type
that acts similar to a regular :class:`dict` except access the attributes
via dotted access, e.g. ``embed.author.icon_url``. If the attribute
is invalid or empty, then a special sentinel value is returned,
:attr:`Embed.Empty`.
For ease of use, all parameters that expect a :class:`str` are implicitly
casted to :class:`str` for you.
Attributes
-----------
title: :class:`str`
The title of the embed.
type: :class:`str`
The type of embed. Usually "rich".
description: :class:`str`
The description of the embed.
url: :class:`str`
The URL of the embed.
timestamp: `datetime.datetime`
The timestamp of the embed content. This could be a naive or aware datetime.
colour: :class:`Colour` or :class:`int`
The colour code of the embed. Aliased to ``color`` as well.
Empty
A special sentinel value used by ``EmbedProxy`` and this class
to denote that the value or attribute is empty.
"""
__slots__ = (
"title",
"url",
"type",
"_timestamp",
"_colour",
"_footer",
"_image",
"_thumbnail",
"_video",
"_provider",
"_author",
"_fields",
"description",
)
Empty = EmptyEmbed
def __init__(self, **kwargs):
# swap the colour/color aliases
try:
colour = kwargs["colour"]
except KeyError:
colour = kwargs.get("color", EmptyEmbed)
self.colour = colour
self.title = kwargs.get("title", EmptyEmbed)
self.type = kwargs.get("type", "rich")
self.url = kwargs.get("url", EmptyEmbed)
self.description = kwargs.get("description", EmptyEmbed)
try:
timestamp = kwargs["timestamp"]
except KeyError:
pass
else:
self.timestamp = timestamp
@classmethod
def from_data(cls, data):
# we are bypassing __init__ here since it doesn't apply here
self = cls.__new__(cls)
# fill in the basic fields
self.title = data.get("title", EmptyEmbed)
self.type = data.get("type", EmptyEmbed)
self.description = data.get("description", EmptyEmbed)
self.url = data.get("url", EmptyEmbed)
# try to fill in the more rich fields
try:
self._colour = Colour(value=data["color"])
except KeyError:
pass
try:
self._timestamp = utils.parse_time(data["timestamp"])
except KeyError:
pass
for attr in ("thumbnail", "video", "provider", "author", "fields", "image", "footer"):
try:
value = data[attr]
except KeyError:
continue
else:
setattr(self, "_" + attr, value)
return self
@property
def colour(self):
return getattr(self, "_colour", EmptyEmbed)
@colour.setter
def colour(self, value):
if isinstance(value, (Colour, _EmptyEmbed)):
self._colour = value
elif isinstance(value, int):
self._colour = Colour(value=value)
else:
raise TypeError(
"Expected discord.Colour, int, or Embed.Empty but received %s instead."
% value.__class__.__name__
)
color = colour
@property
def timestamp(self):
return getattr(self, "_timestamp", EmptyEmbed)
@timestamp.setter
def timestamp(self, value):
if isinstance(value, (datetime.datetime, _EmptyEmbed)):
self._timestamp = value
else:
raise TypeError(
"Expected datetime.datetime or Embed.Empty received %s instead"
% value.__class__.__name__
)
@property
def footer(self):
"""Returns an ``EmbedProxy`` denoting the footer contents.
See :meth:`set_footer` for possible values you can access.
If the attribute has no value then :attr:`Empty` is returned.
"""
return EmbedProxy(getattr(self, "_footer", {}))
def set_footer(self, *, text=EmptyEmbed, icon_url=EmptyEmbed):
"""Sets the footer for the embed content.
This function returns the class instance to allow for fluent-style
chaining.
Parameters
-----------
text: str
The footer text.
icon_url: str
The URL of the footer icon. Only HTTP(S) is supported.
"""
self._footer = {}
if text is not EmptyEmbed:
self._footer["text"] = str(text)
if icon_url is not EmptyEmbed:
self._footer["icon_url"] = str(icon_url)
return self
@property
def image(self):
"""Returns an ``EmbedProxy`` denoting the image contents.
Possible attributes you can access are:
- ``url``
- ``proxy_url``
- ``width``
- ``height``
If the attribute has no value then :attr:`Empty` is returned.
"""
return EmbedProxy(getattr(self, "_image", {}))
def set_image(self, *, url):
"""Sets the image for the embed content.
This function returns the class instance to allow for fluent-style
chaining.
Parameters
-----------
url: str
The source URL for the image. Only HTTP(S) is supported.
"""
self._image = {"url": str(url)}
return self
@property
def thumbnail(self):
"""Returns an ``EmbedProxy`` denoting the thumbnail contents.
Possible attributes you can access are:
- ``url``
- ``proxy_url``
- ``width``
- ``height``
If the attribute has no value then :attr:`Empty` is returned.
"""
return EmbedProxy(getattr(self, "_thumbnail", {}))
def set_thumbnail(self, *, url):
"""Sets the thumbnail for the embed content.
This function returns the class instance to allow for fluent-style
chaining.
Parameters
-----------
url: str
The source URL for the thumbnail. Only HTTP(S) is supported.
"""
self._thumbnail = {"url": str(url)}
return self
@property
def video(self):
"""Returns an ``EmbedProxy`` denoting the video contents.
Possible attributes include:
- ``url`` for the video URL.
- ``height`` for the video height.
- ``width`` for the video width.
If the attribute has no value then :attr:`Empty` is returned.
"""
return EmbedProxy(getattr(self, "_video", {}))
@property
def provider(self):
"""Returns an ``EmbedProxy`` denoting the provider contents.
The only attributes that might be accessed are ``name`` and ``url``.
If the attribute has no value then :attr:`Empty` is returned.
"""
return EmbedProxy(getattr(self, "_provider", {}))
@property
def author(self):
"""Returns an ``EmbedProxy`` denoting the author contents.
See :meth:`set_author` for possible values you can access.
If the attribute has no value then :attr:`Empty` is returned.
"""
return EmbedProxy(getattr(self, "_author", {}))
def set_author(self, *, name, url=EmptyEmbed, icon_url=EmptyEmbed):
"""Sets the author for the embed content.
This function returns the class instance to allow for fluent-style
chaining.
Parameters
-----------
name: str
The name of the author.
url: str
The URL for the author.
icon_url: str
The URL of the author icon. Only HTTP(S) is supported.
"""
self._author = {"name": str(name)}
if url is not EmptyEmbed:
self._author["url"] = str(url)
if icon_url is not EmptyEmbed:
self._author["icon_url"] = str(icon_url)
return self
@property
def fields(self):
"""Returns a :class:`list` of ``EmbedProxy`` denoting the field contents.
See :meth:`add_field` for possible values you can access.
If the attribute has no value then :attr:`Empty` is returned.
"""
return [EmbedProxy(d) for d in getattr(self, "_fields", [])]
def add_field(self, *, name, value, inline=True):
"""Adds a field to the embed object.
This function returns the class instance to allow for fluent-style
chaining.
Parameters
-----------
name: str
The name of the field.
value: str
The value of the field.
inline: bool
Whether the field should be displayed inline.
"""
field = {"inline": inline, "name": str(name), "value": str(value)}
try:
self._fields.append(field)
except AttributeError:
self._fields = [field]
return self
def clear_fields(self):
"""Removes all fields from this embed."""
try:
self._fields.clear()
except AttributeError:
self._fields = []
def remove_field(self, index):
"""Removes a field at a specified index.
If the index is invalid or out of bounds then the error is
silently swallowed.
.. note::
When deleting a field by index, the index of the other fields
shift to fill the gap just like a regular list.
Parameters
-----------
index: int
The index of the field to remove.
"""
try:
del self._fields[index]
except (AttributeError, IndexError):
pass
def set_field_at(self, index, *, name, value, inline=True):
"""Modifies a field to the embed object.
The index must point to a valid pre-existing field.
This function returns the class instance to allow for fluent-style
chaining.
Parameters
-----------
index: int
The index of the field to modify.
name: str
The name of the field.
value: str
The value of the field.
inline: bool
Whether the field should be displayed inline.
Raises
-------
IndexError
An invalid index was provided.
"""
try:
field = self._fields[index]
except (TypeError, IndexError, AttributeError):
raise IndexError("field index out of range")
field["name"] = str(name)
field["value"] = str(value)
field["inline"] = inline
return self
def to_dict(self):
"""Converts this embed object into a dict."""
# add in the raw data into the dict
result = {
key[1:]: getattr(self, key)
for key in self.__slots__
if key[0] == "_" and hasattr(self, key)
}
# deal with basic convenience wrappers
try:
colour = result.pop("colour")
except KeyError:
pass
else:
if colour:
result["color"] = colour.value
try:
timestamp = result.pop("timestamp")
except KeyError:
pass
else:
if timestamp:
result["timestamp"] = timestamp.isoformat()
# add in the non raw attribute ones
if self.type:
result["type"] = self.type
if self.description:
result["description"] = self.description
if self.url:
result["url"] = self.url
if self.title:
result["title"] = self.title
return result

View File

@@ -1,269 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from collections import namedtuple
from . import utils
from .mixins import Hashable
class PartialEmoji(namedtuple("PartialEmoji", "animated name id")):
"""Represents a "partial" emoji.
This model will be given in two scenarios:
- "Raw" data events such as :func:`on_raw_reaction_add`
- Custom emoji that the bot cannot see from e.g. :attr:`Message.reactions`
.. container:: operations
.. describe:: x == y
Checks if two emoji are the same.
.. describe:: x != y
Checks if two emoji are not the same.
.. describe:: hash(x)
Return the emoji's hash.
.. describe:: str(x)
Returns the emoji rendered for discord.
Attributes
-----------
name: :class:`str`
The custom emoji name, if applicable, or the unicode codepoint
of the non-custom emoji.
animated: :class:`bool`
Whether the emoji is animated or not.
id: Optional[:class:`int`]
The ID of the custom emoji, if applicable.
"""
__slots__ = ()
def __str__(self):
if self.id is None:
return self.name
if self.animated:
return "<a:%s:%s>" % (self.name, self.id)
return "<:%s:%s>" % (self.name, self.id)
def is_custom_emoji(self):
"""Checks if this is a custom non-Unicode emoji."""
return self.id is not None
def is_unicode_emoji(self):
"""Checks if this is a Unicode emoji."""
return self.id is None
def _as_reaction(self):
if self.id is None:
return self.name
return "%s:%s" % (self.name, self.id)
@property
def url(self):
"""Returns a URL version of the emoji, if it is custom."""
if self.is_unicode_emoji():
return None
_format = "gif" if self.animated else "png"
return "https://cdn.discordapp.com/emojis/{0.id}.{1}".format(self, _format)
class Emoji(Hashable):
"""Represents a custom emoji.
Depending on the way this object was created, some of the attributes can
have a value of ``None``.
.. container:: operations
.. describe:: x == y
Checks if two emoji are the same.
.. describe:: x != y
Checks if two emoji are not the same.
.. describe:: hash(x)
Return the emoji's hash.
.. describe:: iter(x)
Returns an iterator of ``(field, value)`` pairs. This allows this class
to be used as an iterable in list/dict/etc constructions.
.. describe:: str(x)
Returns the emoji rendered for discord.
Attributes
-----------
name: :class:`str`
The name of the emoji.
id: :class:`int`
The emoji's ID.
require_colons: :class:`bool`
If colons are required to use this emoji in the client (:PJSalt: vs PJSalt).
animated: :class:`bool`
Whether an emoji is animated or not.
managed: :class:`bool`
If this emoji is managed by a Twitch integration.
guild_id: :class:`int`
The guild ID the emoji belongs to.
"""
__slots__ = (
"require_colons",
"animated",
"managed",
"id",
"name",
"_roles",
"guild_id",
"_state",
)
def __init__(self, *, guild, state, data):
self.guild_id = guild.id
self._state = state
self._from_data(data)
def _from_data(self, emoji):
self.require_colons = emoji["require_colons"]
self.managed = emoji["managed"]
self.id = int(emoji["id"])
self.name = emoji["name"]
self.animated = emoji.get("animated", False)
self._roles = utils.SnowflakeList(map(int, emoji.get("roles", [])))
def _iterator(self):
for attr in self.__slots__:
if attr[0] != "_":
value = getattr(self, attr, None)
if value is not None:
yield (attr, value)
def __iter__(self):
return self._iterator()
def __str__(self):
if self.animated:
return "<a:{0.name}:{0.id}>".format(self)
return "<:{0.name}:{0.id}>".format(self)
def __repr__(self):
return "<Emoji id={0.id} name={0.name!r}>".format(self)
@property
def created_at(self):
"""Returns the emoji's creation time in UTC."""
return utils.snowflake_time(self.id)
@property
def url(self):
"""Returns a URL version of the emoji."""
_format = "gif" if self.animated else "png"
return "https://cdn.discordapp.com/emojis/{0.id}.{1}".format(self, _format)
@property
def roles(self):
"""List[:class:`Role`]: A :class:`list` of roles that is allowed to use this emoji.
If roles is empty, the emoji is unrestricted.
"""
guild = self.guild
if guild is None:
return []
return [role for role in guild.roles if self._roles.has(role.id)]
@property
def guild(self):
""":class:`Guild`: The guild this emoji belongs to."""
return self._state._get_guild(self.guild_id)
async def delete(self, *, reason=None):
"""|coro|
Deletes the custom emoji.
You must have :attr:`~Permissions.manage_emojis` permission to
do this.
Parameters
-----------
reason: Optional[str]
The reason for deleting this emoji. Shows up on the audit log.
Raises
-------
Forbidden
You are not allowed to delete emojis.
HTTPException
An error occurred deleting the emoji.
"""
await self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason)
async def edit(self, *, name, roles=None, reason=None):
r"""|coro|
Edits the custom emoji.
You must have :attr:`~Permissions.manage_emojis` permission to
do this.
Parameters
-----------
name: str
The new emoji name.
roles: Optional[list[:class:`Role`]]
A :class:`list` of :class:`Role`\s that can use this emoji. Leave empty to make it available to everyone.
reason: Optional[str]
The reason for editing this emoji. Shows up on the audit log.
Raises
-------
Forbidden
You are not allowed to edit emojis.
HTTPException
An error occurred editing the emoji.
"""
if roles:
roles = [role.id for role in roles]
await self._state.http.edit_custom_emoji(
self.guild.id, self.id, name=name, roles=roles, reason=reason
)

View File

@@ -1,274 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from enum import Enum, IntEnum
__all__ = [
"ChannelType",
"MessageType",
"VoiceRegion",
"VerificationLevel",
"ContentFilter",
"Status",
"DefaultAvatar",
"RelationshipType",
"AuditLogAction",
"AuditLogActionCategory",
"UserFlags",
"ActivityType",
"HypeSquadHouse",
"NotificationLevel",
]
class ChannelType(Enum):
text = 0
private = 1
voice = 2
group = 3
category = 4
def __str__(self):
return self.name
class MessageType(Enum):
default = 0
recipient_add = 1
recipient_remove = 2
call = 3
channel_name_change = 4
channel_icon_change = 5
pins_add = 6
new_member = 7
class VoiceRegion(Enum):
us_west = "us-west"
us_east = "us-east"
us_south = "us-south"
us_central = "us-central"
eu_west = "eu-west"
eu_central = "eu-central"
singapore = "singapore"
london = "london"
sydney = "sydney"
amsterdam = "amsterdam"
frankfurt = "frankfurt"
brazil = "brazil"
hongkong = "hongkong"
russia = "russia"
japan = "japan"
southafrica = "southafrica"
vip_us_east = "vip-us-east"
vip_us_west = "vip-us-west"
vip_amsterdam = "vip-amsterdam"
def __str__(self):
return self.value
class VerificationLevel(IntEnum):
none = 0
low = 1
medium = 2
high = 3
table_flip = 3
extreme = 4
double_table_flip = 4
def __str__(self):
return self.name
class ContentFilter(IntEnum):
disabled = 0
no_role = 1
all_members = 2
def __str__(self):
return self.name
class Status(Enum):
online = "online"
offline = "offline"
idle = "idle"
dnd = "dnd"
do_not_disturb = "dnd"
invisible = "invisible"
def __str__(self):
return self.value
class DefaultAvatar(Enum):
blurple = 0
grey = 1
gray = 1
green = 2
orange = 3
red = 4
def __str__(self):
return self.name
class RelationshipType(Enum):
friend = 1
blocked = 2
incoming_request = 3
outgoing_request = 4
class NotificationLevel(IntEnum):
all_messages = 0
only_mentions = 1
class AuditLogActionCategory(Enum):
create = 1
delete = 2
update = 3
class AuditLogAction(Enum):
guild_update = 1
channel_create = 10
channel_update = 11
channel_delete = 12
overwrite_create = 13
overwrite_update = 14
overwrite_delete = 15
kick = 20
member_prune = 21
ban = 22
unban = 23
member_update = 24
member_role_update = 25
role_create = 30
role_update = 31
role_delete = 32
invite_create = 40
invite_update = 41
invite_delete = 42
webhook_create = 50
webhook_update = 51
webhook_delete = 52
emoji_create = 60
emoji_update = 61
emoji_delete = 62
message_delete = 72
@property
def category(self):
lookup = {
AuditLogAction.guild_update: AuditLogActionCategory.update,
AuditLogAction.channel_create: AuditLogActionCategory.create,
AuditLogAction.channel_update: AuditLogActionCategory.update,
AuditLogAction.channel_delete: AuditLogActionCategory.delete,
AuditLogAction.overwrite_create: AuditLogActionCategory.create,
AuditLogAction.overwrite_update: AuditLogActionCategory.update,
AuditLogAction.overwrite_delete: AuditLogActionCategory.delete,
AuditLogAction.kick: None,
AuditLogAction.member_prune: None,
AuditLogAction.ban: None,
AuditLogAction.unban: None,
AuditLogAction.member_update: AuditLogActionCategory.update,
AuditLogAction.member_role_update: AuditLogActionCategory.update,
AuditLogAction.role_create: AuditLogActionCategory.create,
AuditLogAction.role_update: AuditLogActionCategory.update,
AuditLogAction.role_delete: AuditLogActionCategory.delete,
AuditLogAction.invite_create: AuditLogActionCategory.create,
AuditLogAction.invite_update: AuditLogActionCategory.update,
AuditLogAction.invite_delete: AuditLogActionCategory.delete,
AuditLogAction.webhook_create: AuditLogActionCategory.create,
AuditLogAction.webhook_update: AuditLogActionCategory.update,
AuditLogAction.webhook_delete: AuditLogActionCategory.delete,
AuditLogAction.emoji_create: AuditLogActionCategory.create,
AuditLogAction.emoji_update: AuditLogActionCategory.update,
AuditLogAction.emoji_delete: AuditLogActionCategory.delete,
AuditLogAction.message_delete: AuditLogActionCategory.delete,
}
return lookup[self]
@property
def target_type(self):
v = self.value
if v == -1:
return "all"
elif v < 10:
return "guild"
elif v < 20:
return "channel"
elif v < 30:
return "user"
elif v < 40:
return "role"
elif v < 50:
return "invite"
elif v < 60:
return "webhook"
elif v < 70:
return "emoji"
elif v < 80:
return "message"
class UserFlags(Enum):
staff = 1
partner = 2
hypesquad = 4
bug_hunter = 8
hypesquad_bravery = 64
hypesquad_brilliance = 128
hypesquad_balance = 256
early_supporter = 512
class ActivityType(IntEnum):
unknown = -1
playing = 0
streaming = 1
listening = 2
watching = 3
class HypeSquadHouse(Enum):
bravery = 1
brilliance = 2
balance = 3
def try_enum(cls, val):
"""A function that tries to turn the value into enum ``cls``.
If it fails it returns the value instead.
"""
try:
return cls(val)
except ValueError:
return val

View File

@@ -1,183 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
class DiscordException(Exception):
"""Base exception class for discord.py
Ideally speaking, this could be caught to handle any exceptions thrown from this library.
"""
pass
class ClientException(DiscordException):
"""Exception that's thrown when an operation in the :class:`Client` fails.
These are usually for exceptions that happened due to user input.
"""
pass
class NoMoreItems(DiscordException):
"""Exception that is thrown when an async iteration operation has no more
items."""
pass
class GatewayNotFound(DiscordException):
"""An exception that is usually thrown when the gateway hub
for the :class:`Client` websocket is not found."""
def __init__(self):
message = "The gateway to connect to discord was not found."
super(GatewayNotFound, self).__init__(message)
def flatten_error_dict(d, key=""):
items = []
for k, v in d.items():
new_key = key + "." + k if key else k
if isinstance(v, dict):
try:
_errors = v["_errors"]
except KeyError:
items.extend(flatten_error_dict(v, new_key).items())
else:
items.append((new_key, " ".join(x.get("message", "") for x in _errors)))
else:
items.append((new_key, v))
return dict(items)
class HTTPException(DiscordException):
"""Exception that's thrown when an HTTP request operation fails.
Attributes
------------
response: aiohttp.ClientResponse
The response of the failed HTTP request. This is an
instance of `aiohttp.ClientResponse`__. In some cases
this could also be a ``requests.Response``.
__ http://aiohttp.readthedocs.org/en/stable/client_reference.html#aiohttp.ClientResponse
text: :class:`str`
The text of the error. Could be an empty string.
status: :class:`int`
The status code of the HTTP request.
code: :class:`int`
The Discord specific error code for the failure.
"""
def __init__(self, response, message):
self.response = response
self.status = response.status
if isinstance(message, dict):
self.code = message.get("code", 0)
base = message.get("message", "")
errors = message.get("errors")
if errors:
errors = flatten_error_dict(errors)
helpful = "\n".join("In %s: %s" % t for t in errors.items())
self.text = base + "\n" + helpful
else:
self.text = base
else:
self.text = message
self.code = 0
fmt = "{0.reason} (status code: {0.status})"
if len(self.text):
fmt = fmt + ": {1}"
super().__init__(fmt.format(self.response, self.text))
class Forbidden(HTTPException):
"""Exception that's thrown for when status code 403 occurs.
Subclass of :exc:`HTTPException`
"""
pass
class NotFound(HTTPException):
"""Exception that's thrown for when status code 404 occurs.
Subclass of :exc:`HTTPException`
"""
pass
class InvalidArgument(ClientException):
"""Exception that's thrown when an argument to a function
is invalid some way (e.g. wrong value or wrong type).
This could be considered the analogous of ``ValueError`` and
``TypeError`` except derived from :exc:`ClientException` and thus
:exc:`DiscordException`.
"""
pass
class LoginFailure(ClientException):
"""Exception that's thrown when the :meth:`Client.login` function
fails to log you in from improper credentials or some other misc.
failure.
"""
pass
class ConnectionClosed(ClientException):
"""Exception that's thrown when the gateway connection is
closed for reasons that could not be handled internally.
Attributes
-----------
code: :class:`int`
The close code of the websocket.
reason: :class:`str`
The reason provided for the closure.
shard_id: Optional[:class:`int`]
The shard ID that got closed if applicable.
"""
def __init__(self, original, *, shard_id):
# This exception is just the same exception except
# reconfigured to subclass ClientException for users
self.code = original.code
self.reason = original.reason
self.shard_id = shard_id
super().__init__(str(original))

View File

@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
"""
discord.ext.commands
~~~~~~~~~~~~~~~~~~~~~
An extension module to facilitate creation of bot commands.
:copyright: (c) 2017 Rapptz
:license: MIT, see LICENSE for more details.
"""
from .bot import Bot, AutoShardedBot, when_mentioned, when_mentioned_or
from .context import Context
from .core import *
from .errors import *
from .formatter import HelpFormatter, Paginator
from .converter import *
from .cooldowns import *

File diff suppressed because it is too large Load Diff

View File

@@ -1,225 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import discord.abc
import discord.utils
class Context(discord.abc.Messageable):
r"""Represents the context in which a command is being invoked under.
This class contains a lot of meta data to help you understand more about
the invocation context. This class is not created manually and is instead
passed around to commands as the first parameter.
This class implements the :class:`abc.Messageable` ABC.
Attributes
-----------
message: :class:`discord.Message`
The message that triggered the command being executed.
bot: :class:`.Bot`
The bot that contains the command being executed.
args: :class:`list`
The list of transformed arguments that were passed into the command.
If this is accessed during the :func:`on_command_error` event
then this list could be incomplete.
kwargs: :class:`dict`
A dictionary of transformed arguments that were passed into the command.
Similar to :attr:`args`\, if this is accessed in the
:func:`on_command_error` event then this dict could be incomplete.
prefix: :class:`str`
The prefix that was used to invoke the command.
command
The command (i.e. :class:`.Command` or its superclasses) that is being
invoked currently.
invoked_with: :class:`str`
The command name that triggered this invocation. Useful for finding out
which alias called the command.
invoked_subcommand
The subcommand (i.e. :class:`.Command` or its superclasses) that was
invoked. If no valid subcommand was invoked then this is equal to
`None`.
subcommand_passed: Optional[:class:`str`]
The string that was attempted to call a subcommand. This does not have
to point to a valid registered subcommand and could just point to a
nonsense string. If nothing was passed to attempt a call to a
subcommand then this is set to `None`.
command_failed: :class:`bool`
A boolean that indicates if the command failed to be parsed, checked,
or invoked.
"""
def __init__(self, **attrs):
self.message = attrs.pop("message", None)
self.bot = attrs.pop("bot", None)
self.args = attrs.pop("args", [])
self.kwargs = attrs.pop("kwargs", {})
self.prefix = attrs.pop("prefix")
self.command = attrs.pop("command", None)
self.view = attrs.pop("view", None)
self.invoked_with = attrs.pop("invoked_with", None)
self.invoked_subcommand = attrs.pop("invoked_subcommand", None)
self.subcommand_passed = attrs.pop("subcommand_passed", None)
self.command_failed = attrs.pop("command_failed", False)
self._state = self.message._state
async def invoke(self, *args, **kwargs):
r"""|coro|
Calls a command with the arguments given.
This is useful if you want to just call the callback that a
:class:`.Command` holds internally.
Note
------
You do not pass in the context as it is done for you.
Warning
---------
The first parameter passed **must** be the command being invoked.
Parameters
-----------
command: :class:`.Command`
A command or superclass of a command that is going to be called.
\*args
The arguments to to use.
\*\*kwargs
The keyword arguments to use.
"""
try:
command = args[0]
except IndexError:
raise TypeError("Missing command to invoke.") from None
arguments = []
if command.instance is not None:
arguments.append(command.instance)
arguments.append(self)
arguments.extend(args[1:])
ret = await command.callback(*arguments, **kwargs)
return ret
async def reinvoke(self, *, call_hooks=False, restart=True):
"""|coro|
Calls the command again.
This is similar to :meth:`~.Context.invoke` except that it bypasses
checks, cooldowns, and error handlers.
.. note::
If you want to bypass :exc:`.UserInputError` derived exceptions,
it is recommended to use the regular :meth:`~.Context.invoke`
as it will work more naturally. After all, this will end up
using the old arguments the user has used and will thus just
fail again.
Parameters
------------
call_hooks: bool
Whether to call the before and after invoke hooks.
restart: bool
Whether to start the call chain from the very beginning
or where we left off (i.e. the command that caused the error).
The default is to start where we left off.
"""
cmd = self.command
view = self.view
if cmd is None:
raise ValueError("This context is not valid.")
# some state to revert to when we're done
index, previous = view.index, view.previous
invoked_with = self.invoked_with
invoked_subcommand = self.invoked_subcommand
subcommand_passed = self.subcommand_passed
if restart:
to_call = cmd.root_parent or cmd
view.index = len(self.prefix)
view.previous = 0
view.get_word() # advance to get the root command
else:
to_call = cmd
try:
await to_call.reinvoke(self, call_hooks=call_hooks)
finally:
self.command = cmd
view.index = index
view.previous = previous
self.invoked_with = invoked_with
self.invoked_subcommand = invoked_subcommand
self.subcommand_passed = subcommand_passed
@property
def valid(self):
"""Checks if the invocation context is valid to be invoked with."""
return self.prefix is not None and self.command is not None
async def _get_channel(self):
return self.channel
@property
def cog(self):
"""Returns the cog associated with this context's command. None if it does not exist."""
if self.command is None:
return None
return self.command.instance
@discord.utils.cached_property
def guild(self):
"""Returns the guild associated with this context's command. None if not available."""
return self.message.guild
@discord.utils.cached_property
def channel(self):
"""Returns the channel associated with this context's command. Shorthand for :attr:`Message.channel`."""
return self.message.channel
@discord.utils.cached_property
def author(self):
"""Returns the author associated with this context's command. Shorthand for :attr:`Message.author`"""
return self.message.author
@discord.utils.cached_property
def me(self):
"""Similar to :attr:`Guild.me` except it may return the :class:`ClientUser` in private message contexts."""
return self.guild.me if self.guild is not None else self.bot.user
@property
def voice_client(self):
r"""Optional[:class:`VoiceClient`]: A shortcut to :attr:`Guild.voice_client`\, if applicable."""
g = self.guild
return g.voice_client if g else None

View File

@@ -1,560 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import re
import inspect
import discord
from .errors import BadArgument, NoPrivateMessage
__all__ = [
"Converter",
"MemberConverter",
"UserConverter",
"TextChannelConverter",
"InviteConverter",
"RoleConverter",
"GameConverter",
"ColourConverter",
"VoiceChannelConverter",
"EmojiConverter",
"PartialEmojiConverter",
"CategoryChannelConverter",
"IDConverter",
"clean_content",
"Greedy",
]
def _get_from_guilds(bot, getter, argument):
result = None
for guild in bot.guilds:
result = getattr(guild, getter)(argument)
if result:
return result
return result
class Converter:
"""The base class of custom converters that require the :class:`.Context`
to be passed to be useful.
This allows you to implement converters that function similar to the
special cased ``discord`` classes.
Classes that derive from this should override the :meth:`~.Converter.convert`
method to do its conversion logic. This method must be a coroutine.
"""
async def convert(self, ctx, argument):
"""|coro|
The method to override to do conversion logic.
If an error is found while converting, it is recommended to
raise a :exc:`.CommandError` derived exception as it will
properly propagate to the error handlers.
Parameters
-----------
ctx: :class:`.Context`
The invocation context that the argument is being used in.
argument: str
The argument that is being converted.
"""
raise NotImplementedError("Derived classes need to implement this.")
class IDConverter(Converter):
def __init__(self):
self._id_regex = re.compile(r"([0-9]{15,21})$")
super().__init__()
def _get_id_match(self, argument):
return self._id_regex.match(argument)
class MemberConverter(IDConverter):
"""Converts to a :class:`Member`.
All lookups are via the local guild. If in a DM context, then the lookup
is done by the global cache.
The lookup strategy is as follows (in order):
1. Lookup by ID.
2. Lookup by mention.
3. Lookup by name#discrim
4. Lookup by name
5. Lookup by nickname
"""
async def convert(self, ctx, argument):
bot = ctx.bot
match = self._get_id_match(argument) or re.match(r"<@!?([0-9]+)>$", argument)
guild = ctx.guild
result = None
if match is None:
# not a mention...
if guild:
result = guild.get_member_named(argument)
else:
result = _get_from_guilds(bot, "get_member_named", argument)
else:
user_id = int(match.group(1))
if guild:
result = guild.get_member(user_id)
else:
result = _get_from_guilds(bot, "get_member", user_id)
if result is None:
raise BadArgument('Member "{}" not found'.format(argument))
return result
class UserConverter(IDConverter):
"""Converts to a :class:`User`.
All lookups are via the global user cache.
The lookup strategy is as follows (in order):
1. Lookup by ID.
2. Lookup by mention.
3. Lookup by name#discrim
4. Lookup by name
"""
async def convert(self, ctx, argument):
match = self._get_id_match(argument) or re.match(r"<@!?([0-9]+)>$", argument)
result = None
state = ctx._state
if match is not None:
user_id = int(match.group(1))
result = ctx.bot.get_user(user_id)
else:
arg = argument
# check for discriminator if it exists
if len(arg) > 5 and arg[-5] == "#":
discrim = arg[-4:]
name = arg[:-5]
predicate = lambda u: u.name == name and u.discriminator == discrim
result = discord.utils.find(predicate, state._users.values())
if result is not None:
return result
predicate = lambda u: u.name == arg
result = discord.utils.find(predicate, state._users.values())
if result is None:
raise BadArgument('User "{}" not found'.format(argument))
return result
class TextChannelConverter(IDConverter):
"""Converts to a :class:`TextChannel`.
All lookups are via the local guild. If in a DM context, then the lookup
is done by the global cache.
The lookup strategy is as follows (in order):
1. Lookup by ID.
2. Lookup by mention.
3. Lookup by name
"""
async def convert(self, ctx, argument):
bot = ctx.bot
match = self._get_id_match(argument) or re.match(r"<#([0-9]+)>$", argument)
result = None
guild = ctx.guild
if match is None:
# not a mention
if guild:
result = discord.utils.get(guild.text_channels, name=argument)
else:
def check(c):
return isinstance(c, discord.TextChannel) and c.name == argument
result = discord.utils.find(check, bot.get_all_channels())
else:
channel_id = int(match.group(1))
if guild:
result = guild.get_channel(channel_id)
else:
result = _get_from_guilds(bot, "get_channel", channel_id)
if not isinstance(result, discord.TextChannel):
raise BadArgument('Channel "{}" not found.'.format(argument))
return result
class VoiceChannelConverter(IDConverter):
"""Converts to a :class:`VoiceChannel`.
All lookups are via the local guild. If in a DM context, then the lookup
is done by the global cache.
The lookup strategy is as follows (in order):
1. Lookup by ID.
2. Lookup by mention.
3. Lookup by name
"""
async def convert(self, ctx, argument):
bot = ctx.bot
match = self._get_id_match(argument) or re.match(r"<#([0-9]+)>$", argument)
result = None
guild = ctx.guild
if match is None:
# not a mention
if guild:
result = discord.utils.get(guild.voice_channels, name=argument)
else:
def check(c):
return isinstance(c, discord.VoiceChannel) and c.name == argument
result = discord.utils.find(check, bot.get_all_channels())
else:
channel_id = int(match.group(1))
if guild:
result = guild.get_channel(channel_id)
else:
result = _get_from_guilds(bot, "get_channel", channel_id)
if not isinstance(result, discord.VoiceChannel):
raise BadArgument('Channel "{}" not found.'.format(argument))
return result
class CategoryChannelConverter(IDConverter):
"""Converts to a :class:`CategoryChannel`.
All lookups are via the local guild. If in a DM context, then the lookup
is done by the global cache.
The lookup strategy is as follows (in order):
1. Lookup by ID.
2. Lookup by mention.
3. Lookup by name
"""
async def convert(self, ctx, argument):
bot = ctx.bot
match = self._get_id_match(argument) or re.match(r"<#([0-9]+)>$", argument)
result = None
guild = ctx.guild
if match is None:
# not a mention
if guild:
result = discord.utils.get(guild.categories, name=argument)
else:
def check(c):
return isinstance(c, discord.CategoryChannel) and c.name == argument
result = discord.utils.find(check, bot.get_all_channels())
else:
channel_id = int(match.group(1))
if guild:
result = guild.get_channel(channel_id)
else:
result = _get_from_guilds(bot, "get_channel", channel_id)
if not isinstance(result, discord.CategoryChannel):
raise BadArgument('Channel "{}" not found.'.format(argument))
return result
class ColourConverter(Converter):
"""Converts to a :class:`Colour`.
The following formats are accepted:
- ``0x<hex>``
- ``#<hex>``
- ``0x#<hex>``
- Any of the ``classmethod`` in :class:`Colour`
- The ``_`` in the name can be optionally replaced with spaces.
"""
async def convert(self, ctx, argument):
arg = argument.replace("0x", "").lower()
if arg[0] == "#":
arg = arg[1:]
try:
value = int(arg, base=16)
return discord.Colour(value=value)
except ValueError:
method = getattr(discord.Colour, arg.replace(" ", "_"), None)
if method is None or not inspect.ismethod(method):
raise BadArgument('Colour "{}" is invalid.'.format(arg))
return method()
class RoleConverter(IDConverter):
"""Converts to a :class:`Role`.
All lookups are via the local guild. If in a DM context, then the lookup
is done by the global cache.
The lookup strategy is as follows (in order):
1. Lookup by ID.
2. Lookup by mention.
3. Lookup by name
"""
async def convert(self, ctx, argument):
guild = ctx.guild
if not guild:
raise NoPrivateMessage()
match = self._get_id_match(argument) or re.match(r"<@&([0-9]+)>$", argument)
if match:
result = guild.get_role(int(match.group(1)))
else:
result = discord.utils.get(guild._roles.values(), name=argument)
if result is None:
raise BadArgument('Role "{}" not found.'.format(argument))
return result
class GameConverter(Converter):
"""Converts to :class:`Game`."""
async def convert(self, ctx, argument):
return discord.Game(name=argument)
class InviteConverter(Converter):
"""Converts to a :class:`Invite`.
This is done via an HTTP request using :meth:`.Bot.get_invite`.
"""
async def convert(self, ctx, argument):
try:
invite = await ctx.bot.get_invite(argument)
return invite
except Exception as exc:
raise BadArgument("Invite is invalid or expired") from exc
class EmojiConverter(IDConverter):
"""Converts to a :class:`Emoji`.
All lookups are done for the local guild first, if available. If that lookup
fails, then it checks the client's global cache.
The lookup strategy is as follows (in order):
1. Lookup by ID.
2. Lookup by extracting ID from the emoji.
3. Lookup by name
"""
async def convert(self, ctx, argument):
match = self._get_id_match(argument) or re.match(
r"<a?:[a-zA-Z0-9\_]+:([0-9]+)>$", argument
)
result = None
bot = ctx.bot
guild = ctx.guild
if match is None:
# Try to get the emoji by name. Try local guild first.
if guild:
result = discord.utils.get(guild.emojis, name=argument)
if result is None:
result = discord.utils.get(bot.emojis, name=argument)
else:
emoji_id = int(match.group(1))
# Try to look up emoji by id.
if guild:
result = discord.utils.get(guild.emojis, id=emoji_id)
if result is None:
result = discord.utils.get(bot.emojis, id=emoji_id)
if result is None:
raise BadArgument('Emoji "{}" not found.'.format(argument))
return result
class PartialEmojiConverter(Converter):
"""Converts to a :class:`PartialEmoji`.
This is done by extracting the animated flag, name and ID from the emoji.
"""
async def convert(self, ctx, argument):
match = re.match(r"<(a?):([a-zA-Z0-9\_]+):([0-9]+)>$", argument)
if match:
emoji_animated = bool(match.group(1))
emoji_name = match.group(2)
emoji_id = int(match.group(3))
return discord.PartialEmoji(animated=emoji_animated, name=emoji_name, id=emoji_id)
raise BadArgument('Couldn\'t convert "{}" to PartialEmoji.'.format(argument))
class clean_content(Converter):
"""Converts the argument to mention scrubbed version of
said content.
This behaves similarly to :attr:`.Message.clean_content`.
Attributes
------------
fix_channel_mentions: :obj:`bool`
Whether to clean channel mentions.
use_nicknames: :obj:`bool`
Whether to use nicknames when transforming mentions.
escape_markdown: :obj:`bool`
Whether to also escape special markdown characters.
"""
def __init__(self, *, fix_channel_mentions=False, use_nicknames=True, escape_markdown=False):
self.fix_channel_mentions = fix_channel_mentions
self.use_nicknames = use_nicknames
self.escape_markdown = escape_markdown
async def convert(self, ctx, argument):
message = ctx.message
transformations = {}
if self.fix_channel_mentions and ctx.guild:
def resolve_channel(id, *, _get=ctx.guild.get_channel):
ch = _get(id)
return ("<#%s>" % id), ("#" + ch.name if ch else "#deleted-channel")
transformations.update(
resolve_channel(channel) for channel in message.raw_channel_mentions
)
if self.use_nicknames and ctx.guild:
def resolve_member(id, *, _get=ctx.guild.get_member):
m = _get(id)
return "@" + m.display_name if m else "@deleted-user"
else:
def resolve_member(id, *, _get=ctx.bot.get_user):
m = _get(id)
return "@" + m.name if m else "@deleted-user"
transformations.update(
("<@%s>" % member_id, resolve_member(member_id)) for member_id in message.raw_mentions
)
transformations.update(
("<@!%s>" % member_id, resolve_member(member_id)) for member_id in message.raw_mentions
)
if ctx.guild:
def resolve_role(_id, *, _find=ctx.guild.get_role):
r = _find(_id)
return "@" + r.name if r else "@deleted-role"
transformations.update(
("<@&%s>" % role_id, resolve_role(role_id))
for role_id in message.raw_role_mentions
)
def repl(obj):
return transformations.get(obj.group(0), "")
pattern = re.compile("|".join(transformations.keys()))
result = pattern.sub(repl, argument)
if self.escape_markdown:
transformations = {re.escape(c): "\\" + c for c in ("*", "`", "_", "~", "\\")}
def replace(obj):
return transformations.get(re.escape(obj.group(0)), "")
pattern = re.compile("|".join(transformations.keys()))
result = pattern.sub(replace, result)
# Completely ensure no mentions escape:
return re.sub(r"@(everyone|here|[!&]?[0-9]{17,21})", "@\u200b\\1", result)
class _Greedy:
__slots__ = ("converter",)
def __init__(self, *, converter=None):
self.converter = converter
def __getitem__(self, params):
if not isinstance(params, tuple):
params = (params,)
if len(params) != 1:
raise TypeError("Greedy[...] only takes a single argument")
converter = params[0]
if not inspect.isclass(converter):
raise TypeError("Greedy[...] expects a type.")
if converter is str or converter is type(None) or converter is _Greedy:
raise TypeError("Greedy[%s] is invalid." % converter.__name__)
return self.__class__(converter=converter)
Greedy = _Greedy()

View File

@@ -1,148 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import enum
import time
__all__ = ["BucketType", "Cooldown", "CooldownMapping"]
class BucketType(enum.Enum):
default = 0
user = 1
guild = 2
channel = 3
member = 4
category = 5
class Cooldown:
__slots__ = ("rate", "per", "type", "_window", "_tokens", "_last")
def __init__(self, rate, per, type):
self.rate = int(rate)
self.per = float(per)
self.type = type
self._window = 0.0
self._tokens = self.rate
self._last = 0.0
if not isinstance(self.type, BucketType):
raise TypeError("Cooldown type must be a BucketType")
def get_tokens(self, current=None):
if not current:
current = time.time()
tokens = self._tokens
if current > self._window + self.per:
tokens = self.rate
return tokens
def update_rate_limit(self):
current = time.time()
self._last = current
self._tokens = self.get_tokens(current)
# first token used means that we start a new rate limit window
if self._tokens == self.rate:
self._window = current
# check if we are rate limited
if self._tokens == 0:
return self.per - (current - self._window)
# we're not so decrement our tokens
self._tokens -= 1
# see if we got rate limited due to this token change, and if
# so update the window to point to our current time frame
if self._tokens == 0:
self._window = current
def reset(self):
self._tokens = self.rate
self._last = 0.0
def copy(self):
return Cooldown(self.rate, self.per, self.type)
def __repr__(self):
return "<Cooldown rate: {0.rate} per: {0.per} window: {0._window} tokens: {0._tokens}>".format(
self
)
class CooldownMapping:
def __init__(self, original):
self._cache = {}
self._cooldown = original
@property
def valid(self):
return self._cooldown is not None
@classmethod
def from_cooldown(cls, rate, per, type):
return cls(Cooldown(rate, per, type))
def _bucket_key(self, msg):
bucket_type = self._cooldown.type
if bucket_type is BucketType.user:
return msg.author.id
elif bucket_type is BucketType.guild:
return (msg.guild or msg.author).id
elif bucket_type is BucketType.channel:
return msg.channel.id
elif bucket_type is BucketType.member:
return ((msg.guild and msg.guild.id), msg.author.id)
elif bucket_type is BucketType.category:
return (msg.channel.category or msg.channel).id
def _verify_cache_integrity(self):
# we want to delete all cache objects that haven't been used
# in a cooldown window. e.g. if we have a command that has a
# cooldown of 60s and it has not been used in 60s then that key should be deleted
current = time.time()
dead_keys = [k for k, v in self._cache.items() if current > v._last + v.per]
for k in dead_keys:
del self._cache[k]
def get_bucket(self, message):
if self._cooldown.type is BucketType.default:
return self._cooldown
self._verify_cache_integrity()
key = self._bucket_key(message)
if key not in self._cache:
bucket = self._cooldown.copy()
self._cache[key] = bucket
else:
bucket = self._cache[key]
return bucket

File diff suppressed because it is too large Load Diff

View File

@@ -1,279 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from discord.errors import DiscordException
__all__ = [
"CommandError",
"MissingRequiredArgument",
"BadArgument",
"NoPrivateMessage",
"CheckFailure",
"CommandNotFound",
"DisabledCommand",
"CommandInvokeError",
"TooManyArguments",
"UserInputError",
"CommandOnCooldown",
"NotOwner",
"MissingPermissions",
"BotMissingPermissions",
"ConversionError",
"BadUnionArgument",
]
class CommandError(DiscordException):
r"""The base exception type for all command related errors.
This inherits from :exc:`discord.DiscordException`.
This exception and exceptions derived from it are handled
in a special way as they are caught and passed into a special event
from :class:`.Bot`\, :func:`on_command_error`.
"""
def __init__(self, message=None, *args):
if message is not None:
# clean-up @everyone and @here mentions
m = message.replace("@everyone", "@\u200beveryone").replace("@here", "@\u200bhere")
super().__init__(m, *args)
else:
super().__init__(*args)
class ConversionError(CommandError):
"""Exception raised when a Converter class raises non-CommandError.
This inherits from :exc:`.CommandError`.
Attributes
----------
converter: :class:`discord.ext.commands.Converter`
The converter that failed.
original
The original exception that was raised. You can also get this via
the ``__cause__`` attribute.
"""
def __init__(self, converter, original):
self.converter = converter
self.original = original
class UserInputError(CommandError):
"""The base exception type for errors that involve errors
regarding user input.
This inherits from :exc:`.CommandError`.
"""
pass
class CommandNotFound(CommandError):
"""Exception raised when a command is attempted to be invoked
but no command under that name is found.
This is not raised for invalid subcommands, rather just the
initial main command that is attempted to be invoked.
"""
pass
class MissingRequiredArgument(UserInputError):
"""Exception raised when parsing a command and a parameter
that is required is not encountered.
Attributes
-----------
param: :class:`inspect.Parameter`
The argument that is missing.
"""
def __init__(self, param):
self.param = param
super().__init__("{0.name} is a required argument that is missing.".format(param))
class TooManyArguments(UserInputError):
"""Exception raised when the command was passed too many arguments and its
:attr:`.Command.ignore_extra` attribute was not set to ``True``.
"""
pass
class BadArgument(UserInputError):
"""Exception raised when a parsing or conversion failure is encountered
on an argument to pass into a command.
"""
pass
class CheckFailure(CommandError):
"""Exception raised when the predicates in :attr:`.Command.checks` have failed."""
pass
class NoPrivateMessage(CheckFailure):
"""Exception raised when an operation does not work in private message
contexts.
"""
pass
class NotOwner(CheckFailure):
"""Exception raised when the message author is not the owner of the bot."""
pass
class DisabledCommand(CommandError):
"""Exception raised when the command being invoked is disabled."""
pass
class CommandInvokeError(CommandError):
"""Exception raised when the command being invoked raised an exception.
Attributes
-----------
original
The original exception that was raised. You can also get this via
the ``__cause__`` attribute.
"""
def __init__(self, e):
self.original = e
super().__init__("Command raised an exception: {0.__class__.__name__}: {0}".format(e))
class CommandOnCooldown(CommandError):
"""Exception raised when the command being invoked is on cooldown.
Attributes
-----------
cooldown: Cooldown
A class with attributes ``rate``, ``per``, and ``type`` similar to
the :func:`.cooldown` decorator.
retry_after: :class:`float`
The amount of seconds to wait before you can retry again.
"""
def __init__(self, cooldown, retry_after):
self.cooldown = cooldown
self.retry_after = retry_after
super().__init__("You are on cooldown. Try again in {:.2f}s".format(retry_after))
class MissingPermissions(CheckFailure):
"""Exception raised when the command invoker lacks permissions to run
command.
Attributes
-----------
missing_perms: :class:`list`
The required permissions that are missing.
"""
def __init__(self, missing_perms, *args):
self.missing_perms = missing_perms
missing = [
perm.replace("_", " ").replace("guild", "server").title() for perm in missing_perms
]
if len(missing) > 2:
fmt = "{}, and {}".format(", ".join(missing[:-1]), missing[-1])
else:
fmt = " and ".join(missing)
message = "You are missing {} permission(s) to run command.".format(fmt)
super().__init__(message, *args)
class BotMissingPermissions(CheckFailure):
"""Exception raised when the bot lacks permissions to run command.
Attributes
-----------
missing_perms: :class:`list`
The required permissions that are missing.
"""
def __init__(self, missing_perms, *args):
self.missing_perms = missing_perms
missing = [
perm.replace("_", " ").replace("guild", "server").title() for perm in missing_perms
]
if len(missing) > 2:
fmt = "{}, and {}".format(", ".join(missing[:-1]), missing[-1])
else:
fmt = " and ".join(missing)
message = "Bot requires {} permission(s) to run command.".format(fmt)
super().__init__(message, *args)
class BadUnionArgument(UserInputError):
"""Exception raised when a :class:`typing.Union` converter fails for all
its associated types.
Attributes
-----------
param: :class:`inspect.Parameter`
The parameter that failed being converted.
converters: Tuple[Type, ...]
A tuple of converters attempted in conversion, in order of failure.
errors: List[:class:`CommandError`]
A list of errors that were caught from failing the conversion.
"""
def __init__(self, param, converters, errors):
self.param = param
self.converters = converters
self.errors = errors
def _get_name(x):
try:
return x.__name__
except AttributeError:
return x.__class__.__name__
to_string = [_get_name(x) for x in converters]
if len(to_string) > 2:
fmt = "{}, or {}".format(", ".join(to_string[:-1]), to_string[-1])
else:
fmt = " or ".join(to_string)
super().__init__('Could not convert "{0.name}" into {1}.'.format(param, fmt))

View File

@@ -1,365 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import itertools
import inspect
from .core import GroupMixin, Command
from .errors import CommandError
# from discord.iterators import _FilteredAsyncIterator
# help -> shows info of bot on top/bottom and lists subcommands
# help command -> shows detailed info of command
# help command <subcommand chain> -> same as above
# <description>
# <command signature with aliases>
# <long doc>
# Cog:
# <command> <shortdoc>
# <command> <shortdoc>
# Other Cog:
# <command> <shortdoc>
# No Category:
# <command> <shortdoc>
# Type <prefix>help command for more info on a command.
# You can also type <prefix>help category for more info on a category.
class Paginator:
"""A class that aids in paginating code blocks for Discord messages.
Attributes
-----------
prefix: :class:`str`
The prefix inserted to every page. e.g. three backticks.
suffix: :class:`str`
The suffix appended at the end of every page. e.g. three backticks.
max_size: :class:`int`
The maximum amount of codepoints allowed in a page.
"""
def __init__(self, prefix="```", suffix="```", max_size=2000):
self.prefix = prefix
self.suffix = suffix
self.max_size = max_size - len(suffix)
self._current_page = [prefix]
self._count = len(prefix) + 1 # prefix + newline
self._pages = []
def add_line(self, line="", *, empty=False):
"""Adds a line to the current page.
If the line exceeds the :attr:`max_size` then an exception
is raised.
Parameters
-----------
line: str
The line to add.
empty: bool
Indicates if another empty line should be added.
Raises
------
RuntimeError
The line was too big for the current :attr:`max_size`.
"""
if len(line) > self.max_size - len(self.prefix) - 2:
raise RuntimeError(
"Line exceeds maximum page size %s" % (self.max_size - len(self.prefix) - 2)
)
if self._count + len(line) + 1 > self.max_size:
self.close_page()
self._count += len(line) + 1
self._current_page.append(line)
if empty:
self._current_page.append("")
self._count += 1
def close_page(self):
"""Prematurely terminate a page."""
self._current_page.append(self.suffix)
self._pages.append("\n".join(self._current_page))
self._current_page = [self.prefix]
self._count = len(self.prefix) + 1 # prefix + newline
@property
def pages(self):
"""Returns the rendered list of pages."""
# we have more than just the prefix in our current page
if len(self._current_page) > 1:
self.close_page()
return self._pages
def __repr__(self):
fmt = "<Paginator prefix: {0.prefix} suffix: {0.suffix} max_size: {0.max_size} count: {0._count}>"
return fmt.format(self)
class HelpFormatter:
"""The default base implementation that handles formatting of the help
command.
To override the behaviour of the formatter, :meth:`~.HelpFormatter.format`
should be overridden. A number of utility functions are provided for use
inside that method.
Attributes
-----------
show_hidden: :class:`bool`
Dictates if hidden commands should be shown in the output.
Defaults to ``False``.
show_check_failure: :class:`bool`
Dictates if commands that have their :attr:`.Command.checks` failed
shown. Defaults to ``False``.
width: :class:`int`
The maximum number of characters that fit in a line.
Defaults to 80.
"""
def __init__(self, show_hidden=False, show_check_failure=False, width=80):
self.width = width
self.show_hidden = show_hidden
self.show_check_failure = show_check_failure
def has_subcommands(self):
""":class:`bool`: Specifies if the command has subcommands."""
return isinstance(self.command, GroupMixin)
def is_bot(self):
""":class:`bool`: Specifies if the command being formatted is the bot itself."""
return self.command is self.context.bot
def is_cog(self):
""":class:`bool`: Specifies if the command being formatted is actually a cog."""
return not self.is_bot() and not isinstance(self.command, Command)
def shorten(self, text):
"""Shortens text to fit into the :attr:`width`."""
if len(text) > self.width:
return text[: self.width - 3] + "..."
return text
@property
def max_name_size(self):
""":class:`int`: Returns the largest name length of a command or if it has subcommands
the largest subcommand name."""
try:
commands = (
self.command.all_commands if not self.is_cog() else self.context.bot.all_commands
)
if commands:
return max(
map(
lambda c: len(c.name) if self.show_hidden or not c.hidden else 0,
commands.values(),
)
)
return 0
except AttributeError:
return len(self.command.name)
@property
def clean_prefix(self):
"""The cleaned up invoke prefix. i.e. mentions are ``@name`` instead of ``<@id>``."""
user = self.context.guild.me if self.context.guild else self.context.bot.user
# this breaks if the prefix mention is not the bot itself but I
# consider this to be an *incredibly* strange use case. I'd rather go
# for this common use case rather than waste performance for the
# odd one.
return self.context.prefix.replace(user.mention, "@" + user.display_name)
def get_command_signature(self):
"""Retrieves the signature portion of the help page."""
prefix = self.clean_prefix
cmd = self.command
return prefix + cmd.signature
def get_ending_note(self):
command_name = self.context.invoked_with
return (
"Type {0}{1} command for more info on a command.\n"
"You can also type {0}{1} category for more info on a category.".format(
self.clean_prefix, command_name
)
)
async def filter_command_list(self):
"""Returns a filtered list of commands based on the two attributes
provided, :attr:`show_check_failure` and :attr:`show_hidden`.
Also filters based on if :meth:`~.HelpFormatter.is_cog` is valid.
Returns
--------
iterable
An iterable with the filter being applied. The resulting value is
a (key, value) :class:`tuple` of the command name and the command itself.
"""
def sane_no_suspension_point_predicate(tup):
cmd = tup[1]
if self.is_cog():
# filter commands that don't exist to this cog.
if cmd.instance is not self.command:
return False
if cmd.hidden and not self.show_hidden:
return False
return True
async def predicate(tup):
if sane_no_suspension_point_predicate(tup) is False:
return False
cmd = tup[1]
try:
return await cmd.can_run(self.context)
except CommandError:
return False
iterator = (
self.command.all_commands.items()
if not self.is_cog()
else self.context.bot.all_commands.items()
)
if self.show_check_failure:
return filter(sane_no_suspension_point_predicate, iterator)
# Gotta run every check and verify it
ret = []
for elem in iterator:
valid = await predicate(elem)
if valid:
ret.append(elem)
return ret
def _add_subcommands_to_page(self, max_width, commands):
for name, command in commands:
if name in command.aliases:
# skip aliases
continue
entry = " {0:<{width}} {1}".format(name, command.short_doc, width=max_width)
shortened = self.shorten(entry)
self._paginator.add_line(shortened)
async def format_help_for(self, context, command_or_bot):
"""Formats the help page and handles the actual heavy lifting of how
the help command looks like. To change the behaviour, override the
:meth:`~.HelpFormatter.format` method.
Parameters
-----------
context: :class:`.Context`
The context of the invoked help command.
command_or_bot: :class:`.Command` or :class:`.Bot`
The bot or command that we are getting the help of.
Returns
--------
list
A paginated output of the help command.
"""
self.context = context
self.command = command_or_bot
return await self.format()
async def format(self):
"""Handles the actual behaviour involved with formatting.
To change the behaviour, this method should be overridden.
Returns
--------
list
A paginated output of the help command.
"""
self._paginator = Paginator()
# we need a padding of ~80 or so
description = (
self.command.description if not self.is_cog() else inspect.getdoc(self.command)
)
if description:
# <description> portion
self._paginator.add_line(description, empty=True)
if isinstance(self.command, Command):
# <signature portion>
signature = self.get_command_signature()
self._paginator.add_line(signature, empty=True)
# <long doc> section
if self.command.help:
self._paginator.add_line(self.command.help, empty=True)
# end it here if it's just a regular command
if not self.has_subcommands():
self._paginator.close_page()
return self._paginator.pages
max_width = self.max_name_size
def category(tup):
cog = tup[1].cog_name
# we insert the zero width space there to give it approximate
# last place sorting position.
return cog + ":" if cog is not None else "\u200bNo Category:"
filtered = await self.filter_command_list()
if self.is_bot():
data = sorted(filtered, key=category)
for category, commands in itertools.groupby(data, key=category):
# there simply is no prettier way of doing this.
commands = sorted(commands)
if len(commands) > 0:
self._paginator.add_line(category)
self._add_subcommands_to_page(max_width, commands)
else:
filtered = sorted(filtered)
if filtered:
self._paginator.add_line("Commands:")
self._add_subcommands_to_page(max_width, filtered)
# add the ending note
self._paginator.add_line()
ending_note = self.get_ending_note()
self._paginator.add_line(ending_note)
return self._paginator.pages

View File

@@ -1,201 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from .errors import BadArgument
class StringView:
def __init__(self, buffer):
self.index = 0
self.buffer = buffer
self.end = len(buffer)
self.previous = 0
@property
def current(self):
return None if self.eof else self.buffer[self.index]
@property
def eof(self):
return self.index >= self.end
def undo(self):
self.index = self.previous
def skip_ws(self):
pos = 0
while not self.eof:
try:
current = self.buffer[self.index + pos]
if not current.isspace():
break
pos += 1
except IndexError:
break
self.previous = self.index
self.index += pos
return self.previous != self.index
def skip_string(self, string):
strlen = len(string)
if self.buffer[self.index : self.index + strlen] == string:
self.previous = self.index
self.index += strlen
return True
return False
def read_rest(self):
result = self.buffer[self.index :]
self.previous = self.index
self.index = self.end
return result
def read(self, n):
result = self.buffer[self.index : self.index + n]
self.previous = self.index
self.index += n
return result
def get(self):
try:
result = self.buffer[self.index + 1]
except IndexError:
result = None
self.previous = self.index
self.index += 1
return result
def get_word(self):
pos = 0
while not self.eof:
try:
current = self.buffer[self.index + pos]
if current.isspace():
break
pos += 1
except IndexError:
break
self.previous = self.index
result = self.buffer[self.index : self.index + pos]
self.index += pos
return result
def __repr__(self):
return "<StringView pos: {0.index} prev: {0.previous} end: {0.end} eof: {0.eof}>".format(
self
)
# Parser
# map from opening quotes to closing quotes
_quotes = {
'"': '"',
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"«": "»",
"": "",
"": "",
"": "",
}
_all_quotes = set(_quotes.keys()) | set(_quotes.values())
def quoted_word(view):
current = view.current
if current is None:
return None
close_quote = _quotes.get(current)
is_quoted = bool(close_quote)
if is_quoted:
result = []
_escaped_quotes = (current, close_quote)
else:
result = [current]
_escaped_quotes = _all_quotes
while not view.eof:
current = view.get()
if not current:
if is_quoted:
# unexpected EOF
raise BadArgument("Expected closing {}.".format(close_quote))
return "".join(result)
# currently we accept strings in the format of "hello world"
# to embed a quote inside the string you must escape it: "a \"world\""
if current == "\\":
next_char = view.get()
if not next_char:
# string ends with \ and no character after it
if is_quoted:
# if we're quoted then we're expecting a closing quote
raise BadArgument("Expected closing {}.".format(close_quote))
# if we aren't then we just let it through
return "".join(result)
if next_char in _escaped_quotes:
# escaped quote
result.append(next_char)
else:
# different escape character, ignore it
view.undo()
result.append(current)
continue
if not is_quoted and current in _all_quotes:
# we aren't quoted
raise BadArgument("Unexpected quote mark in non-quoted string")
# closing quote
if is_quoted and current == close_quote:
next_char = view.get()
valid_eof = not next_char or next_char.isspace()
if not valid_eof:
raise BadArgument("Expected space after closing quotation")
# we're quoted so it's okay
return "".join(result)
if current.isspace() and not is_quoted:
# end of word found
return "".join(result)
result.append(current)

View File

@@ -1,81 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import os.path
class File:
"""A parameter object used for :meth:`abc.Messageable.send`
for sending file objects.
Attributes
-----------
fp: Union[:class:`str`, BinaryIO]
A file-like object opened in binary mode and read mode
or a filename representing a file in the hard drive to
open.
.. note::
If the file-like object passed is opened via ``open`` then the
modes 'rb' should be used.
To pass binary data, consider usage of ``io.BytesIO``.
filename: Optional[:class:`str`]
The filename to display when uploading to Discord.
If this is not given then it defaults to ``fp.name`` or if ``fp`` is
a string then the ``filename`` will default to the string given.
spoiler: :class:`bool`
Whether the attachment is a spoiler.
"""
__slots__ = ("fp", "filename", "_true_fp")
def __init__(self, fp, filename=None, *, spoiler=False):
self.fp = fp
self._true_fp = None
if filename is None:
if isinstance(fp, str):
_, self.filename = os.path.split(fp)
else:
self.filename = getattr(fp, "name", None)
else:
self.filename = filename
if spoiler and not self.filename.startswith("SPOILER_"):
self.filename = "SPOILER_" + self.filename
def open_file(self):
fp = self.fp
if isinstance(fp, str):
self._true_fp = fp = open(fp, "rb")
return fp
def close(self):
if self._true_fp:
self._true_fp.close()

View File

@@ -1,701 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import asyncio
from collections import namedtuple
import json
import logging
import struct
import sys
import time
import threading
import zlib
import websockets
from . import utils
from .activity import _ActivityTag
from .errors import ConnectionClosed, InvalidArgument
log = logging.getLogger(__name__)
__all__ = [
"DiscordWebSocket",
"KeepAliveHandler",
"VoiceKeepAliveHandler",
"DiscordVoiceWebSocket",
"ResumeWebSocket",
]
class ResumeWebSocket(Exception):
"""Signals to initialise via RESUME opcode instead of IDENTIFY."""
def __init__(self, shard_id):
self.shard_id = shard_id
EventListener = namedtuple("EventListener", "predicate event result future")
class KeepAliveHandler(threading.Thread):
def __init__(self, *args, **kwargs):
ws = kwargs.pop("ws", None)
interval = kwargs.pop("interval", None)
shard_id = kwargs.pop("shard_id", None)
threading.Thread.__init__(self, *args, **kwargs)
self.ws = ws
self.interval = interval
self.daemon = True
self.shard_id = shard_id
self.msg = "Keeping websocket alive with sequence %s."
self._stop_ev = threading.Event()
self._last_ack = time.perf_counter()
self._last_send = time.perf_counter()
self.latency = float("inf")
self.heartbeat_timeout = ws._max_heartbeat_timeout
def run(self):
while not self._stop_ev.wait(self.interval):
if self._last_ack + self.heartbeat_timeout < time.perf_counter():
log.warning(
"Shard ID %s has stopped responding to the gateway. Closing and restarting.",
self.shard_id,
)
coro = self.ws.close(4000)
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
try:
f.result()
except Exception:
pass
finally:
self.stop()
return
data = self.get_payload()
log.debug(self.msg, data["d"])
coro = self.ws.send_as_json(data)
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
try:
# block until sending is complete
f.result()
except Exception:
self.stop()
else:
self._last_send = time.perf_counter()
def get_payload(self):
return {"op": self.ws.HEARTBEAT, "d": self.ws.sequence}
def stop(self):
self._stop_ev.set()
def ack(self):
ack_time = time.perf_counter()
self._last_ack = ack_time
self.latency = ack_time - self._last_send
class VoiceKeepAliveHandler(KeepAliveHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.msg = "Keeping voice websocket alive with timestamp %s."
def get_payload(self):
return {"op": self.ws.HEARTBEAT, "d": int(time.time() * 1000)}
class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
"""Implements a WebSocket for Discord's gateway v6.
This is created through :func:`create_main_websocket`. Library
users should never create this manually.
Attributes
-----------
DISPATCH
Receive only. Denotes an event to be sent to Discord, such as READY.
HEARTBEAT
When received tells Discord to keep the connection alive.
When sent asks if your connection is currently alive.
IDENTIFY
Send only. Starts a new session.
PRESENCE
Send only. Updates your presence.
VOICE_STATE
Send only. Starts a new connection to a voice guild.
VOICE_PING
Send only. Checks ping time to a voice guild, do not use.
RESUME
Send only. Resumes an existing connection.
RECONNECT
Receive only. Tells the client to reconnect to a new gateway.
REQUEST_MEMBERS
Send only. Asks for the full member list of a guild.
INVALIDATE_SESSION
Receive only. Tells the client to optionally invalidate the session
and IDENTIFY again.
HELLO
Receive only. Tells the client the heartbeat interval.
HEARTBEAT_ACK
Receive only. Confirms receiving of a heartbeat. Not having it implies
a connection issue.
GUILD_SYNC
Send only. Requests a guild sync.
gateway
The gateway we are currently connected to.
token
The authentication token for discord.
"""
DISPATCH = 0
HEARTBEAT = 1
IDENTIFY = 2
PRESENCE = 3
VOICE_STATE = 4
VOICE_PING = 5
RESUME = 6
RECONNECT = 7
REQUEST_MEMBERS = 8
INVALIDATE_SESSION = 9
HELLO = 10
HEARTBEAT_ACK = 11
GUILD_SYNC = 12
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.max_size = None
# an empty dispatcher to prevent crashes
self._dispatch = lambda *args: None
# generic event listeners
self._dispatch_listeners = []
# the keep alive
self._keep_alive = None
# ws related stuff
self.session_id = None
self.sequence = None
self._zlib = zlib.decompressobj()
self._buffer = bytearray()
@classmethod
async def from_client(
cls, client, *, shard_id=None, session=None, sequence=None, resume=False
):
"""Creates a main websocket for Discord from a :class:`Client`.
This is for internal use only.
"""
gateway = await client.http.get_gateway()
ws = await websockets.connect(gateway, loop=client.loop, klass=cls, compression=None)
# dynamically add attributes needed
ws.token = client.http.token
ws._connection = client._connection
ws._dispatch = client.dispatch
ws.gateway = gateway
ws.shard_id = shard_id
ws.shard_count = client._connection.shard_count
ws.session_id = session
ws.sequence = sequence
ws._max_heartbeat_timeout = client._connection.heartbeat_timeout
client._connection._update_references(ws)
log.info("Created websocket connected to %s", gateway)
# poll event for OP Hello
await ws.poll_event()
if not resume:
await ws.identify()
return ws
await ws.resume()
try:
await ws.ensure_open()
except websockets.exceptions.ConnectionClosed:
# ws got closed so let's just do a regular IDENTIFY connect.
log.info(
"RESUME failed (the websocket decided to close) for Shard ID %s. Retrying.",
shard_id,
)
return await cls.from_client(client, shard_id=shard_id)
else:
return ws
def wait_for(self, event, predicate, result=None):
"""Waits for a DISPATCH'd event that meets the predicate.
Parameters
-----------
event : str
The event name in all upper case to wait for.
predicate
A function that takes a data parameter to check for event
properties. The data parameter is the 'd' key in the JSON message.
result
A function that takes the same data parameter and executes to send
the result to the future. If None, returns the data.
Returns
--------
asyncio.Future
A future to wait for.
"""
future = self.loop.create_future()
entry = EventListener(event=event, predicate=predicate, result=result, future=future)
self._dispatch_listeners.append(entry)
return future
async def identify(self):
"""Sends the IDENTIFY packet."""
payload = {
"op": self.IDENTIFY,
"d": {
"token": self.token,
"properties": {
"$os": sys.platform,
"$browser": "discord.py",
"$device": "discord.py",
"$referrer": "",
"$referring_domain": "",
},
"compress": True,
"large_threshold": 250,
"v": 3,
},
}
if not self._connection.is_bot:
payload["d"]["synced_guilds"] = []
if self.shard_id is not None and self.shard_count is not None:
payload["d"]["shard"] = [self.shard_id, self.shard_count]
state = self._connection
if state._activity is not None or state._status is not None:
payload["d"]["presence"] = {
"status": state._status,
"game": state._activity,
"since": 0,
"afk": False,
}
await self.send_as_json(payload)
log.info("Shard ID %s has sent the IDENTIFY payload.", self.shard_id)
async def resume(self):
"""Sends the RESUME packet."""
payload = {
"op": self.RESUME,
"d": {"seq": self.sequence, "session_id": self.session_id, "token": self.token},
}
await self.send_as_json(payload)
log.info("Shard ID %s has sent the RESUME payload.", self.shard_id)
async def received_message(self, msg):
self._dispatch("socket_raw_receive", msg)
if type(msg) is bytes:
self._buffer.extend(msg)
if len(msg) >= 4:
if msg[-4:] == b"\x00\x00\xff\xff":
msg = self._zlib.decompress(self._buffer)
msg = msg.decode("utf-8")
self._buffer = bytearray()
else:
return
else:
return
msg = json.loads(msg)
log.debug("For Shard ID %s: WebSocket Event: %s", self.shard_id, msg)
self._dispatch("socket_response", msg)
op = msg.get("op")
data = msg.get("d")
seq = msg.get("s")
if seq is not None:
self.sequence = seq
if op != self.DISPATCH:
if op == self.RECONNECT:
# "reconnect" can only be handled by the Client
# so we terminate our connection and raise an
# internal exception signalling to reconnect.
log.info("Received RECONNECT opcode.")
await self.close()
raise ResumeWebSocket(self.shard_id)
if op == self.HEARTBEAT_ACK:
self._keep_alive.ack()
return
if op == self.HEARTBEAT:
beat = self._keep_alive.get_payload()
await self.send_as_json(beat)
return
if op == self.HELLO:
interval = data["heartbeat_interval"] / 1000.0
self._keep_alive = KeepAliveHandler(
ws=self, interval=interval, shard_id=self.shard_id
)
# send a heartbeat immediately
await self.send_as_json(self._keep_alive.get_payload())
self._keep_alive.start()
return
if op == self.INVALIDATE_SESSION:
if data is True:
await asyncio.sleep(5.0, loop=self.loop)
await self.close()
raise ResumeWebSocket(self.shard_id)
self.sequence = None
self.session_id = None
log.info("Shard ID %s session has been invalidated.", self.shard_id)
await self.identify()
return
log.warning("Unknown OP code %s.", op)
return
event = msg.get("t")
if event == "READY":
self._trace = trace = data.get("_trace", [])
self.sequence = msg["s"]
self.session_id = data["session_id"]
log.info(
"Shard ID %s has connected to Gateway: %s (Session ID: %s).",
self.shard_id,
", ".join(trace),
self.session_id,
)
elif event == "RESUMED":
self._trace = trace = data.get("_trace", [])
log.info(
"Shard ID %s has successfully RESUMED session %s under trace %s.",
self.shard_id,
self.session_id,
", ".join(trace),
)
parser = "parse_" + event.lower()
try:
func = getattr(self._connection, parser)
except AttributeError:
log.warning("Unknown event %s.", event)
else:
func(data)
# remove the dispatched listeners
removed = []
for index, entry in enumerate(self._dispatch_listeners):
if entry.event != event:
continue
future = entry.future
if future.cancelled():
removed.append(index)
continue
try:
valid = entry.predicate(data)
except Exception as exc:
future.set_exception(exc)
removed.append(index)
else:
if valid:
ret = data if entry.result is None else entry.result(data)
future.set_result(ret)
removed.append(index)
for index in reversed(removed):
del self._dispatch_listeners[index]
@property
def latency(self):
""":obj:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds."""
heartbeat = self._keep_alive
return float("inf") if heartbeat is None else heartbeat.latency
def _can_handle_close(self, code):
return code not in (1000, 4004, 4010, 4011)
async def poll_event(self):
"""Polls for a DISPATCH event and handles the general gateway loop.
Raises
------
ConnectionClosed
The websocket connection was terminated for unhandled reasons.
"""
try:
msg = await self.recv()
await self.received_message(msg)
except websockets.exceptions.ConnectionClosed as exc:
if self._can_handle_close(exc.code):
log.info(
"Websocket closed with %s (%s), attempting a reconnect.", exc.code, exc.reason
)
raise ResumeWebSocket(self.shard_id) from exc
else:
log.info("Websocket closed with %s (%s), cannot reconnect.", exc.code, exc.reason)
raise ConnectionClosed(exc, shard_id=self.shard_id) from exc
async def send(self, data):
self._dispatch("socket_raw_send", data)
await super().send(data)
async def send_as_json(self, data):
try:
await super().send(utils.to_json(data))
except websockets.exceptions.ConnectionClosed as exc:
if not self._can_handle_close(exc.code):
raise ConnectionClosed(exc, shard_id=self.shard_id) from exc
async def change_presence(self, *, activity=None, status=None, afk=False, since=0.0):
if activity is not None:
if not isinstance(activity, _ActivityTag):
raise InvalidArgument("activity must be one of Game, Streaming, or Activity.")
activity = activity.to_dict()
if status == "idle":
since = int(time.time() * 1000)
payload = {
"op": self.PRESENCE,
"d": {"game": activity, "afk": afk, "since": since, "status": status},
}
sent = utils.to_json(payload)
log.debug('Sending "%s" to change status', sent)
await self.send(sent)
async def request_sync(self, guild_ids):
payload = {"op": self.GUILD_SYNC, "d": list(guild_ids)}
await self.send_as_json(payload)
async def voice_state(self, guild_id, channel_id, self_mute=False, self_deaf=False):
payload = {
"op": self.VOICE_STATE,
"d": {
"guild_id": guild_id,
"channel_id": channel_id,
"self_mute": self_mute,
"self_deaf": self_deaf,
},
}
log.debug("Updating our voice state to %s.", payload)
await self.send_as_json(payload)
async def close(self, code=1000, reason=""):
if self._keep_alive:
self._keep_alive.stop()
await super().close(code, reason)
async def close_connection(self, *args, **kwargs):
if self._keep_alive:
self._keep_alive.stop()
await super().close_connection(*args, **kwargs)
class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
"""Implements the websocket protocol for handling voice connections.
Attributes
-----------
IDENTIFY
Send only. Starts a new voice session.
SELECT_PROTOCOL
Send only. Tells discord what encryption mode and how to connect for voice.
READY
Receive only. Tells the websocket that the initial connection has completed.
HEARTBEAT
Send only. Keeps your websocket connection alive.
SESSION_DESCRIPTION
Receive only. Gives you the secret key required for voice.
SPEAKING
Send only. Notifies the client if you are currently speaking.
HEARTBEAT_ACK
Receive only. Tells you your heartbeat has been acknowledged.
RESUME
Sent only. Tells the client to resume its session.
HELLO
Receive only. Tells you that your websocket connection was acknowledged.
INVALIDATE_SESSION
Sent only. Tells you that your RESUME request has failed and to re-IDENTIFY.
"""
IDENTIFY = 0
SELECT_PROTOCOL = 1
READY = 2
HEARTBEAT = 3
SESSION_DESCRIPTION = 4
SPEAKING = 5
HEARTBEAT_ACK = 6
RESUME = 7
HELLO = 8
INVALIDATE_SESSION = 9
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.max_size = None
self._keep_alive = None
async def send_as_json(self, data):
log.debug("Sending voice websocket frame: %s.", data)
await self.send(utils.to_json(data))
async def resume(self):
state = self._connection
payload = {
"op": self.RESUME,
"d": {
"token": state.token,
"server_id": str(state.server_id),
"session_id": state.session_id,
},
}
await self.send_as_json(payload)
async def identify(self):
state = self._connection
payload = {
"op": self.IDENTIFY,
"d": {
"server_id": str(state.server_id),
"user_id": str(state.user.id),
"session_id": state.session_id,
"token": state.token,
},
}
await self.send_as_json(payload)
@classmethod
async def from_client(cls, client, *, resume=False):
"""Creates a voice websocket for the :class:`VoiceClient`."""
gateway = "wss://" + client.endpoint + "/?v=3"
ws = await websockets.connect(gateway, loop=client.loop, klass=cls, compression=None)
ws.gateway = gateway
ws._connection = client
ws._max_heartbeat_timeout = 60.0
if resume:
await ws.resume()
else:
await ws.identify()
return ws
async def select_protocol(self, ip, port):
payload = {
"op": self.SELECT_PROTOCOL,
"d": {
"protocol": "udp",
"data": {"address": ip, "port": port, "mode": "xsalsa20_poly1305"},
},
}
await self.send_as_json(payload)
async def speak(self, is_speaking=True):
payload = {"op": self.SPEAKING, "d": {"speaking": is_speaking, "delay": 0}}
await self.send_as_json(payload)
async def received_message(self, msg):
log.debug("Voice websocket frame received: %s", msg)
op = msg["op"]
data = msg.get("d")
if op == self.READY:
interval = data["heartbeat_interval"] / 1000.0
self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=interval)
self._keep_alive.start()
await self.initial_connection(data)
elif op == self.HEARTBEAT_ACK:
self._keep_alive.ack()
elif op == self.INVALIDATE_SESSION:
log.info("Voice RESUME failed.")
await self.identify()
elif op == self.SESSION_DESCRIPTION:
await self.load_secret_key(data)
async def initial_connection(self, data):
state = self._connection
state.ssrc = data["ssrc"]
state.voice_port = data["port"]
packet = bytearray(70)
struct.pack_into(">I", packet, 0, state.ssrc)
state.socket.sendto(packet, (state.endpoint_ip, state.voice_port))
recv = await self.loop.sock_recv(state.socket, 70)
log.debug("received packet in initial_connection: %s", recv)
# the ip is ascii starting at the 4th byte and ending at the first null
ip_start = 4
ip_end = recv.index(0, ip_start)
state.ip = recv[ip_start:ip_end].decode("ascii")
# the port is a little endian unsigned short in the last two bytes
# yes, this is different endianness from everything else
state.port = struct.unpack_from("<H", recv, len(recv) - 2)[0]
log.debug("detected ip: %s port: %s", state.ip, state.port)
await self.select_protocol(state.ip, state.port)
log.info("selected the voice protocol for use")
async def load_secret_key(self, data):
log.info("received secret key for voice connection")
self._connection.secret_key = data.get("secret_key")
await self.speak()
async def poll_event(self):
try:
msg = await asyncio.wait_for(self.recv(), timeout=30.0, loop=self.loop)
await self.received_message(json.loads(msg))
except websockets.exceptions.ConnectionClosed as exc:
raise ConnectionClosed(exc, shard_id=None) from exc
async def close_connection(self, *args, **kwargs):
if self._keep_alive:
self._keep_alive.stop()
await super().close_connection(*args, **kwargs)

File diff suppressed because it is too large Load Diff

View File

@@ -1,911 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import asyncio
import json
import logging
import sys
from urllib.parse import quote as _uriquote
import weakref
import aiohttp
from .errors import HTTPException, Forbidden, NotFound, LoginFailure, GatewayNotFound
from . import __version__, utils
log = logging.getLogger(__name__)
async def json_or_text(response):
text = await response.text(encoding="utf-8")
if response.headers["content-type"] == "application/json":
return json.loads(text)
return text
class Route:
BASE = "https://discordapp.com/api/v7"
def __init__(self, method, path, **parameters):
self.path = path
self.method = method
url = self.BASE + self.path
if parameters:
self.url = url.format(
**{k: _uriquote(v) if isinstance(v, str) else v for k, v in parameters.items()}
)
else:
self.url = url
# major parameters:
self.channel_id = parameters.get("channel_id")
self.guild_id = parameters.get("guild_id")
@property
def bucket(self):
# the bucket is just method + path w/ major parameters
return "{0.method}:{0.channel_id}:{0.guild_id}:{0.path}".format(self)
class MaybeUnlock:
def __init__(self, lock):
self.lock = lock
self._unlock = True
def __enter__(self):
return self
def defer(self):
self._unlock = False
def __exit__(self, type, value, traceback):
if self._unlock:
self.lock.release()
class HTTPClient:
"""Represents an HTTP client sending HTTP requests to the Discord API."""
SUCCESS_LOG = "{method} {url} has received {text}"
REQUEST_LOG = "{method} {url} with {json} has returned {status}"
def __init__(self, connector=None, *, proxy=None, proxy_auth=None, loop=None):
self.loop = asyncio.get_event_loop() if loop is None else loop
self.connector = connector
self._session = aiohttp.ClientSession(connector=connector, loop=self.loop)
self._locks = weakref.WeakValueDictionary()
self._global_over = asyncio.Event(loop=self.loop)
self._global_over.set()
self.token = None
self.bot_token = False
self.proxy = proxy
self.proxy_auth = proxy_auth
user_agent = "DiscordBot (https://github.com/Rapptz/discord.py {0}) Python/{1[0]}.{1[1]} aiohttp/{2}"
self.user_agent = user_agent.format(__version__, sys.version_info, aiohttp.__version__)
def recreate(self):
if self._session.closed:
self._session = aiohttp.ClientSession(connector=self.connector, loop=self.loop)
async def request(self, route, *, header_bypass_delay=None, **kwargs):
bucket = route.bucket
method = route.method
url = route.url
lock = self._locks.get(bucket)
if lock is None:
lock = asyncio.Lock(loop=self.loop)
if bucket is not None:
self._locks[bucket] = lock
# header creation
headers = {"User-Agent": self.user_agent}
if self.token is not None:
headers["Authorization"] = "Bot " + self.token if self.bot_token else self.token
# some checking if it's a JSON request
if "json" in kwargs:
headers["Content-Type"] = "application/json"
kwargs["data"] = utils.to_json(kwargs.pop("json"))
try:
reason = kwargs.pop("reason")
except KeyError:
pass
else:
if reason:
headers["X-Audit-Log-Reason"] = _uriquote(reason, safe="/ ")
kwargs["headers"] = headers
# Proxy support
if self.proxy is not None:
kwargs["proxy"] = self.proxy
if self.proxy_auth is not None:
kwargs["proxy_auth"] = self.proxy_auth
if not self._global_over.is_set():
# wait until the global lock is complete
await self._global_over.wait()
await lock
with MaybeUnlock(lock) as maybe_lock:
for tries in range(5):
async with self._session.request(method, url, **kwargs) as r:
log.debug(
"%s %s with %s has returned %s", method, url, kwargs.get("data"), r.status
)
# even errors have text involved in them so this is safe to call
data = await json_or_text(r)
# check if we have rate limit header information
remaining = r.headers.get("X-Ratelimit-Remaining")
if remaining == "0" and r.status != 429:
# we've depleted our current bucket
if header_bypass_delay is None:
delta = utils._parse_ratelimit_header(r)
else:
delta = header_bypass_delay
log.debug(
"A rate limit bucket has been exhausted (bucket: %s, retry: %s).",
bucket,
delta,
)
maybe_lock.defer()
self.loop.call_later(delta, lock.release)
# the request was successful so just return the text/json
if 300 > r.status >= 200:
log.debug("%s %s has received %s", method, url, data)
return data
# we are being rate limited
if r.status == 429:
fmt = 'We are being rate limited. Retrying in %.2f seconds. Handled under the bucket "%s"'
# sleep a bit
retry_after = data["retry_after"] / 1000.0
log.warning(fmt, retry_after, bucket)
# check if it's a global rate limit
is_global = data.get("global", False)
if is_global:
log.warning(
"Global rate limit has been hit. Retrying in %.2f seconds.",
retry_after,
)
self._global_over.clear()
await asyncio.sleep(retry_after, loop=self.loop)
log.debug("Done sleeping for the rate limit. Retrying...")
# release the global lock now that the
# global rate limit has passed
if is_global:
self._global_over.set()
log.debug("Global rate limit is now over.")
continue
# we've received a 500 or 502, unconditional retry
if r.status in {500, 502}:
await asyncio.sleep(1 + tries * 2, loop=self.loop)
continue
# the usual error cases
if r.status == 403:
raise Forbidden(r, data)
elif r.status == 404:
raise NotFound(r, data)
else:
raise HTTPException(r, data)
# We've run out of retries, raise.
raise HTTPException(r, data)
async def get_attachment(self, url):
async with self._session.get(url) as resp:
if resp.status == 200:
return await resp.read()
elif resp.status == 404:
raise NotFound(resp, "attachment not found")
elif resp.status == 403:
raise Forbidden(resp, "cannot retrieve attachment")
else:
raise HTTPException(resp, "failed to get attachment")
# state management
async def close(self):
await self._session.close()
def _token(self, token, *, bot=True):
self.token = token
self.bot_token = bot
self._ack_token = None
# login management
async def static_login(self, token, *, bot):
old_token, old_bot = self.token, self.bot_token
self._token(token, bot=bot)
try:
data = await self.request(Route("GET", "/users/@me"))
except HTTPException as exc:
self._token(old_token, bot=old_bot)
if exc.response.status == 401:
raise LoginFailure("Improper token has been passed.") from exc
raise
return data
def logout(self):
return self.request(Route("POST", "/auth/logout"))
# Group functionality
def start_group(self, user_id, recipients):
payload = {"recipients": recipients}
return self.request(
Route("POST", "/users/{user_id}/channels", user_id=user_id), json=payload
)
def leave_group(self, channel_id):
return self.request(Route("DELETE", "/channels/{channel_id}", channel_id=channel_id))
def add_group_recipient(self, channel_id, user_id):
r = Route(
"PUT",
"/channels/{channel_id}/recipients/{user_id}",
channel_id=channel_id,
user_id=user_id,
)
return self.request(r)
def remove_group_recipient(self, channel_id, user_id):
r = Route(
"DELETE",
"/channels/{channel_id}/recipients/{user_id}",
channel_id=channel_id,
user_id=user_id,
)
return self.request(r)
def edit_group(self, channel_id, **options):
valid_keys = ("name", "icon")
payload = {k: v for k, v in options.items() if k in valid_keys}
return self.request(
Route("PATCH", "/channels/{channel_id}", channel_id=channel_id), json=payload
)
def convert_group(self, channel_id):
return self.request(Route("POST", "/channels/{channel_id}/convert", channel_id=channel_id))
# Message management
def start_private_message(self, user_id):
payload = {"recipient_id": user_id}
return self.request(Route("POST", "/users/@me/channels"), json=payload)
def send_message(self, channel_id, content, *, tts=False, embed=None, nonce=None):
r = Route("POST", "/channels/{channel_id}/messages", channel_id=channel_id)
payload = {}
if content:
payload["content"] = content
if tts:
payload["tts"] = True
if embed:
payload["embed"] = embed
if nonce:
payload["nonce"] = nonce
return self.request(r, json=payload)
def send_typing(self, channel_id):
return self.request(Route("POST", "/channels/{channel_id}/typing", channel_id=channel_id))
def send_files(self, channel_id, *, files, content=None, tts=False, embed=None, nonce=None):
r = Route("POST", "/channels/{channel_id}/messages", channel_id=channel_id)
form = aiohttp.FormData()
payload = {"tts": tts}
if content:
payload["content"] = content
if embed:
payload["embed"] = embed
if nonce:
payload["nonce"] = nonce
form.add_field("payload_json", utils.to_json(payload))
if len(files) == 1:
fp = files[0]
form.add_field("file", fp[0], filename=fp[1], content_type="application/octet-stream")
else:
for index, (buffer, filename) in enumerate(files):
form.add_field(
"file%s" % index,
buffer,
filename=filename,
content_type="application/octet-stream",
)
return self.request(r, data=form)
async def ack_message(self, channel_id, message_id):
r = Route(
"POST",
"/channels/{channel_id}/messages/{message_id}/ack",
channel_id=channel_id,
message_id=message_id,
)
data = await self.request(r, json={"token": self._ack_token})
self._ack_token = data["token"]
def ack_guild(self, guild_id):
return self.request(Route("POST", "/guilds/{guild_id}/ack", guild_id=guild_id))
def delete_message(self, channel_id, message_id, *, reason=None):
r = Route(
"DELETE",
"/channels/{channel_id}/messages/{message_id}",
channel_id=channel_id,
message_id=message_id,
)
return self.request(r, reason=reason)
def delete_messages(self, channel_id, message_ids, *, reason=None):
r = Route("POST", "/channels/{channel_id}/messages/bulk_delete", channel_id=channel_id)
payload = {"messages": message_ids}
return self.request(r, json=payload, reason=reason)
def edit_message(self, message_id, channel_id, **fields):
r = Route(
"PATCH",
"/channels/{channel_id}/messages/{message_id}",
channel_id=channel_id,
message_id=message_id,
)
return self.request(r, json=fields)
def add_reaction(self, message_id, channel_id, emoji):
r = Route(
"PUT",
"/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/@me",
channel_id=channel_id,
message_id=message_id,
emoji=emoji,
)
return self.request(r, header_bypass_delay=0.25)
def remove_reaction(self, message_id, channel_id, emoji, member_id):
r = Route(
"DELETE",
"/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/{member_id}",
channel_id=channel_id,
message_id=message_id,
member_id=member_id,
emoji=emoji,
)
return self.request(r, header_bypass_delay=0.25)
def remove_own_reaction(self, message_id, channel_id, emoji):
r = Route(
"DELETE",
"/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/@me",
channel_id=channel_id,
message_id=message_id,
emoji=emoji,
)
return self.request(r, header_bypass_delay=0.25)
def get_reaction_users(self, message_id, channel_id, emoji, limit, after=None):
r = Route(
"GET",
"/channels/{channel_id}/messages/{message_id}/reactions/{emoji}",
channel_id=channel_id,
message_id=message_id,
emoji=emoji,
)
params = {"limit": limit}
if after:
params["after"] = after
return self.request(r, params=params)
def clear_reactions(self, message_id, channel_id):
r = Route(
"DELETE",
"/channels/{channel_id}/messages/{message_id}/reactions",
channel_id=channel_id,
message_id=message_id,
)
return self.request(r)
def get_message(self, channel_id, message_id):
r = Route(
"GET",
"/channels/{channel_id}/messages/{message_id}",
channel_id=channel_id,
message_id=message_id,
)
return self.request(r)
def logs_from(self, channel_id, limit, before=None, after=None, around=None):
params = {"limit": limit}
if before:
params["before"] = before
if after:
params["after"] = after
if around:
params["around"] = around
return self.request(
Route("GET", "/channels/{channel_id}/messages", channel_id=channel_id), params=params
)
def pin_message(self, channel_id, message_id):
return self.request(
Route(
"PUT",
"/channels/{channel_id}/pins/{message_id}",
channel_id=channel_id,
message_id=message_id,
)
)
def unpin_message(self, channel_id, message_id):
return self.request(
Route(
"DELETE",
"/channels/{channel_id}/pins/{message_id}",
channel_id=channel_id,
message_id=message_id,
)
)
def pins_from(self, channel_id):
return self.request(Route("GET", "/channels/{channel_id}/pins", channel_id=channel_id))
# Member management
def kick(self, user_id, guild_id, reason=None):
r = Route(
"DELETE", "/guilds/{guild_id}/members/{user_id}", guild_id=guild_id, user_id=user_id
)
if reason:
# thanks aiohttp
r.url = "{0.url}?reason={1}".format(r, _uriquote(reason))
return self.request(r)
def ban(self, user_id, guild_id, delete_message_days=1, reason=None):
r = Route("PUT", "/guilds/{guild_id}/bans/{user_id}", guild_id=guild_id, user_id=user_id)
params = {"delete-message-days": delete_message_days}
if reason:
# thanks aiohttp
r.url = "{0.url}?reason={1}".format(r, _uriquote(reason))
return self.request(r, params=params)
def unban(self, user_id, guild_id, *, reason=None):
r = Route(
"DELETE", "/guilds/{guild_id}/bans/{user_id}", guild_id=guild_id, user_id=user_id
)
return self.request(r, reason=reason)
def guild_voice_state(self, user_id, guild_id, *, mute=None, deafen=None, reason=None):
r = Route(
"PATCH", "/guilds/{guild_id}/members/{user_id}", guild_id=guild_id, user_id=user_id
)
payload = {}
if mute is not None:
payload["mute"] = mute
if deafen is not None:
payload["deaf"] = deafen
return self.request(r, json=payload, reason=reason)
def edit_profile(self, password, username, avatar, **fields):
payload = {"password": password, "username": username, "avatar": avatar}
if "email" in fields:
payload["email"] = fields["email"]
if "new_password" in fields:
payload["new_password"] = fields["new_password"]
return self.request(Route("PATCH", "/users/@me"), json=payload)
def change_my_nickname(self, guild_id, nickname, *, reason=None):
r = Route("PATCH", "/guilds/{guild_id}/members/@me/nick", guild_id=guild_id)
payload = {"nick": nickname}
return self.request(r, json=payload, reason=reason)
def change_nickname(self, guild_id, user_id, nickname, *, reason=None):
r = Route(
"PATCH", "/guilds/{guild_id}/members/{user_id}", guild_id=guild_id, user_id=user_id
)
payload = {"nick": nickname}
return self.request(r, json=payload, reason=reason)
def edit_member(self, guild_id, user_id, *, reason=None, **fields):
r = Route(
"PATCH", "/guilds/{guild_id}/members/{user_id}", guild_id=guild_id, user_id=user_id
)
return self.request(r, json=fields, reason=reason)
# Channel management
def edit_channel(self, channel_id, *, reason=None, **options):
r = Route("PATCH", "/channels/{channel_id}", channel_id=channel_id)
valid_keys = (
"name",
"parent_id",
"topic",
"bitrate",
"nsfw",
"user_limit",
"position",
"permission_overwrites",
"rate_limit_per_user",
)
payload = {k: v for k, v in options.items() if k in valid_keys}
return self.request(r, reason=reason, json=payload)
def bulk_channel_update(self, guild_id, data, *, reason=None):
r = Route("PATCH", "/guilds/{guild_id}/channels", guild_id=guild_id)
return self.request(r, json=data, reason=reason)
def create_channel(
self,
guild_id,
name,
channel_type,
parent_id=None,
permission_overwrites=None,
*,
reason=None
):
payload = {"name": name, "type": channel_type}
if permission_overwrites is not None:
payload["permission_overwrites"] = permission_overwrites
if parent_id is not None:
payload["parent_id"] = parent_id
return self.request(
Route("POST", "/guilds/{guild_id}/channels", guild_id=guild_id),
json=payload,
reason=reason,
)
def delete_channel(self, channel_id, *, reason=None):
return self.request(
Route("DELETE", "/channels/{channel_id}", channel_id=channel_id), reason=reason
)
# Webhook management
def create_webhook(self, channel_id, *, name, avatar=None):
payload = {"name": name}
if avatar is not None:
payload["avatar"] = avatar
return self.request(
Route("POST", "/channels/{channel_id}/webhooks", channel_id=channel_id), json=payload
)
def channel_webhooks(self, channel_id):
return self.request(Route("GET", "/channels/{channel_id}/webhooks", channel_id=channel_id))
def guild_webhooks(self, guild_id):
return self.request(Route("GET", "/guilds/{guild_id}/webhooks", guild_id=guild_id))
def get_webhook(self, webhook_id):
return self.request(Route("GET", "/webhooks/{webhook_id}", webhook_id=webhook_id))
# Guild management
def leave_guild(self, guild_id):
return self.request(Route("DELETE", "/users/@me/guilds/{guild_id}", guild_id=guild_id))
def delete_guild(self, guild_id):
return self.request(Route("DELETE", "/guilds/{guild_id}", guild_id=guild_id))
def create_guild(self, name, region, icon):
payload = {"name": name, "icon": icon, "region": region}
return self.request(Route("POST", "/guilds"), json=payload)
def edit_guild(self, guild_id, *, reason=None, **fields):
valid_keys = (
"name",
"region",
"icon",
"afk_timeout",
"owner_id",
"afk_channel_id",
"splash",
"verification_level",
"system_channel_id",
"default_message_notifications",
"explicit_content_filter",
)
payload = {k: v for k, v in fields.items() if k in valid_keys}
return self.request(
Route("PATCH", "/guilds/{guild_id}", guild_id=guild_id), json=payload, reason=reason
)
def get_bans(self, guild_id):
return self.request(Route("GET", "/guilds/{guild_id}/bans", guild_id=guild_id))
def get_ban(self, user_id, guild_id):
return self.request(
Route("GET", "/guilds/{guild_id}/bans/{user_id}", guild_id=guild_id, user_id=user_id)
)
def get_vanity_code(self, guild_id):
return self.request(Route("GET", "/guilds/{guild_id}/vanity-url", guild_id=guild_id))
def change_vanity_code(self, guild_id, code, *, reason=None):
payload = {"code": code}
return self.request(
Route("PATCH", "/guilds/{guild_id}/vanity-url", guild_id=guild_id),
json=payload,
reason=reason,
)
def prune_members(self, guild_id, days, *, reason=None):
params = {"days": days}
return self.request(
Route("POST", "/guilds/{guild_id}/prune", guild_id=guild_id),
params=params,
reason=reason,
)
def estimate_pruned_members(self, guild_id, days):
params = {"days": days}
return self.request(
Route("GET", "/guilds/{guild_id}/prune", guild_id=guild_id), params=params
)
def create_custom_emoji(self, guild_id, name, image, *, roles=None, reason=None):
payload = {"name": name, "image": image, "roles": roles or []}
r = Route("POST", "/guilds/{guild_id}/emojis", guild_id=guild_id)
return self.request(r, json=payload, reason=reason)
def delete_custom_emoji(self, guild_id, emoji_id, *, reason=None):
r = Route(
"DELETE", "/guilds/{guild_id}/emojis/{emoji_id}", guild_id=guild_id, emoji_id=emoji_id
)
return self.request(r, reason=reason)
def edit_custom_emoji(self, guild_id, emoji_id, *, name, roles=None, reason=None):
payload = {"name": name, "roles": roles or []}
r = Route(
"PATCH", "/guilds/{guild_id}/emojis/{emoji_id}", guild_id=guild_id, emoji_id=emoji_id
)
return self.request(r, json=payload, reason=reason)
def get_audit_logs(
self, guild_id, limit=100, before=None, after=None, user_id=None, action_type=None
):
params = {"limit": limit}
if before:
params["before"] = before
if after:
params["after"] = after
if user_id:
params["user_id"] = user_id
if action_type:
params["action_type"] = action_type
r = Route("GET", "/guilds/{guild_id}/audit-logs", guild_id=guild_id)
return self.request(r, params=params)
# Invite management
def create_invite(self, channel_id, *, reason=None, **options):
r = Route("POST", "/channels/{channel_id}/invites", channel_id=channel_id)
payload = {
"max_age": options.get("max_age", 0),
"max_uses": options.get("max_uses", 0),
"temporary": options.get("temporary", False),
"unique": options.get("unique", True),
}
return self.request(r, reason=reason, json=payload)
def get_invite(self, invite_id):
return self.request(Route("GET", "/invite/{invite_id}", invite_id=invite_id))
def invites_from(self, guild_id):
return self.request(Route("GET", "/guilds/{guild_id}/invites", guild_id=guild_id))
def invites_from_channel(self, channel_id):
return self.request(Route("GET", "/channels/{channel_id}/invites", channel_id=channel_id))
def delete_invite(self, invite_id, *, reason=None):
return self.request(
Route("DELETE", "/invite/{invite_id}", invite_id=invite_id), reason=reason
)
# Role management
def edit_role(self, guild_id, role_id, *, reason=None, **fields):
r = Route(
"PATCH", "/guilds/{guild_id}/roles/{role_id}", guild_id=guild_id, role_id=role_id
)
valid_keys = ("name", "permissions", "color", "hoist", "mentionable")
payload = {k: v for k, v in fields.items() if k in valid_keys}
return self.request(r, json=payload, reason=reason)
def delete_role(self, guild_id, role_id, *, reason=None):
r = Route(
"DELETE", "/guilds/{guild_id}/roles/{role_id}", guild_id=guild_id, role_id=role_id
)
return self.request(r, reason=reason)
def replace_roles(self, user_id, guild_id, role_ids, *, reason=None):
return self.edit_member(guild_id=guild_id, user_id=user_id, roles=role_ids, reason=reason)
def create_role(self, guild_id, *, reason=None, **fields):
r = Route("POST", "/guilds/{guild_id}/roles", guild_id=guild_id)
return self.request(r, json=fields, reason=reason)
def move_role_position(self, guild_id, positions, *, reason=None):
r = Route("PATCH", "/guilds/{guild_id}/roles", guild_id=guild_id)
return self.request(r, json=positions, reason=reason)
def add_role(self, guild_id, user_id, role_id, *, reason=None):
r = Route(
"PUT",
"/guilds/{guild_id}/members/{user_id}/roles/{role_id}",
guild_id=guild_id,
user_id=user_id,
role_id=role_id,
)
return self.request(r, reason=reason)
def remove_role(self, guild_id, user_id, role_id, *, reason=None):
r = Route(
"DELETE",
"/guilds/{guild_id}/members/{user_id}/roles/{role_id}",
guild_id=guild_id,
user_id=user_id,
role_id=role_id,
)
return self.request(r, reason=reason)
def edit_channel_permissions(self, channel_id, target, allow, deny, type, *, reason=None):
payload = {"id": target, "allow": allow, "deny": deny, "type": type}
r = Route(
"PUT",
"/channels/{channel_id}/permissions/{target}",
channel_id=channel_id,
target=target,
)
return self.request(r, json=payload, reason=reason)
def delete_channel_permissions(self, channel_id, target, *, reason=None):
r = Route(
"DELETE",
"/channels/{channel_id}/permissions/{target}",
channel_id=channel_id,
target=target,
)
return self.request(r, reason=reason)
# Voice management
def move_member(self, user_id, guild_id, channel_id, *, reason=None):
return self.edit_member(
guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason
)
# Relationship related
def remove_relationship(self, user_id):
r = Route("DELETE", "/users/@me/relationships/{user_id}", user_id=user_id)
return self.request(r)
def add_relationship(self, user_id, type=None):
r = Route("PUT", "/users/@me/relationships/{user_id}", user_id=user_id)
payload = {}
if type is not None:
payload["type"] = type
return self.request(r, json=payload)
def send_friend_request(self, username, discriminator):
r = Route("POST", "/users/@me/relationships")
payload = {"username": username, "discriminator": int(discriminator)}
return self.request(r, json=payload)
# Misc
def application_info(self):
return self.request(Route("GET", "/oauth2/applications/@me"))
async def get_gateway(self, *, encoding="json", v=6, zlib=True):
try:
data = await self.request(Route("GET", "/gateway"))
except HTTPException as exc:
raise GatewayNotFound() from exc
if zlib:
value = "{0}?encoding={1}&v={2}&compress=zlib-stream"
else:
value = "{0}?encoding={1}&v={2}"
return value.format(data["url"], encoding, v)
async def get_bot_gateway(self, *, encoding="json", v=6, zlib=True):
try:
data = await self.request(Route("GET", "/gateway/bot"))
except HTTPException as exc:
raise GatewayNotFound() from exc
if zlib:
value = "{0}?encoding={1}&v={2}&compress=zlib-stream"
else:
value = "{0}?encoding={1}&v={2}"
return data["shards"], value.format(data["url"], encoding, v)
def get_user_info(self, user_id):
return self.request(Route("GET", "/users/{user_id}", user_id=user_id))
def get_user_profile(self, user_id):
return self.request(Route("GET", "/users/{user_id}/profile", user_id=user_id))
def get_mutual_friends(self, user_id):
return self.request(Route("GET", "/users/{user_id}/relationships", user_id=user_id))
def change_hypesquad_house(self, house_id):
payload = {"house_id": house_id}
return self.request(Route("POST", "/hypesquad/online"), json=payload)
def leave_hypesquad_house(self):
return self.request(Route("DELETE", "/hypesquad/online"))

View File

@@ -1,176 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from .utils import parse_time
from .mixins import Hashable
from .object import Object
class Invite(Hashable):
"""Represents a Discord :class:`Guild` or :class:`abc.GuildChannel` invite.
Depending on the way this object was created, some of the attributes can
have a value of ``None``.
.. container:: operations
.. describe:: x == y
Checks if two invites are equal.
.. describe:: x != y
Checks if two invites are not equal.
.. describe:: hash(x)
Returns the invite hash.
.. describe:: str(x)
Returns the invite URL.
Attributes
-----------
max_age: :class:`int`
How long the before the invite expires in seconds. A value of 0 indicates that it doesn't expire.
code: :class:`str`
The URL fragment used for the invite.
guild: :class:`Guild`
The guild the invite is for.
revoked: :class:`bool`
Indicates if the invite has been revoked.
created_at: `datetime.datetime`
A datetime object denoting the time the invite was created.
temporary: :class:`bool`
Indicates that the invite grants temporary membership.
If True, members who joined via this invite will be kicked upon disconnect.
uses: :class:`int`
How many times the invite has been used.
max_uses: :class:`int`
How many times the invite can be used.
inviter: :class:`User`
The user who created the invite.
channel: :class:`abc.GuildChannel`
The channel the invite is for.
"""
__slots__ = (
"max_age",
"code",
"guild",
"revoked",
"created_at",
"uses",
"temporary",
"max_uses",
"inviter",
"channel",
"_state",
)
def __init__(self, *, state, data):
self._state = state
self.max_age = data.get("max_age")
self.code = data.get("code")
self.guild = data.get("guild")
self.revoked = data.get("revoked")
self.created_at = parse_time(data.get("created_at"))
self.temporary = data.get("temporary")
self.uses = data.get("uses")
self.max_uses = data.get("max_uses")
inviter_data = data.get("inviter")
self.inviter = None if inviter_data is None else self._state.store_user(inviter_data)
self.channel = data.get("channel")
@classmethod
def from_incomplete(cls, *, state, data):
guild_id = int(data["guild"]["id"])
channel_id = int(data["channel"]["id"])
guild = state._get_guild(guild_id)
if guild is not None:
channel = guild.get_channel(channel_id)
else:
guild = Object(id=guild_id)
channel = Object(id=channel_id)
guild.name = data["guild"]["name"]
guild.splash = data["guild"]["splash"]
guild.splash_url = ""
if guild.splash:
guild.splash_url = "https://cdn.discordapp.com/splashes/{0.id}/{0.splash}.jpg?size=2048".format(
guild
)
channel.name = data["channel"]["name"]
data["guild"] = guild
data["channel"] = channel
return cls(state=state, data=data)
def __str__(self):
return self.url
def __repr__(self):
return "<Invite code={0.code!r}>".format(self)
def __hash__(self):
return hash(self.code)
@property
def id(self):
"""Returns the proper code portion of the invite."""
return self.code
@property
def url(self):
"""A property that retrieves the invite URL."""
return "http://discord.gg/" + self.code
async def delete(self, *, reason=None):
"""|coro|
Revokes the instant invite.
You must have the :attr:`~Permissions.manage_channels` permission to do this.
Parameters
-----------
reason: Optional[str]
The reason for deleting this invite. Shows up on the audit log.
Raises
-------
Forbidden
You do not have permissions to revoke invites.
NotFound
The invite is invalid or expired.
HTTPException
Revoking the invite failed.
"""
await self._state.http.delete_invite(self.code, reason=reason)

View File

@@ -1,489 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import asyncio
import datetime
from .errors import NoMoreItems
from .utils import time_snowflake, maybe_coroutine
from .object import Object
from .audit_logs import AuditLogEntry
class _AsyncIterator:
__slots__ = ()
def get(self, **attrs):
def predicate(elem):
for attr, val in attrs.items():
nested = attr.split("__")
obj = elem
for attribute in nested:
obj = getattr(obj, attribute)
if obj != val:
return False
return True
return self.find(predicate)
async def find(self, predicate):
while True:
try:
elem = await self.next()
except NoMoreItems:
return None
ret = await maybe_coroutine(predicate, elem)
if ret:
return elem
def map(self, func):
return _MappedAsyncIterator(self, func)
def filter(self, predicate):
return _FilteredAsyncIterator(self, predicate)
async def flatten(self):
ret = []
while True:
try:
item = await self.next()
except NoMoreItems:
return ret
else:
ret.append(item)
def __aiter__(self):
return self
async def __anext__(self):
try:
msg = await self.next()
except NoMoreItems:
raise StopAsyncIteration()
else:
return msg
def _identity(x):
return x
class _MappedAsyncIterator(_AsyncIterator):
def __init__(self, iterator, func):
self.iterator = iterator
self.func = func
async def next(self):
# this raises NoMoreItems and will propagate appropriately
item = await self.iterator.next()
return await maybe_coroutine(self.func, item)
class _FilteredAsyncIterator(_AsyncIterator):
def __init__(self, iterator, predicate):
self.iterator = iterator
if predicate is None:
predicate = _identity
self.predicate = predicate
async def next(self):
getter = self.iterator.next
pred = self.predicate
while True:
# propagate NoMoreItems similar to _MappedAsyncIterator
item = await getter()
ret = await maybe_coroutine(pred, item)
if ret:
return item
class ReactionIterator(_AsyncIterator):
def __init__(self, message, emoji, limit=100, after=None):
self.message = message
self.limit = limit
self.after = after
state = message._state
self.getter = state.http.get_reaction_users
self.state = state
self.emoji = emoji
self.guild = message.guild
self.channel_id = message.channel.id
self.users = asyncio.Queue(loop=state.loop)
async def next(self):
if self.users.empty():
await self.fill_users()
try:
return self.users.get_nowait()
except asyncio.QueueEmpty:
raise NoMoreItems()
async def fill_users(self):
# this is a hack because >circular imports<
from .user import User
if self.limit > 0:
retrieve = self.limit if self.limit <= 100 else 100
after = self.after.id if self.after else None
data = await self.getter(
self.message.id, self.channel_id, self.emoji, retrieve, after=after
)
if data:
self.limit -= retrieve
self.after = Object(id=int(data[0]["id"]))
if self.guild is None:
for element in reversed(data):
await self.users.put(User(state=self.state, data=element))
else:
for element in reversed(data):
member_id = int(element["id"])
member = self.guild.get_member(member_id)
if member is not None:
await self.users.put(member)
else:
await self.users.put(User(state=self.state, data=element))
class HistoryIterator(_AsyncIterator):
"""Iterator for receiving a channel's message history.
The messages endpoint has two behaviours we care about here:
If `before` is specified, the messages endpoint returns the `limit`
newest messages before `before`, sorted with newest first. For filling over
100 messages, update the `before` parameter to the oldest message received.
Messages will be returned in order by time.
If `after` is specified, it returns the `limit` oldest messages after
`after`, sorted with newest first. For filling over 100 messages, update the
`after` parameter to the newest message received. If messages are not
reversed, they will be out of order (99-0, 199-100, so on)
A note that if both before and after are specified, before is ignored by the
messages endpoint.
Parameters
-----------
messageable: :class:`abc.Messageable`
Messageable class to retrieve message history fro.
limit : int
Maximum number of messages to retrieve
before : :class:`Message` or id-like
Message before which all messages must be.
after : :class:`Message` or id-like
Message after which all messages must be.
around : :class:`Message` or id-like
Message around which all messages must be. Limit max 101. Note that if
limit is an even number, this will return at most limit+1 messages.
reverse: bool
If set to true, return messages in oldest->newest order. Recommended
when using with "after" queries with limit over 100, otherwise messages
will be out of order.
"""
def __init__(self, messageable, limit, before=None, after=None, around=None, reverse=None):
if isinstance(before, datetime.datetime):
before = Object(id=time_snowflake(before, high=False))
if isinstance(after, datetime.datetime):
after = Object(id=time_snowflake(after, high=True))
if isinstance(around, datetime.datetime):
around = Object(id=time_snowflake(around))
self.messageable = messageable
self.limit = limit
self.before = before
self.after = after
self.around = around
if reverse is None:
self.reverse = after is not None
else:
self.reverse = reverse
self._filter = None # message dict -> bool
self.state = self.messageable._state
self.logs_from = self.state.http.logs_from
self.messages = asyncio.Queue(loop=self.state.loop)
if self.around:
if self.limit is None:
raise ValueError("history does not support around with limit=None")
if self.limit > 101:
raise ValueError("history max limit 101 when specifying around parameter")
elif self.limit == 101:
self.limit = 100 # Thanks discord
elif self.limit == 1:
raise ValueError("Use get_message.")
self._retrieve_messages = self._retrieve_messages_around_strategy
if self.before and self.after:
self._filter = lambda m: self.after.id < int(m["id"]) < self.before.id
elif self.before:
self._filter = lambda m: int(m["id"]) < self.before.id
elif self.after:
self._filter = lambda m: self.after.id < int(m["id"])
elif self.before and self.after:
if self.reverse:
self._retrieve_messages = self._retrieve_messages_after_strategy
self._filter = lambda m: int(m["id"]) < self.before.id
else:
self._retrieve_messages = self._retrieve_messages_before_strategy
self._filter = lambda m: int(m["id"]) > self.after.id
elif self.after:
self._retrieve_messages = self._retrieve_messages_after_strategy
else:
self._retrieve_messages = self._retrieve_messages_before_strategy
async def next(self):
if self.messages.empty():
await self.fill_messages()
try:
return self.messages.get_nowait()
except asyncio.QueueEmpty:
raise NoMoreItems()
def _get_retrieve(self):
l = self.limit
if l is None:
r = 100
elif l <= 100:
r = l
else:
r = 100
self.retrieve = r
return r > 0
async def flatten(self):
# this is similar to fill_messages except it uses a list instead
# of a queue to place the messages in.
result = []
channel = await self.messageable._get_channel()
self.channel = channel
while self._get_retrieve():
data = await self._retrieve_messages(self.retrieve)
if len(data) < 100:
self.limit = 0 # terminate the infinite loop
if self.reverse:
data = reversed(data)
if self._filter:
data = filter(self._filter, data)
for element in data:
result.append(self.state.create_message(channel=channel, data=element))
return result
async def fill_messages(self):
if not hasattr(self, "channel"):
# do the required set up
channel = await self.messageable._get_channel()
self.channel = channel
if self._get_retrieve():
data = await self._retrieve_messages(self.retrieve)
if self.limit is None and len(data) < 100:
self.limit = 0 # terminate the infinite loop
if self.reverse:
data = reversed(data)
if self._filter:
data = filter(self._filter, data)
channel = self.channel
for element in data:
await self.messages.put(self.state.create_message(channel=channel, data=element))
async def _retrieve_messages(self, retrieve):
"""Retrieve messages and update next parameters."""
pass
async def _retrieve_messages_before_strategy(self, retrieve):
"""Retrieve messages using before parameter."""
before = self.before.id if self.before else None
data = await self.logs_from(self.channel.id, retrieve, before=before)
if len(data):
if self.limit is not None:
self.limit -= retrieve
self.before = Object(id=int(data[-1]["id"]))
return data
async def _retrieve_messages_after_strategy(self, retrieve):
"""Retrieve messages using after parameter."""
after = self.after.id if self.after else None
data = await self.logs_from(self.channel.id, retrieve, after=after)
if len(data):
if self.limit is not None:
self.limit -= retrieve
self.after = Object(id=int(data[0]["id"]))
return data
async def _retrieve_messages_around_strategy(self, retrieve):
"""Retrieve messages using around parameter."""
if self.around:
around = self.around.id if self.around else None
data = await self.logs_from(self.channel.id, retrieve, around=around)
self.around = None
return data
return []
class AuditLogIterator(_AsyncIterator):
def __init__(
self,
guild,
limit=None,
before=None,
after=None,
reverse=None,
user_id=None,
action_type=None,
):
if isinstance(before, datetime.datetime):
before = Object(id=time_snowflake(before, high=False))
if isinstance(after, datetime.datetime):
after = Object(id=time_snowflake(after, high=True))
self.guild = guild
self.loop = guild._state.loop
self.request = guild._state.http.get_audit_logs
self.limit = limit
self.before = before
self.user_id = user_id
self.action_type = action_type
self.after = after
self._users = {}
self._state = guild._state
if reverse is None:
self.reverse = after is not None
else:
self.reverse = reverse
self._filter = None # entry dict -> bool
self.entries = asyncio.Queue(loop=self.loop)
if self.before and self.after:
if self.reverse:
self._strategy = self._after_strategy
self._filter = lambda m: int(m["id"]) < self.before.id
else:
self._strategy = self._before_strategy
self._filter = lambda m: int(m["id"]) > self.after.id
elif self.after:
self._strategy = self._after_strategy
else:
self._strategy = self._before_strategy
async def _before_strategy(self, retrieve):
before = self.before.id if self.before else None
data = await self.request(
self.guild.id,
limit=retrieve,
user_id=self.user_id,
action_type=self.action_type,
before=before,
)
entries = data.get("audit_log_entries", [])
if len(data) and entries:
if self.limit is not None:
self.limit -= retrieve
self.before = Object(id=int(entries[-1]["id"]))
return data.get("users", []), entries
async def _after_strategy(self, retrieve):
after = self.after.id if self.after else None
data = await self.request(
self.guild.id,
limit=retrieve,
user_id=self.user_id,
action_type=self.action_type,
after=after,
)
entries = data.get("audit_log_entries", [])
if len(data) and entries:
if self.limit is not None:
self.limit -= retrieve
self.after = Object(id=int(entries[0]["id"]))
return data.get("users", []), entries
async def next(self):
if self.entries.empty():
await self._fill()
try:
return self.entries.get_nowait()
except asyncio.QueueEmpty:
raise NoMoreItems()
def _get_retrieve(self):
l = self.limit
if l is None:
r = 100
elif l <= 100:
r = l
else:
r = 100
self.retrieve = r
return r > 0
async def _fill(self):
from .user import User
if self._get_retrieve():
users, data = await self._strategy(self.retrieve)
if self.limit is None and len(data) < 100:
self.limit = 0 # terminate the infinite loop
if self.reverse:
data = reversed(data)
if self._filter:
data = filter(self._filter, data)
for user in users:
u = User(data=user, state=self._state)
self._users[u.id] = u
for element in data:
# TODO: remove this if statement later
if element["action_type"] is None:
continue
await self.entries.put(
AuditLogEntry(data=element, users=self._users, guild=self.guild)
)

View File

@@ -1,621 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import itertools
import discord.abc
from . import utils
from .user import BaseUser, User
from .activity import create_activity
from .permissions import Permissions
from .enums import Status, try_enum
from .colour import Colour
from .object import Object
class VoiceState:
"""Represents a Discord user's voice state.
Attributes
------------
deaf: :class:`bool`
Indicates if the user is currently deafened by the guild.
mute: :class:`bool`
Indicates if the user is currently muted by the guild.
self_mute: :class:`bool`
Indicates if the user is currently muted by their own accord.
self_deaf: :class:`bool`
Indicates if the user is currently deafened by their own accord.
afk: :class:`bool`
Indicates if the user is currently in the AFK channel in the guild.
channel: :class:`VoiceChannel`
The voice channel that the user is currently connected to. None if the user
is not currently in a voice channel.
"""
__slots__ = ("session_id", "deaf", "mute", "self_mute", "self_deaf", "afk", "channel")
def __init__(self, *, data, channel=None):
self.session_id = data.get("session_id")
self._update(data, channel)
def _update(self, data, channel):
self.self_mute = data.get("self_mute", False)
self.self_deaf = data.get("self_deaf", False)
self.afk = data.get("suppress", False)
self.mute = data.get("mute", False)
self.deaf = data.get("deaf", False)
self.channel = channel
def __repr__(self):
return "<VoiceState self_mute={0.self_mute} self_deaf={0.self_deaf} channel={0.channel!r}>".format(
self
)
def flatten_user(cls):
for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()):
# ignore private/special methods
if attr.startswith("_"):
continue
# don't override what we already have
if attr in cls.__dict__:
continue
# if it's a slotted attribute or a property, redirect it
# slotted members are implemented as member_descriptors in Type.__dict__
if not hasattr(value, "__annotations__"):
def getter(self, x=attr):
return getattr(self._user, x)
setattr(cls, attr, property(getter, doc="Equivalent to :attr:`User.%s`" % attr))
else:
# probably a member function by now
def generate_function(x):
def general(self, *args, **kwargs):
return getattr(self._user, x)(*args, **kwargs)
general.__name__ = x
return general
func = generate_function(attr)
func.__doc__ = value.__doc__
setattr(cls, attr, func)
return cls
_BaseUser = discord.abc.User
@flatten_user
class Member(discord.abc.Messageable, _BaseUser):
"""Represents a Discord member to a :class:`Guild`.
This implements a lot of the functionality of :class:`User`.
.. container:: operations
.. describe:: x == y
Checks if two members are equal.
Note that this works with :class:`User` instances too.
.. describe:: x != y
Checks if two members are not equal.
Note that this works with :class:`User` instances too.
.. describe:: hash(x)
Returns the member's hash.
.. describe:: str(x)
Returns the member's name with the discriminator.
Attributes
----------
joined_at: `datetime.datetime`
A datetime object that specifies the date and time in UTC that the member joined the guild for
the first time.
activities: Tuple[Union[:class:`Game`, :class:`Streaming`, :class:`Spotify`, :class:`Activity`]]
The activities that the user is currently doing.
guild: :class:`Guild`
The guild that the member belongs to.
nick: Optional[:class:`str`]
The guild specific nickname of the user.
"""
__slots__ = (
"_roles",
"joined_at",
"_client_status",
"activities",
"guild",
"nick",
"_user",
"_state",
)
def __init__(self, *, data, guild, state):
self._state = state
self._user = state.store_user(data["user"])
self.guild = guild
self.joined_at = utils.parse_time(data.get("joined_at"))
self._update_roles(data)
self._client_status = {None: Status.offline}
self.activities = tuple(map(create_activity, data.get("activities", [])))
self.nick = data.get("nick", None)
def __str__(self):
return str(self._user)
def __repr__(self):
return (
"<Member id={1.id} name={1.name!r} discriminator={1.discriminator!r}"
" bot={1.bot} nick={0.nick!r} guild={0.guild!r}>".format(self, self._user)
)
def __eq__(self, other):
return isinstance(other, _BaseUser) and other.id == self.id
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self._user)
@classmethod
def _copy(cls, member):
self = cls.__new__(cls) # to bypass __init__
self._roles = utils.SnowflakeList(member._roles, is_sorted=True)
self.joined_at = member.joined_at
self._client_status = member._client_status.copy()
self.guild = member.guild
self.nick = member.nick
self.activities = member.activities
self._state = member._state
self._user = User._copy(member._user)
return self
async def _get_channel(self):
ch = await self.create_dm()
return ch
def _update_roles(self, data):
self._roles = utils.SnowflakeList(map(int, data["roles"]))
def _update(self, data, user=None):
if user:
self._user.name = user["username"]
self._user.discriminator = user["discriminator"]
self._user.avatar = user["avatar"]
self._user.bot = user.get("bot", False)
# the nickname change is optional,
# if it isn't in the payload then it didn't change
try:
self.nick = data["nick"]
except KeyError:
pass
self._update_roles(data)
def _presence_update(self, data, user):
self.activities = tuple(map(create_activity, data.get("activities", [])))
self._client_status = {key: value for key, value in data.get("client_status", {}).items()}
self._client_status[None] = data["status"]
if len(user) > 1:
u = self._user
u.name = user.get("username", u.name)
u.avatar = user.get("avatar", u.avatar)
u.discriminator = user.get("discriminator", u.discriminator)
@property
def status(self):
""":class:`Status`: The member's overall status. If the value is unknown, then it will be a :class:`str` instead."""
return try_enum(Status, self._client_status[None])
@status.setter
def status(self, value):
# internal use only
self._client_status[None] = str(value)
@property
def mobile_status(self):
""":class:`Status`: The member's status on a mobile device, if applicable."""
return try_enum(Status, self._client_status.get("mobile", "offline"))
@property
def desktop_status(self):
""":class:`Status`: The member's status on the desktop client, if applicable."""
return try_enum(Status, self._client_status.get("desktop", "offline"))
@property
def web_status(self):
""":class:`Status`: The member's status on the web client, if applicable."""
return try_enum(Status, self._client_status.get("web", "offline"))
def is_on_mobile(self):
""":class:`bool`: A helper function that determines if a member is active on a mobile device."""
return "mobile" in self._client_status
@property
def colour(self):
"""A property that returns a :class:`Colour` denoting the rendered colour
for the member. If the default colour is the one rendered then an instance
of :meth:`Colour.default` is returned.
There is an alias for this under ``color``.
"""
roles = self.roles[1:] # remove @everyone
# highest order of the colour is the one that gets rendered.
# if the highest is the default colour then the next one with a colour
# is chosen instead
for role in reversed(roles):
if role.colour.value:
return role.colour
return Colour.default()
color = colour
@property
def roles(self):
"""A :class:`list` of :class:`Role` that the member belongs to. Note
that the first element of this list is always the default '@everyone'
role.
These roles are sorted by their position in the role hierarchy.
"""
result = []
g = self.guild
for role_id in self._roles:
role = g.get_role(role_id)
if role:
result.append(role)
result.append(g.default_role)
result.sort()
return result
@property
def mention(self):
"""Returns a string that mentions the member."""
if self.nick:
return "<@!%s>" % self.id
return "<@%s>" % self.id
@property
def display_name(self):
"""Returns the user's display name.
For regular users this is just their username, but
if they have a guild specific nickname then that
is returned instead.
"""
return self.nick if self.nick is not None else self.name
@property
def activity(self):
"""Returns a class Union[:class:`Game`, :class:`Streaming`, :class:`Spotify`, :class:`Activity`] for the primary
activity the user is currently doing. Could be None if no activity is being done.
.. note::
A user may have multiple activities, these can be accessed under :attr:`activities`.
"""
if self.activities:
return self.activities[0]
def mentioned_in(self, message):
"""Checks if the member is mentioned in the specified message.
Parameters
-----------
message: :class:`Message`
The message to check if you're mentioned in.
"""
if self._user.mentioned_in(message):
return True
for role in message.role_mentions:
has_role = utils.get(self.roles, id=role.id) is not None
if has_role:
return True
return False
def permissions_in(self, channel):
"""An alias for :meth:`abc.GuildChannel.permissions_for`.
Basically equivalent to:
.. code-block:: python3
channel.permissions_for(self)
Parameters
-----------
channel
The channel to check your permissions for.
"""
return channel.permissions_for(self)
@property
def top_role(self):
"""Returns the member's highest role.
This is useful for figuring where a member stands in the role
hierarchy chain.
"""
return self.roles[-1]
@property
def guild_permissions(self):
"""Returns the member's guild permissions.
This only takes into consideration the guild permissions
and not most of the implied permissions or any of the
channel permission overwrites. For 100% accurate permission
calculation, please use either :meth:`permissions_in` or
:meth:`abc.GuildChannel.permissions_for`.
This does take into consideration guild ownership and the
administrator implication.
"""
if self.guild.owner == self:
return Permissions.all()
base = Permissions.none()
for r in self.roles:
base.value |= r.permissions.value
if base.administrator:
return Permissions.all()
return base
@property
def voice(self):
"""Optional[:class:`VoiceState`]: Returns the member's current voice state."""
return self.guild._voice_state_for(self._user.id)
async def ban(self, **kwargs):
"""|coro|
Bans this member. Equivalent to :meth:`Guild.ban`
"""
await self.guild.ban(self, **kwargs)
async def unban(self, *, reason=None):
"""|coro|
Unbans this member. Equivalent to :meth:`Guild.unban`
"""
await self.guild.unban(self, reason=reason)
async def kick(self, *, reason=None):
"""|coro|
Kicks this member. Equivalent to :meth:`Guild.kick`
"""
await self.guild.kick(self, reason=reason)
async def edit(self, *, reason=None, **fields):
"""|coro|
Edits the member's data.
Depending on the parameter passed, this requires different permissions listed below:
+---------------+--------------------------------------+
| Parameter | Permission |
+---------------+--------------------------------------+
| nick | :attr:`Permissions.manage_nicknames` |
+---------------+--------------------------------------+
| mute | :attr:`Permissions.mute_members` |
+---------------+--------------------------------------+
| deafen | :attr:`Permissions.deafen_members` |
+---------------+--------------------------------------+
| roles | :attr:`Permissions.manage_roles` |
+---------------+--------------------------------------+
| voice_channel | :attr:`Permissions.move_members` |
+---------------+--------------------------------------+
All parameters are optional.
Parameters
-----------
nick: str
The member's new nickname. Use ``None`` to remove the nickname.
mute: bool
Indicates if the member should be guild muted or un-muted.
deafen: bool
Indicates if the member should be guild deafened or un-deafened.
roles: List[:class:`Roles`]
The member's new list of roles. This *replaces* the roles.
voice_channel: :class:`VoiceChannel`
The voice channel to move the member to.
reason: Optional[str]
The reason for editing this member. Shows up on the audit log.
Raises
-------
Forbidden
You do not have the proper permissions to the action requested.
HTTPException
The operation failed.
"""
http = self._state.http
guild_id = self.guild.id
payload = {}
try:
nick = fields["nick"]
except KeyError:
# nick not present so...
pass
else:
nick = nick if nick else ""
if self._state.self_id == self.id:
await http.change_my_nickname(guild_id, nick, reason=reason)
else:
payload["nick"] = nick
deafen = fields.get("deafen")
if deafen is not None:
payload["deaf"] = deafen
mute = fields.get("mute")
if mute is not None:
payload["mute"] = mute
try:
vc = fields["voice_channel"]
except KeyError:
pass
else:
payload["channel_id"] = vc.id
try:
roles = fields["roles"]
except KeyError:
pass
else:
payload["roles"] = tuple(r.id for r in roles)
await http.edit_member(guild_id, self.id, reason=reason, **payload)
# TODO: wait for WS event for modify-in-place behaviour
async def move_to(self, channel, *, reason=None):
"""|coro|
Moves a member to a new voice channel (they must be connected first).
You must have the :attr:`~Permissions.move_members` permission to
use this.
This raises the same exceptions as :meth:`edit`.
Parameters
-----------
channel: :class:`VoiceChannel`
The new voice channel to move the member to.
reason: Optional[str]
The reason for doing this action. Shows up on the audit log.
"""
await self.edit(voice_channel=channel, reason=reason)
async def add_roles(self, *roles, reason=None, atomic=True):
r"""|coro|
Gives the member a number of :class:`Role`\s.
You must have the :attr:`~Permissions.manage_roles` permission to
use this.
Parameters
-----------
\*roles
An argument list of :class:`abc.Snowflake` representing a :class:`Role`
to give to the member.
reason: Optional[str]
The reason for adding these roles. Shows up on the audit log.
atomic: bool
Whether to atomically add roles. This will ensure that multiple
operations will always be applied regardless of the current
state of the cache.
Raises
-------
Forbidden
You do not have permissions to add these roles.
HTTPException
Adding roles failed.
"""
if not atomic:
new_roles = utils._unique(Object(id=r.id) for s in (self.roles[1:], roles) for r in s)
await self.edit(roles=new_roles, reason=reason)
else:
req = self._state.http.add_role
guild_id = self.guild.id
user_id = self.id
for role in roles:
await req(guild_id, user_id, role.id, reason=reason)
async def remove_roles(self, *roles, reason=None, atomic=True):
r"""|coro|
Removes :class:`Role`\s from this member.
You must have the :attr:`~Permissions.manage_roles` permission to
use this.
Parameters
-----------
\*roles
An argument list of :class:`abc.Snowflake` representing a :class:`Role`
to remove from the member.
reason: Optional[str]
The reason for removing these roles. Shows up on the audit log.
atomic: bool
Whether to atomically remove roles. This will ensure that multiple
operations will always be applied regardless of the current
state of the cache.
Raises
-------
Forbidden
You do not have permissions to remove these roles.
HTTPException
Removing the roles failed.
"""
if not atomic:
new_roles = [Object(id=r.id) for r in self.roles[1:]] # remove @everyone
for role in roles:
try:
new_roles.remove(Object(id=role.id))
except ValueError:
pass
await self.edit(roles=new_roles, reason=reason)
else:
req = self._state.http.remove_role
guild_id = self.guild.id
user_id = self.id
for role in roles:
await req(guild_id, user_id, role.id, reason=reason)

View File

@@ -1,799 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import asyncio
import re
from . import utils
from .reaction import Reaction
from .emoji import Emoji, PartialEmoji
from .calls import CallMessage
from .enums import MessageType, try_enum
from .errors import InvalidArgument, ClientException, HTTPException
from .embeds import Embed
class Attachment:
"""Represents an attachment from Discord.
Attributes
------------
id: :class:`int`
The attachment ID.
size: :class:`int`
The attachment size in bytes.
height: Optional[:class:`int`]
The attachment's height, in pixels. Only applicable to images.
width: Optional[:class:`int`]
The attachment's width, in pixels. Only applicable to images.
filename: :class:`str`
The attachment's filename.
url: :class:`str`
The attachment URL. If the message this attachment was attached
to is deleted, then this will 404.
proxy_url: :class:`str`
The proxy URL. This is a cached version of the :attr:`~Attachment.url` in the
case of images. When the message is deleted, this URL might be valid for a few
minutes or not valid at all.
"""
__slots__ = ("id", "size", "height", "width", "filename", "url", "proxy_url", "_http")
def __init__(self, *, data, state):
self.id = int(data["id"])
self.size = data["size"]
self.height = data.get("height")
self.width = data.get("width")
self.filename = data["filename"]
self.url = data.get("url")
self.proxy_url = data.get("proxy_url")
self._http = state.http
def is_spoiler(self):
""":class:`bool`: Whether this attachment contains a spoiler."""
return self.filename.startswith("SPOILER_")
async def save(self, fp, *, seek_begin=True):
"""|coro|
Saves this attachment into a file-like object.
Parameters
-----------
fp: Union[BinaryIO, str]
The file-like object to save this attachment to or the filename
to use. If a filename is passed then a file is created with that
filename and used instead.
seek_begin: bool
Whether to seek to the beginning of the file after saving is
successfully done.
Raises
--------
HTTPException
Saving the attachment failed.
NotFound
The attachment was deleted.
Returns
--------
int
The number of bytes written.
"""
data = await self._http.get_attachment(self.url)
if isinstance(fp, str):
with open(fp, "wb") as f:
return f.write(data)
else:
written = fp.write(data)
if seek_begin:
fp.seek(0)
return written
class Message:
r"""Represents a message from Discord.
There should be no need to create one of these manually.
Attributes
-----------
tts: :class:`bool`
Specifies if the message was done with text-to-speech.
type: :class:`MessageType`
The type of message. In most cases this should not be checked, but it is helpful
in cases where it might be a system message for :attr:`system_content`.
author
A :class:`Member` that sent the message. If :attr:`channel` is a
private channel or the user has the left the guild, then it is a :class:`User` instead.
content: :class:`str`
The actual contents of the message.
nonce
The value used by the discord guild and the client to verify that the message is successfully sent.
This is typically non-important.
embeds: List[:class:`Embed`]
A list of embeds the message has.
channel
The :class:`TextChannel` that the message was sent from.
Could be a :class:`DMChannel` or :class:`GroupChannel` if it's a private message.
call: Optional[:class:`CallMessage`]
The call that the message refers to. This is only applicable to messages of type
:attr:`MessageType.call`.
mention_everyone: :class:`bool`
Specifies if the message mentions everyone.
.. note::
This does not check if the ``@everyone`` or the ``@here`` text is in the message itself.
Rather this boolean indicates if either the ``@everyone`` or the ``@here`` text is in the message
**and** it did end up mentioning.
mentions: :class:`list`
A list of :class:`Member` that were mentioned. If the message is in a private message
then the list will be of :class:`User` instead. For messages that are not of type
:attr:`MessageType.default`\, this array can be used to aid in system messages.
For more information, see :attr:`system_content`.
.. warning::
The order of the mentions list is not in any particular order so you should
not rely on it. This is a discord limitation, not one with the library.
channel_mentions: :class:`list`
A list of :class:`abc.GuildChannel` that were mentioned. If the message is in a private message
then the list is always empty.
role_mentions: :class:`list`
A list of :class:`Role` that were mentioned. If the message is in a private message
then the list is always empty.
id: :class:`int`
The message ID.
webhook_id: Optional[:class:`int`]
If this message was sent by a webhook, then this is the webhook ID's that sent this
message.
attachments: List[:class:`Attachment`]
A list of attachments given to a message.
pinned: :class:`bool`
Specifies if the message is currently pinned.
reactions : List[:class:`Reaction`]
Reactions to a message. Reactions can be either custom emoji or standard unicode emoji.
activity: Optional[:class:`dict`]
The activity associated with this message. Sent with Rich-Presence related messages that for
example, request joining, spectating, or listening to or with another member.
It is a dictionary with the following optional keys:
- ``type``: An integer denoting the type of message activity being requested.
- ``party_id``: The party ID associated with the party.
application: Optional[:class:`dict`]
The rich presence enabled application associated with this message.
It is a dictionary with the following keys:
- ``id``: A string representing the application's ID.
- ``name``: A string representing the application's name.
- ``description``: A string representing the application's description.
- ``icon``: A string representing the icon ID of the application.
- ``cover_image``: A string representing the embed's image asset ID.
"""
__slots__ = (
"_edited_timestamp",
"tts",
"content",
"channel",
"webhook_id",
"mention_everyone",
"embeds",
"id",
"mentions",
"author",
"_cs_channel_mentions",
"_cs_raw_mentions",
"attachments",
"_cs_clean_content",
"_cs_raw_channel_mentions",
"nonce",
"pinned",
"role_mentions",
"_cs_raw_role_mentions",
"type",
"call",
"_cs_system_content",
"_cs_guild",
"_state",
"reactions",
"application",
"activity",
)
def __init__(self, *, state, channel, data):
self._state = state
self.id = int(data["id"])
self.webhook_id = utils._get_as_snowflake(data, "webhook_id")
self.reactions = [Reaction(message=self, data=d) for d in data.get("reactions", [])]
self.application = data.get("application")
self.activity = data.get("activity")
self._update(channel, data)
def __repr__(self):
return "<Message id={0.id} pinned={0.pinned} author={0.author!r}>".format(self)
def _try_patch(self, data, key, transform=None):
try:
value = data[key]
except KeyError:
pass
else:
if transform is None:
setattr(self, key, value)
else:
setattr(self, key, transform(value))
def _add_reaction(self, data, emoji, user_id):
reaction = utils.find(lambda r: r.emoji == emoji, self.reactions)
is_me = data["me"] = user_id == self._state.self_id
if reaction is None:
reaction = Reaction(message=self, data=data, emoji=emoji)
self.reactions.append(reaction)
else:
reaction.count += 1
if is_me:
reaction.me = is_me
return reaction
def _remove_reaction(self, data, emoji, user_id):
reaction = utils.find(lambda r: r.emoji == emoji, self.reactions)
if reaction is None:
# already removed?
raise ValueError("Emoji already removed?")
# if reaction isn't in the list, we crash. This means discord
# sent bad data, or we stored improperly
reaction.count -= 1
if user_id == self._state.self_id:
reaction.me = False
if reaction.count == 0:
# this raises ValueError if something went wrong as well.
self.reactions.remove(reaction)
return reaction
def _update(self, channel, data):
self.channel = channel
self._edited_timestamp = utils.parse_time(data.get("edited_timestamp"))
self._try_patch(data, "pinned")
self._try_patch(data, "application")
self._try_patch(data, "activity")
self._try_patch(data, "mention_everyone")
self._try_patch(data, "tts")
self._try_patch(data, "type", lambda x: try_enum(MessageType, x))
self._try_patch(data, "content")
self._try_patch(
data, "attachments", lambda x: [Attachment(data=a, state=self._state) for a in x]
)
self._try_patch(data, "embeds", lambda x: list(map(Embed.from_data, x)))
self._try_patch(data, "nonce")
for handler in ("author", "mentions", "mention_roles", "call"):
try:
getattr(self, "_handle_%s" % handler)(data[handler])
except KeyError:
continue
# clear the cached properties
cached = filter(lambda attr: attr.startswith("_cs_"), self.__slots__)
for attr in cached:
try:
delattr(self, attr)
except AttributeError:
pass
def _handle_author(self, author):
self.author = self._state.store_user(author)
if self.guild is not None:
found = self.guild.get_member(self.author.id)
if found is not None:
self.author = found
def _handle_mentions(self, mentions):
self.mentions = []
if self.guild is None:
self.mentions = [self._state.store_user(m) for m in mentions]
return
for mention in filter(None, mentions):
id_search = int(mention["id"])
member = self.guild.get_member(id_search)
if member is not None:
self.mentions.append(member)
def _handle_mention_roles(self, role_mentions):
self.role_mentions = []
if self.guild is not None:
for role_id in map(int, role_mentions):
role = self.guild.get_role(role_id)
if role is not None:
self.role_mentions.append(role)
def _handle_call(self, call):
if call is None or self.type is not MessageType.call:
self.call = None
return
# we get the participant source from the mentions array or
# the author
participants = []
for uid in map(int, call.get("participants", [])):
if uid == self.author.id:
participants.append(self.author)
else:
user = utils.find(lambda u: u.id == uid, self.mentions)
if user is not None:
participants.append(user)
call["participants"] = participants
self.call = CallMessage(message=self, **call)
@utils.cached_slot_property("_cs_guild")
def guild(self):
"""Optional[:class:`Guild`]: The guild that the message belongs to, if applicable."""
return getattr(self.channel, "guild", None)
@utils.cached_slot_property("_cs_raw_mentions")
def raw_mentions(self):
"""A property that returns an array of user IDs matched with
the syntax of <@user_id> in the message content.
This allows you to receive the user IDs of mentioned users
even in a private message context.
"""
return [int(x) for x in re.findall(r"<@!?([0-9]+)>", self.content)]
@utils.cached_slot_property("_cs_raw_channel_mentions")
def raw_channel_mentions(self):
"""A property that returns an array of channel IDs matched with
the syntax of <#channel_id> in the message content.
"""
return [int(x) for x in re.findall(r"<#([0-9]+)>", self.content)]
@utils.cached_slot_property("_cs_raw_role_mentions")
def raw_role_mentions(self):
"""A property that returns an array of role IDs matched with
the syntax of <@&role_id> in the message content.
"""
return [int(x) for x in re.findall(r"<@&([0-9]+)>", self.content)]
@utils.cached_slot_property("_cs_channel_mentions")
def channel_mentions(self):
if self.guild is None:
return []
it = filter(None, map(self.guild.get_channel, self.raw_channel_mentions))
return utils._unique(it)
@utils.cached_slot_property("_cs_clean_content")
def clean_content(self):
"""A property that returns the content in a "cleaned up"
manner. This basically means that mentions are transformed
into the way the client shows it. e.g. ``<#id>`` will transform
into ``#name``.
This will also transform @everyone and @here mentions into
non-mentions.
"""
transformations = {
re.escape("<#%s>" % channel.id): "#" + channel.name
for channel in self.channel_mentions
}
mention_transforms = {
re.escape("<@%s>" % member.id): "@" + member.display_name for member in self.mentions
}
# add the <@!user_id> cases as well..
second_mention_transforms = {
re.escape("<@!%s>" % member.id): "@" + member.display_name for member in self.mentions
}
transformations.update(mention_transforms)
transformations.update(second_mention_transforms)
if self.guild is not None:
role_transforms = {
re.escape("<@&%s>" % role.id): "@" + role.name for role in self.role_mentions
}
transformations.update(role_transforms)
def repl(obj):
return transformations.get(re.escape(obj.group(0)), "")
pattern = re.compile("|".join(transformations.keys()))
result = pattern.sub(repl, self.content)
transformations = {"@everyone": "@\u200beveryone", "@here": "@\u200bhere"}
def repl2(obj):
return transformations.get(obj.group(0), "")
pattern = re.compile("|".join(transformations.keys()))
return pattern.sub(repl2, result)
@property
def created_at(self):
"""datetime.datetime: The message's creation time in UTC."""
return utils.snowflake_time(self.id)
@property
def edited_at(self):
"""Optional[datetime.datetime]: A naive UTC datetime object containing the edited time of the message."""
return self._edited_timestamp
@property
def jump_url(self):
""":class:`str`: Returns a URL that allows the client to jump to this message."""
guild_id = getattr(self.guild, "id", "@me")
return "https://discordapp.com/channels/{0}/{1.channel.id}/{1.id}".format(guild_id, self)
@utils.cached_slot_property("_cs_system_content")
def system_content(self):
r"""A property that returns the content that is rendered
regardless of the :attr:`Message.type`.
In the case of :attr:`MessageType.default`\, this just returns the
regular :attr:`Message.content`. Otherwise this returns an English
message denoting the contents of the system message.
"""
if self.type is MessageType.default:
return self.content
if self.type is MessageType.pins_add:
return "{0.name} pinned a message to this channel.".format(self.author)
if self.type is MessageType.recipient_add:
return "{0.name} added {1.name} to the group.".format(self.author, self.mentions[0])
if self.type is MessageType.recipient_remove:
return "{0.name} removed {1.name} from the group.".format(
self.author, self.mentions[0]
)
if self.type is MessageType.channel_name_change:
return "{0.author.name} changed the channel name: {0.content}".format(self)
if self.type is MessageType.channel_icon_change:
return "{0.author.name} changed the channel icon.".format(self)
if self.type is MessageType.new_member:
formats = [
"{0} just joined the server - glhf!",
"{0} just joined. Everyone, look busy!",
"{0} just joined. Can I get a heal?",
"{0} joined your party.",
"{0} joined. You must construct additional pylons.",
"Ermagherd. {0} is here.",
"Welcome, {0}. Stay awhile and listen.",
"Welcome, {0}. We were expecting you ( ͡° ͜ʖ ͡°)",
"Welcome, {0}. We hope you brought pizza.",
"Welcome {0}. Leave your weapons by the door.",
"A wild {0} appeared.",
"Swoooosh. {0} just landed.",
"Brace yourselves. {0} just joined the server.",
"{0} just joined. Hide your bananas.",
"{0} just arrived. Seems OP - please nerf.",
"{0} just slid into the server.",
"A {0} has spawned in the server.",
"Big {0} showed up!",
"Wheres {0}? In the server!",
"{0} hopped into the server. Kangaroo!!",
"{0} just showed up. Hold my beer.",
"Challenger approaching - {0} has appeared!",
"It's a bird! It's a plane! Nevermind, it's just {0}.",
"It's {0}! Praise the sun! [T]/",
"Never gonna give {0} up. Never gonna let {0} down.",
"Ha! {0} has joined! You activated my trap card!",
"Cheers, love! {0}'s here!",
"Hey! Listen! {0} has joined!",
"We've been expecting you {0}",
"It's dangerous to go alone, take {0}!",
"{0} has joined the server! It's super effective!",
"Cheers, love! {0} is here!",
"{0} is here, as the prophecy foretold.",
"{0} has arrived. Party's over.",
"Ready player {0}",
"{0} is here to kick butt and chew bubblegum. And {0} is all out of gum.",
"Hello. Is it {0} you're looking for?",
"{0} has joined. Stay a while and listen!",
"Roses are red, violets are blue, {0} joined this server with you",
]
index = int(self.created_at.timestamp()) % len(formats)
return formats[index].format(self.author.name)
if self.type is MessageType.call:
# we're at the call message type now, which is a bit more complicated.
# we can make the assumption that Message.channel is a PrivateChannel
# with the type ChannelType.group or ChannelType.private
call_ended = self.call.ended_timestamp is not None
if self.channel.me in self.call.participants:
return "{0.author.name} started a call.".format(self)
elif call_ended:
return "You missed a call from {0.author.name}".format(self)
else:
return "{0.author.name} started a call \N{EM DASH} Join the call.".format(self)
async def delete(self):
"""|coro|
Deletes the message.
Your own messages could be deleted without any proper permissions. However to
delete other people's messages, you need the :attr:`~Permissions.manage_messages`
permission.
Raises
------
Forbidden
You do not have proper permissions to delete the message.
HTTPException
Deleting the message failed.
"""
await self._state.http.delete_message(self.channel.id, self.id)
async def edit(self, **fields):
"""|coro|
Edits the message.
The content must be able to be transformed into a string via ``str(content)``.
Parameters
-----------
content: Optional[str]
The new content to replace the message with.
Could be ``None`` to remove the content.
embed: Optional[:class:`Embed`]
The new embed to replace the original with.
Could be ``None`` to remove the embed.
delete_after: Optional[float]
If provided, the number of seconds to wait in the background
before deleting the message we just edited. If the deletion fails,
then it is silently ignored.
Raises
-------
HTTPException
Editing the message failed.
"""
try:
content = fields["content"]
except KeyError:
pass
else:
if content is not None:
fields["content"] = str(content)
try:
embed = fields["embed"]
except KeyError:
pass
else:
if embed is not None:
fields["embed"] = embed.to_dict()
data = await self._state.http.edit_message(self.id, self.channel.id, **fields)
self._update(channel=self.channel, data=data)
try:
delete_after = fields["delete_after"]
except KeyError:
pass
else:
if delete_after is not None:
async def delete():
await asyncio.sleep(delete_after, loop=self._state.loop)
try:
await self._state.http.delete_message(self.channel.id, self.id)
except HTTPException:
pass
asyncio.ensure_future(delete(), loop=self._state.loop)
async def pin(self):
"""|coro|
Pins the message.
You must have the :attr:`~Permissions.manage_messages` permission to do
this in a non-private channel context.
Raises
-------
Forbidden
You do not have permissions to pin the message.
NotFound
The message or channel was not found or deleted.
HTTPException
Pinning the message failed, probably due to the channel
having more than 50 pinned messages.
"""
await self._state.http.pin_message(self.channel.id, self.id)
self.pinned = True
async def unpin(self):
"""|coro|
Unpins the message.
You must have the :attr:`~Permissions.manage_messages` permission to do
this in a non-private channel context.
Raises
-------
Forbidden
You do not have permissions to unpin the message.
NotFound
The message or channel was not found or deleted.
HTTPException
Unpinning the message failed.
"""
await self._state.http.unpin_message(self.channel.id, self.id)
self.pinned = False
async def add_reaction(self, emoji):
"""|coro|
Add a reaction to the message.
The emoji may be a unicode emoji or a custom guild :class:`Emoji`.
You must have the :attr:`~Permissions.read_message_history` permission
to use this. If nobody else has reacted to the message using this
emoji, the :attr:`~Permissions.add_reactions` permission is required.
Parameters
------------
emoji: Union[:class:`Emoji`, :class:`Reaction`, :class:`PartialEmoji`, str]
The emoji to react with.
Raises
--------
HTTPException
Adding the reaction failed.
Forbidden
You do not have the proper permissions to react to the message.
NotFound
The emoji you specified was not found.
InvalidArgument
The emoji parameter is invalid.
"""
emoji = self._emoji_reaction(emoji)
await self._state.http.add_reaction(self.id, self.channel.id, emoji)
async def remove_reaction(self, emoji, member):
"""|coro|
Remove a reaction by the member from the message.
The emoji may be a unicode emoji or a custom guild :class:`Emoji`.
If the reaction is not your own (i.e. ``member`` parameter is not you) then
the :attr:`~Permissions.manage_messages` permission is needed.
The ``member`` parameter must represent a member and meet
the :class:`abc.Snowflake` abc.
Parameters
------------
emoji: Union[:class:`Emoji`, :class:`Reaction`, :class:`PartialEmoji`, str]
The emoji to remove.
member: :class:`abc.Snowflake`
The member for which to remove the reaction.
Raises
--------
HTTPException
Removing the reaction failed.
Forbidden
You do not have the proper permissions to remove the reaction.
NotFound
The member or emoji you specified was not found.
InvalidArgument
The emoji parameter is invalid.
"""
emoji = self._emoji_reaction(emoji)
if member.id == self._state.self_id:
await self._state.http.remove_own_reaction(self.id, self.channel.id, emoji)
else:
await self._state.http.remove_reaction(self.id, self.channel.id, emoji, member.id)
@staticmethod
def _emoji_reaction(emoji):
if isinstance(emoji, Reaction):
emoji = emoji.emoji
if isinstance(emoji, Emoji):
return "%s:%s" % (emoji.name, emoji.id)
if isinstance(emoji, PartialEmoji):
return emoji._as_reaction()
if isinstance(emoji, str):
return emoji # this is okay
raise InvalidArgument(
"emoji argument must be str, Emoji, or Reaction not {.__class__.__name__}.".format(
emoji
)
)
async def clear_reactions(self):
"""|coro|
Removes all the reactions from the message.
You need the :attr:`~Permissions.manage_messages` permission to use this.
Raises
--------
HTTPException
Removing the reactions failed.
Forbidden
You do not have the proper permissions to remove all the reactions.
"""
await self._state.http.clear_reactions(self.id, self.channel.id)
def ack(self):
"""|coro|
Marks this message as read.
The user must not be a bot user.
Raises
-------
HTTPException
Acking failed.
ClientException
You must not be a bot user.
"""
state = self._state
if state.is_bot:
raise ClientException("Must not be a bot account to ack messages.")
return state.http.ack_message(self.channel.id, self.id)

View File

@@ -1,44 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
class EqualityComparable:
__slots__ = ()
def __eq__(self, other):
return isinstance(other, self.__class__) and other.id == self.id
def __ne__(self, other):
if isinstance(other, self.__class__):
return other.id != self.id
return True
class Hashable(EqualityComparable):
__slots__ = ()
def __hash__(self):
return self.id >> 22

View File

@@ -1,71 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from . import utils
from .mixins import Hashable
class Object(Hashable):
"""Represents a generic Discord object.
The purpose of this class is to allow you to create 'miniature'
versions of data classes if you want to pass in just an ID. Most functions
that take in a specific data class with an ID can also take in this class
as a substitute instead. Note that even though this is the case, not all
objects (if any) actually inherit from this class.
There are also some cases where some websocket events are received
in :issue:`strange order <21>` and when such events happened you would
receive this class rather than the actual data class. These cases are
extremely rare.
.. container:: operations
.. describe:: x == y
Checks if two objects are equal.
.. describe:: x != y
Checks if two objects are not equal.
.. describe:: hash(x)
Returns the object's hash.
Attributes
-----------
id : :class:`str`
The ID of the object.
"""
def __init__(self, id):
self.id = id
@property
def created_at(self):
"""Returns the snowflake's creation time in UTC."""
return utils.snowflake_time(self.id)

View File

@@ -1,286 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import array
import ctypes
import ctypes.util
import logging
import os.path
import sys
from .errors import DiscordException
log = logging.getLogger(__name__)
c_int_ptr = ctypes.POINTER(ctypes.c_int)
c_int16_ptr = ctypes.POINTER(ctypes.c_int16)
c_float_ptr = ctypes.POINTER(ctypes.c_float)
class EncoderStruct(ctypes.Structure):
pass
EncoderStructPtr = ctypes.POINTER(EncoderStruct)
def _err_lt(result, func, args):
if result < 0:
log.info("error has happened in %s", func.__name__)
raise OpusError(result)
return result
def _err_ne(result, func, args):
ret = args[-1]._obj
if ret.value != 0:
log.info("error has happened in %s", func.__name__)
raise OpusError(ret.value)
return result
# A list of exported functions.
# The first argument is obviously the name.
# The second one are the types of arguments it takes.
# The third is the result type.
# The fourth is the error handler.
exported_functions = [
("opus_strerror", [ctypes.c_int], ctypes.c_char_p, None),
("opus_encoder_get_size", [ctypes.c_int], ctypes.c_int, None),
(
"opus_encoder_create",
[ctypes.c_int, ctypes.c_int, ctypes.c_int, c_int_ptr],
EncoderStructPtr,
_err_ne,
),
(
"opus_encode",
[EncoderStructPtr, c_int16_ptr, ctypes.c_int, ctypes.c_char_p, ctypes.c_int32],
ctypes.c_int32,
_err_lt,
),
("opus_encoder_ctl", None, ctypes.c_int32, _err_lt),
("opus_encoder_destroy", [EncoderStructPtr], None, None),
]
def libopus_loader(name):
# create the library...
lib = ctypes.cdll.LoadLibrary(name)
# register the functions...
for item in exported_functions:
func = getattr(lib, item[0])
try:
if item[1]:
func.argtypes = item[1]
func.restype = item[2]
except KeyError:
pass
try:
if item[3]:
func.errcheck = item[3]
except KeyError:
log.exception("Error assigning check function to %s", func)
return lib
try:
if sys.platform == "win32":
_basedir = os.path.dirname(os.path.abspath(__file__))
_bitness = "x64" if sys.maxsize > 2 ** 32 else "x86"
_filename = os.path.join(_basedir, "bin", "libopus-0.{}.dll".format(_bitness))
_lib = libopus_loader(_filename)
else:
_lib = libopus_loader(ctypes.util.find_library("opus"))
except Exception:
_lib = None
def load_opus(name):
"""Loads the libopus shared library for use with voice.
If this function is not called then the library uses the function
`ctypes.util.find_library`__ and then loads that one
if available.
.. _find library: https://docs.python.org/3.5/library/ctypes.html#finding-shared-libraries
__ `find library`_
Not loading a library leads to voice not working.
This function propagates the exceptions thrown.
Warning
--------
The bitness of the library must match the bitness of your python
interpreter. If the library is 64-bit then your python interpreter
must be 64-bit as well. Usually if there's a mismatch in bitness then
the load will throw an exception.
Note
----
On Windows, the .dll extension is not necessary. However, on Linux
the full extension is required to load the library, e.g. ``libopus.so.1``.
On Linux however, `find library`_ will usually find the library automatically
without you having to call this.
Parameters
----------
name: str
The filename of the shared library.
"""
global _lib
_lib = libopus_loader(name)
def is_loaded():
"""Function to check if opus lib is successfully loaded either
via the ``ctypes.util.find_library`` call of :func:`load_opus`.
This must return ``True`` for voice to work.
Returns
-------
bool
Indicates if the opus library has been loaded.
"""
global _lib
return _lib is not None
class OpusError(DiscordException):
"""An exception that is thrown for libopus related errors.
Attributes
----------
code : :class:`int`
The error code returned.
"""
def __init__(self, code):
self.code = code
msg = _lib.opus_strerror(self.code).decode("utf-8")
log.info('"%s" has happened', msg)
super().__init__(msg)
class OpusNotLoaded(DiscordException):
"""An exception that is thrown for when libopus is not loaded."""
pass
# Some constants...
OK = 0
APPLICATION_AUDIO = 2049
APPLICATION_VOIP = 2048
APPLICATION_LOWDELAY = 2051
CTL_SET_BITRATE = 4002
CTL_SET_BANDWIDTH = 4008
CTL_SET_FEC = 4012
CTL_SET_PLP = 4014
CTL_SET_SIGNAL = 4024
band_ctl = {"narrow": 1101, "medium": 1102, "wide": 1103, "superwide": 1104, "full": 1105}
signal_ctl = {"auto": -1000, "voice": 3001, "music": 3002}
class Encoder:
SAMPLING_RATE = 48000
CHANNELS = 2
FRAME_LENGTH = 20
SAMPLE_SIZE = 4 # (bit_rate / 8) * CHANNELS (bit_rate == 16)
SAMPLES_PER_FRAME = int(SAMPLING_RATE / 1000 * FRAME_LENGTH)
FRAME_SIZE = SAMPLES_PER_FRAME * SAMPLE_SIZE
def __init__(self, application=APPLICATION_AUDIO):
self.application = application
if not is_loaded():
raise OpusNotLoaded()
self._state = self._create_state()
self.set_bitrate(128)
self.set_fec(True)
self.set_expected_packet_loss_percent(0.15)
self.set_bandwidth("full")
self.set_signal_type("auto")
def __del__(self):
if hasattr(self, "_state"):
_lib.opus_encoder_destroy(self._state)
self._state = None
def _create_state(self):
ret = ctypes.c_int()
return _lib.opus_encoder_create(
self.SAMPLING_RATE, self.CHANNELS, self.application, ctypes.byref(ret)
)
def set_bitrate(self, kbps):
kbps = min(128, max(16, int(kbps)))
_lib.opus_encoder_ctl(self._state, CTL_SET_BITRATE, kbps * 1024)
return kbps
def set_bandwidth(self, req):
if req not in band_ctl:
raise KeyError(
"%r is not a valid bandwidth setting. Try one of: %s" % (req, ",".join(band_ctl))
)
k = band_ctl[req]
_lib.opus_encoder_ctl(self._state, CTL_SET_BANDWIDTH, k)
def set_signal_type(self, req):
if req not in signal_ctl:
raise KeyError(
"%r is not a valid signal setting. Try one of: %s" % (req, ",".join(signal_ctl))
)
k = signal_ctl[req]
_lib.opus_encoder_ctl(self._state, CTL_SET_SIGNAL, k)
def set_fec(self, enabled=True):
_lib.opus_encoder_ctl(self._state, CTL_SET_FEC, 1 if enabled else 0)
def set_expected_packet_loss_percent(self, percentage):
_lib.opus_encoder_ctl(self._state, CTL_SET_PLP, min(100, max(0, int(percentage * 100))))
def encode(self, pcm, frame_size):
max_data_bytes = len(pcm)
pcm = ctypes.cast(pcm, c_int16_ptr)
data = (ctypes.c_char * max_data_bytes)()
ret = _lib.opus_encode(self._state, pcm, frame_size, data, max_data_bytes)
return array.array("b", data[:ret]).tobytes()

View File

@@ -1,636 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
class Permissions:
"""Wraps up the Discord permission value.
The properties provided are two way. You can set and retrieve individual
bits using the properties as if they were regular bools. This allows
you to edit permissions.
.. container:: operations
.. describe:: x == y
Checks if two permissions are equal.
.. describe:: x != y
Checks if two permissions are not equal.
.. describe:: x <= y
Checks if a permission is a subset of another permission.
.. describe:: x >= y
Checks if a permission is a superset of another permission.
.. describe:: x < y
Checks if a permission is a strict subset of another permission.
.. describe:: x > y
Checks if a permission is a strict superset of another permission.
.. describe:: hash(x)
Return the permission's hash.
.. describe:: iter(x)
Returns an iterator of ``(perm, value)`` pairs. This allows it
to be, for example, constructed as a dict or a list of pairs.
Attributes
-----------
value
The raw value. This value is a bit array field of a 53-bit integer
representing the currently available permissions. You should query
permissions via the properties rather than using this raw value.
"""
__slots__ = ("value",)
def __init__(self, permissions=0):
if not isinstance(permissions, int):
raise TypeError(
"Expected int parameter, received %s instead." % permissions.__class__.__name__
)
self.value = permissions
def __eq__(self, other):
return isinstance(other, Permissions) and self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self.value)
def __repr__(self):
return "<Permissions value=%s>" % self.value
def _perm_iterator(self):
for attr in dir(self):
# check if it's a property, because if so it's a permission
is_property = isinstance(getattr(self.__class__, attr), property)
if is_property:
yield (attr, getattr(self, attr))
def __iter__(self):
return self._perm_iterator()
def is_subset(self, other):
"""Returns True if self has the same or fewer permissions as other."""
if isinstance(other, Permissions):
return (self.value & other.value) == self.value
else:
raise TypeError(
"cannot compare {} with {}".format(
self.__class__.__name__, other.__class__.__name__
)
)
def is_superset(self, other):
"""Returns True if self has the same or more permissions as other."""
if isinstance(other, Permissions):
return (self.value | other.value) == self.value
else:
raise TypeError(
"cannot compare {} with {}".format(
self.__class__.__name__, other.__class__.__name__
)
)
def is_strict_subset(self, other):
"""Returns True if the permissions on other are a strict subset of those on self."""
return self.is_subset(other) and self != other
def is_strict_superset(self, other):
"""Returns True if the permissions on other are a strict superset of those on self."""
return self.is_superset(other) and self != other
__le__ = is_subset
__ge__ = is_superset
__lt__ = is_strict_subset
__gt__ = is_strict_superset
@classmethod
def none(cls):
"""A factory method that creates a :class:`Permissions` with all
permissions set to False."""
return cls(0)
@classmethod
def all(cls):
"""A factory method that creates a :class:`Permissions` with all
permissions set to True."""
return cls(0b01111111111101111111110111111111)
@classmethod
def all_channel(cls):
"""A :class:`Permissions` with all channel-specific permissions set to
True and the guild-specific ones set to False. The guild-specific
permissions are currently:
- manage_guild
- kick_members
- ban_members
- administrator
- change_nicknames
- manage_nicknames
"""
return cls(0b00110011111101111111110001010001)
@classmethod
def general(cls):
"""A factory method that creates a :class:`Permissions` with all
"General" permissions from the official Discord UI set to True."""
return cls(0b01111100000000000000000010111111)
@classmethod
def text(cls):
"""A factory method that creates a :class:`Permissions` with all
"Text" permissions from the official Discord UI set to True."""
return cls(0b00000000000001111111110001000000)
@classmethod
def voice(cls):
"""A factory method that creates a :class:`Permissions` with all
"Voice" permissions from the official Discord UI set to True."""
return cls(0b00000011111100000000000100000000)
def update(self, **kwargs):
r"""Bulk updates this permission object.
Allows you to set multiple attributes by using keyword
arguments. The names must be equivalent to the properties
listed. Extraneous key/value pairs will be silently ignored.
Parameters
------------
\*\*kwargs
A list of key/value pairs to bulk update permissions with.
"""
for key, value in kwargs.items():
try:
is_property = isinstance(getattr(self.__class__, key), property)
except AttributeError:
continue
if is_property:
setattr(self, key, value)
def _bit(self, index):
return bool((self.value >> index) & 1)
def _set(self, index, value):
if value is True:
self.value |= 1 << index
elif value is False:
self.value &= ~(1 << index)
else:
raise TypeError("Value to set for Permissions must be a bool.")
def handle_overwrite(self, allow, deny):
# Basically this is what's happening here.
# We have an original bit array, e.g. 1010
# Then we have another bit array that is 'denied', e.g. 1111
# And then we have the last one which is 'allowed', e.g. 0101
# We want original OP denied to end up resulting in
# whatever is in denied to be set to 0.
# So 1010 OP 1111 -> 0000
# Then we take this value and look at the allowed values.
# And whatever is allowed is set to 1.
# So 0000 OP2 0101 -> 0101
# The OP is base & ~denied.
# The OP2 is base | allowed.
self.value = (self.value & ~deny) | allow
@property
def create_instant_invite(self):
"""Returns True if the user can create instant invites."""
return self._bit(0)
@create_instant_invite.setter
def create_instant_invite(self, value):
self._set(0, value)
@property
def kick_members(self):
"""Returns True if the user can kick users from the guild."""
return self._bit(1)
@kick_members.setter
def kick_members(self, value):
self._set(1, value)
@property
def ban_members(self):
"""Returns True if a user can ban users from the guild."""
return self._bit(2)
@ban_members.setter
def ban_members(self, value):
self._set(2, value)
@property
def administrator(self):
"""Returns True if a user is an administrator. This role overrides all other permissions.
This also bypasses all channel-specific overrides.
"""
return self._bit(3)
@administrator.setter
def administrator(self, value):
self._set(3, value)
@property
def manage_channels(self):
"""Returns True if a user can edit, delete, or create channels in the guild.
This also corresponds to the "Manage Channel" channel-specific override."""
return self._bit(4)
@manage_channels.setter
def manage_channels(self, value):
self._set(4, value)
@property
def manage_guild(self):
"""Returns True if a user can edit guild properties."""
return self._bit(5)
@manage_guild.setter
def manage_guild(self, value):
self._set(5, value)
@property
def add_reactions(self):
"""Returns True if a user can add reactions to messages."""
return self._bit(6)
@add_reactions.setter
def add_reactions(self, value):
self._set(6, value)
@property
def view_audit_log(self):
"""Returns True if a user can view the guild's audit log."""
return self._bit(7)
@view_audit_log.setter
def view_audit_log(self, value):
self._set(7, value)
@property
def priority_speaker(self):
"""Returns True if a user can be more easily heard while talking."""
return self._bit(8)
@priority_speaker.setter
def priority_speaker(self, value):
self._set(8, value)
# 1 unused
@property
def read_messages(self):
"""Returns True if a user can read messages from all or specific text channels."""
return self._bit(10)
@read_messages.setter
def read_messages(self, value):
self._set(10, value)
@property
def send_messages(self):
"""Returns True if a user can send messages from all or specific text channels."""
return self._bit(11)
@send_messages.setter
def send_messages(self, value):
self._set(11, value)
@property
def send_tts_messages(self):
"""Returns True if a user can send TTS messages from all or specific text channels."""
return self._bit(12)
@send_tts_messages.setter
def send_tts_messages(self, value):
self._set(12, value)
@property
def manage_messages(self):
"""Returns True if a user can delete or pin messages in a text channel. Note that there are currently no ways to edit other people's messages."""
return self._bit(13)
@manage_messages.setter
def manage_messages(self, value):
self._set(13, value)
@property
def embed_links(self):
"""Returns True if a user's messages will automatically be embedded by Discord."""
return self._bit(14)
@embed_links.setter
def embed_links(self, value):
self._set(14, value)
@property
def attach_files(self):
"""Returns True if a user can send files in their messages."""
return self._bit(15)
@attach_files.setter
def attach_files(self, value):
self._set(15, value)
@property
def read_message_history(self):
"""Returns True if a user can read a text channel's previous messages."""
return self._bit(16)
@read_message_history.setter
def read_message_history(self, value):
self._set(16, value)
@property
def mention_everyone(self):
"""Returns True if a user's @everyone or @here will mention everyone in the text channel."""
return self._bit(17)
@mention_everyone.setter
def mention_everyone(self, value):
self._set(17, value)
@property
def external_emojis(self):
"""Returns True if a user can use emojis from other guilds."""
return self._bit(18)
@external_emojis.setter
def external_emojis(self, value):
self._set(18, value)
# 1 unused
@property
def connect(self):
"""Returns True if a user can connect to a voice channel."""
return self._bit(20)
@connect.setter
def connect(self, value):
self._set(20, value)
@property
def speak(self):
"""Returns True if a user can speak in a voice channel."""
return self._bit(21)
@speak.setter
def speak(self, value):
self._set(21, value)
@property
def mute_members(self):
"""Returns True if a user can mute other users."""
return self._bit(22)
@mute_members.setter
def mute_members(self, value):
self._set(22, value)
@property
def deafen_members(self):
"""Returns True if a user can deafen other users."""
return self._bit(23)
@deafen_members.setter
def deafen_members(self, value):
self._set(23, value)
@property
def move_members(self):
"""Returns True if a user can move users between other voice channels."""
return self._bit(24)
@move_members.setter
def move_members(self, value):
self._set(24, value)
@property
def use_voice_activation(self):
"""Returns True if a user can use voice activation in voice channels."""
return self._bit(25)
@use_voice_activation.setter
def use_voice_activation(self, value):
self._set(25, value)
@property
def change_nickname(self):
"""Returns True if a user can change their nickname in the guild."""
return self._bit(26)
@change_nickname.setter
def change_nickname(self, value):
self._set(26, value)
@property
def manage_nicknames(self):
"""Returns True if a user can change other user's nickname in the guild."""
return self._bit(27)
@manage_nicknames.setter
def manage_nicknames(self, value):
self._set(27, value)
@property
def manage_roles(self):
"""Returns True if a user can create or edit roles less than their role's position.
This also corresponds to the "Manage Permissions" channel-specific override.
"""
return self._bit(28)
@manage_roles.setter
def manage_roles(self, value):
self._set(28, value)
@property
def manage_webhooks(self):
"""Returns True if a user can create, edit, or delete webhooks."""
return self._bit(29)
@manage_webhooks.setter
def manage_webhooks(self, value):
self._set(29, value)
@property
def manage_emojis(self):
"""Returns True if a user can create, edit, or delete emojis."""
return self._bit(30)
@manage_emojis.setter
def manage_emojis(self, value):
self._set(30, value)
# 1 unused
# after these 32 bits, there's 21 more unused ones technically
def augment_from_permissions(cls):
cls.VALID_NAMES = {
name for name in dir(Permissions) if isinstance(getattr(Permissions, name), property)
}
# make descriptors for all the valid names
for name in cls.VALID_NAMES:
# god bless Python
def getter(self, x=name):
return self._values.get(x)
def setter(self, value, x=name):
self._set(x, value)
prop = property(getter, setter)
setattr(cls, name, prop)
return cls
@augment_from_permissions
class PermissionOverwrite:
r"""A type that is used to represent a channel specific permission.
Unlike a regular :class:`Permissions`\, the default value of a
permission is equivalent to ``None`` and not ``False``. Setting
a value to ``False`` is **explicitly** denying that permission,
while setting a value to ``True`` is **explicitly** allowing
that permission.
The values supported by this are the same as :class:`Permissions`
with the added possibility of it being set to ``None``.
Supported operations:
+-----------+------------------------------------------+
| Operation | Description |
+===========+==========================================+
| iter(x) | Returns an iterator of (perm, value) |
| | pairs. This allows this class to be used |
| | as an iterable in e.g. set/list/dict |
| | constructions. |
+-----------+------------------------------------------+
Parameters
-----------
\*\*kwargs
Set the value of permissions by their name.
"""
__slots__ = ("_values",)
def __init__(self, **kwargs):
self._values = {}
for key, value in kwargs.items():
if key not in self.VALID_NAMES:
raise ValueError("no permission called {0}.".format(key))
setattr(self, key, value)
def _set(self, key, value):
if value not in (True, None, False):
raise TypeError(
"Expected bool or NoneType, received {0.__class__.__name__}".format(value)
)
self._values[key] = value
def pair(self):
"""Returns the (allow, deny) pair from this overwrite.
The value of these pairs is :class:`Permissions`.
"""
allow = Permissions.none()
deny = Permissions.none()
for key, value in self._values.items():
if value is True:
setattr(allow, key, True)
elif value is False:
setattr(deny, key, True)
return allow, deny
@classmethod
def from_pair(cls, allow, deny):
"""Creates an overwrite from an allow/deny pair of :class:`Permissions`."""
ret = cls()
for key, value in allow:
if value is True:
setattr(ret, key, True)
for key, value in deny:
if value is True:
setattr(ret, key, False)
return ret
def is_empty(self):
"""Checks if the permission overwrite is currently empty.
An empty permission overwrite is one that has no overwrites set
to True or False.
"""
return all(x is None for x in self._values.values())
def update(self, **kwargs):
r"""Bulk updates this permission overwrite object.
Allows you to set multiple attributes by using keyword
arguments. The names must be equivalent to the properties
listed. Extraneous key/value pairs will be silently ignored.
Parameters
------------
\*\*kwargs
A list of key/value pairs to bulk update with.
"""
for key, value in kwargs.items():
if key not in self.VALID_NAMES:
continue
setattr(self, key, value)
def __iter__(self):
for key in self.VALID_NAMES:
yield key, self._values.get(key)

View File

@@ -1,356 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import threading
import subprocess
import audioop
import logging
import shlex
import time
from .errors import ClientException
from .opus import Encoder as OpusEncoder
log = logging.getLogger(__name__)
__all__ = ["AudioSource", "PCMAudio", "FFmpegPCMAudio", "PCMVolumeTransformer"]
class AudioSource:
"""Represents an audio stream.
The audio stream can be Opus encoded or not, however if the audio stream
is not Opus encoded then the audio format must be 16-bit 48KHz stereo PCM.
.. warning::
The audio source reads are done in a separate thread.
"""
def read(self):
"""Reads 20ms worth of audio.
Subclasses must implement this.
If the audio is complete, then returning an empty
:term:`py:bytes-like object` to signal this is the way to do so.
If :meth:`is_opus` method returns ``True``, then it must return
20ms worth of Opus encoded audio. Otherwise, it must be 20ms
worth of 16-bit 48KHz stereo PCM, which is about 3,840 bytes
per frame (20ms worth of audio).
Returns
--------
bytes
A bytes like object that represents the PCM or Opus data.
"""
raise NotImplementedError
def is_opus(self):
"""Checks if the audio source is already encoded in Opus.
Defaults to ``False``.
"""
return False
def cleanup(self):
"""Called when clean-up is needed to be done.
Useful for clearing buffer data or processes after
it is done playing audio.
"""
pass
def __del__(self):
self.cleanup()
class PCMAudio(AudioSource):
"""Represents raw 16-bit 48KHz stereo PCM audio source.
Attributes
-----------
stream: file-like object
A file-like object that reads byte data representing raw PCM.
"""
def __init__(self, stream):
self.stream = stream
def read(self):
ret = self.stream.read(OpusEncoder.FRAME_SIZE)
if len(ret) != OpusEncoder.FRAME_SIZE:
return b""
return ret
class FFmpegPCMAudio(AudioSource):
"""An audio source from FFmpeg (or AVConv).
This launches a sub-process to a specific input file given.
.. warning::
You must have the ffmpeg or avconv executable in your path environment
variable in order for this to work.
Parameters
------------
source: Union[str, BinaryIO]
The input that ffmpeg will take and convert to PCM bytes.
If ``pipe`` is True then this is a file-like object that is
passed to the stdin of ffmpeg.
executable: str
The executable name (and path) to use. Defaults to ``ffmpeg``.
pipe: bool
If true, denotes that ``source`` parameter will be passed
to the stdin of ffmpeg. Defaults to ``False``.
stderr: Optional[BinaryIO]
A file-like object to pass to the Popen constructor.
Could also be an instance of ``subprocess.PIPE``.
options: Optional[str]
Extra command line arguments to pass to ffmpeg after the ``-i`` flag.
before_options: Optional[str]
Extra command line arguments to pass to ffmpeg before the ``-i`` flag.
Raises
--------
ClientException
The subprocess failed to be created.
"""
def __init__(
self,
source,
*,
executable="ffmpeg",
pipe=False,
stderr=None,
before_options=None,
options=None
):
stdin = None if not pipe else source
args = [executable]
if isinstance(before_options, str):
args.extend(shlex.split(before_options))
args.append("-i")
args.append("-" if pipe else source)
args.extend(("-f", "s16le", "-ar", "48000", "-ac", "2", "-loglevel", "warning"))
if isinstance(options, str):
args.extend(shlex.split(options))
args.append("pipe:1")
self._process = None
try:
self._process = subprocess.Popen(
args, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr
)
self._stdout = self._process.stdout
except FileNotFoundError:
raise ClientException(executable + " was not found.") from None
except subprocess.SubprocessError as exc:
raise ClientException("Popen failed: {0.__class__.__name__}: {0}".format(exc)) from exc
def read(self):
ret = self._stdout.read(OpusEncoder.FRAME_SIZE)
if len(ret) != OpusEncoder.FRAME_SIZE:
return b""
return ret
def cleanup(self):
proc = self._process
if proc is None:
return
log.info("Preparing to terminate ffmpeg process %s.", proc.pid)
proc.kill()
if proc.poll() is None:
log.info("ffmpeg process %s has not terminated. Waiting to terminate...", proc.pid)
proc.communicate()
log.info(
"ffmpeg process %s should have terminated with a return code of %s.",
proc.pid,
proc.returncode,
)
else:
log.info(
"ffmpeg process %s successfully terminated with return code of %s.",
proc.pid,
proc.returncode,
)
self._process = None
class PCMVolumeTransformer(AudioSource):
"""Transforms a previous :class:`AudioSource` to have volume controls.
This does not work on audio sources that have :meth:`AudioSource.is_opus`
set to ``True``.
Parameters
------------
original: :class:`AudioSource`
The original AudioSource to transform.
volume: float
The initial volume to set it to.
See :attr:`volume` for more info.
Raises
-------
TypeError
Not an audio source.
ClientException
The audio source is opus encoded.
"""
def __init__(self, original, volume=1.0):
if not isinstance(original, AudioSource):
raise TypeError("expected AudioSource not {0.__class__.__name__}.".format(original))
if original.is_opus():
raise ClientException("AudioSource must not be Opus encoded.")
self.original = original
self.volume = volume
@property
def volume(self):
"""Retrieves or sets the volume as a floating point percentage (e.g. 1.0 for 100%)."""
return self._volume
@volume.setter
def volume(self, value):
self._volume = max(value, 0.0)
def cleanup(self):
self.original.cleanup()
def read(self):
ret = self.original.read()
return audioop.mul(ret, 2, min(self._volume, 2.0))
class AudioPlayer(threading.Thread):
DELAY = OpusEncoder.FRAME_LENGTH / 1000.0
def __init__(self, source, client, *, after=None):
threading.Thread.__init__(self)
self.daemon = True
self.source = source
self.client = client
self.after = after
self._end = threading.Event()
self._resumed = threading.Event()
self._resumed.set() # we are not paused
self._current_error = None
self._connected = client._connected
self._lock = threading.Lock()
if after is not None and not callable(after):
raise TypeError('Expected a callable for the "after" parameter.')
def _do_run(self):
self.loops = 0
self._start = time.time()
# getattr lookup speed ups
play_audio = self.client.send_audio_packet
while not self._end.is_set():
# are we paused?
if not self._resumed.is_set():
# wait until we aren't
self._resumed.wait()
continue
# are we disconnected from voice?
if not self._connected.is_set():
# wait until we are connected
self._connected.wait()
# reset our internal data
self.loops = 0
self._start = time.time()
self.loops += 1
data = self.source.read()
if not data:
self.stop()
break
play_audio(data, encode=not self.source.is_opus())
next_time = self._start + self.DELAY * self.loops
delay = max(0, self.DELAY + (next_time - time.time()))
time.sleep(delay)
def run(self):
try:
self._do_run()
except Exception as exc:
self._current_error = exc
self.stop()
finally:
self.source.cleanup()
self._call_after()
def _call_after(self):
if self.after is not None:
try:
self.after(self._current_error)
except Exception:
log.exception("Calling the after function failed.")
def stop(self):
self._end.set()
self._resumed.set()
def pause(self):
self._resumed.clear()
def resume(self):
self.loops = 0
self._start = time.time()
self._resumed.set()
def is_playing(self):
return self._resumed.is_set() and not self._end.is_set()
def is_paused(self):
return not self._end.is_set() and not self._resumed.is_set()
def _set_source(self, source):
with self._lock:
self.pause()
self.source = source
self.resume()

View File

@@ -1,151 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2018 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
class RawMessageDeleteEvent:
"""Represents the event payload for a :func:`on_raw_message_delete` event.
Attributes
------------
channel_id: :class:`int`
The channel ID where the deletion took place.
guild_id: Optional[:class:`int`]
The guild ID where the deletion took place, if applicable.
message_id: :class:`int`
The message ID that got deleted.
"""
__slots__ = ("message_id", "channel_id", "guild_id")
def __init__(self, data):
self.message_id = int(data["id"])
self.channel_id = int(data["channel_id"])
try:
self.guild_id = int(data["guild_id"])
except KeyError:
self.guild_id = None
class RawBulkMessageDeleteEvent:
"""Represents the event payload for a :func:`on_raw_bulk_message_delete` event.
Attributes
-----------
message_ids: Set[:class:`int`]
A :class:`set` of the message IDs that were deleted.
channel_id: :class:`int`
The channel ID where the message got deleted.
guild_id: Optional[:class:`int`]
The guild ID where the message got deleted, if applicable.
"""
__slots__ = ("message_ids", "channel_id", "guild_id")
def __init__(self, data):
self.message_ids = {int(x) for x in data.get("ids", [])}
self.channel_id = int(data["channel_id"])
try:
self.guild_id = int(data["guild_id"])
except KeyError:
self.guild_id = None
class RawMessageUpdateEvent:
"""Represents the payload for a :func:`on_raw_message_edit` event.
Attributes
-----------
message_id: :class:`int`
The message ID that got updated.
data: :class:`dict`
The raw data given by the
`gateway <https://discordapp.com/developers/docs/topics/gateway#message-update>`_
"""
__slots__ = ("message_id", "data")
def __init__(self, data):
self.message_id = int(data["id"])
self.data = data
class RawReactionActionEvent:
"""Represents the payload for a :func:`on_raw_reaction_add` or
:func:`on_raw_reaction_remove` event.
Attributes
-----------
message_id: :class:`int`
The message ID that got or lost a reaction.
user_id: :class:`int`
The user ID who added or removed the reaction.
channel_id: :class:`int`
The channel ID where the reaction got added or removed.
guild_id: Optional[:class:`int`]
The guild ID where the reaction got added or removed, if applicable.
emoji: :class:`PartialEmoji`
The custom or unicode emoji being used.
"""
__slots__ = ("message_id", "user_id", "channel_id", "guild_id", "emoji")
def __init__(self, data, emoji):
self.message_id = int(data["message_id"])
self.channel_id = int(data["channel_id"])
self.user_id = int(data["user_id"])
self.emoji = emoji
try:
self.guild_id = int(data["guild_id"])
except KeyError:
self.guild_id = None
class RawReactionClearEvent:
"""Represents the payload for a :func:`on_raw_reaction_clear` event.
Attributes
-----------
message_id: :class:`int`
The message ID that got its reactions cleared.
channel_id: :class:`int`
The channel ID where the reactions got cleared.
guild_id: Optional[:class:`int`]
The guild ID where the reactions got cleared.
"""
__slots__ = ("message_id", "channel_id", "guild_id")
def __init__(self, data):
self.message_id = int(data["message_id"])
self.channel_id = int(data["channel_id"])
try:
self.guild_id = int(data["guild_id"])
except KeyError:
self.guild_id = None

View File

@@ -1,151 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from .iterators import ReactionIterator
class Reaction:
"""Represents a reaction to a message.
Depending on the way this object was created, some of the attributes can
have a value of ``None``.
.. container:: operations
.. describe:: x == y
Checks if two reactions are equal. This works by checking if the emoji
is the same. So two messages with the same reaction will be considered
"equal".
.. describe:: x != y
Checks if two reactions are not equal.
.. describe:: hash(x)
Returns the reaction's hash.
.. describe:: str(x)
Returns the string form of the reaction's emoji.
Attributes
-----------
emoji: :class:`Emoji` or :class:`str`
The reaction emoji. May be a custom emoji, or a unicode emoji.
count: :class:`int`
Number of times this reaction was made
me: :class:`bool`
If the user sent this reaction.
message: :class:`Message`
Message this reaction is for.
"""
__slots__ = ("message", "count", "emoji", "me")
def __init__(self, *, message, data, emoji=None):
self.message = message
self.emoji = emoji or message._state.get_reaction_emoji(data["emoji"])
self.count = data.get("count", 1)
self.me = data.get("me")
@property
def custom_emoji(self):
""":class:`bool`: If this is a custom emoji."""
return not isinstance(self.emoji, str)
def __eq__(self, other):
return isinstance(other, self.__class__) and other.emoji == self.emoji
def __ne__(self, other):
if isinstance(other, self.__class__):
return other.emoji != self.emoji
return True
def __hash__(self):
return hash(self.emoji)
def __str__(self):
return str(self.emoji)
def __repr__(self):
return "<Reaction emoji={0.emoji!r} me={0.me} count={0.count}>".format(self)
def users(self, limit=None, after=None):
"""Returns an :class:`AsyncIterator` representing the users that have reacted to the message.
The ``after`` parameter must represent a member
and meet the :class:`abc.Snowflake` abc.
Parameters
------------
limit: int
The maximum number of results to return.
If not provided, returns all the users who
reacted to the message.
after: :class:`abc.Snowflake`
For pagination, reactions are sorted by member.
Raises
--------
HTTPException
Getting the users for the reaction failed.
Examples
---------
Usage ::
# I do not actually recommend doing this.
async for user in reaction.users():
await channel.send('{0} has reacted with {1.emoji}!'.format(user, reaction))
Flattening into a list: ::
users = await reaction.users().flatten()
# users is now a list...
winner = random.choice(users)
await channel.send('{} has won the raffle.'.format(winner))
Yields
--------
Union[:class:`User`, :class:`Member`]
The member (if retrievable) or the user that has reacted
to this message. The case where it can be a :class:`Member` is
in a guild message context. Sometimes it can be a :class:`User`
if the member has left the guild.
"""
if self.custom_emoji:
emoji = "{0.name}:{0.id}".format(self.emoji)
else:
emoji = self.emoji
if limit is None:
limit = self.count
return ReactionIterator(self.message, emoji, limit, after)

View File

@@ -1,79 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from .enums import RelationshipType, try_enum
class Relationship:
"""Represents a relationship in Discord.
A relationship is like a friendship, a person who is blocked, etc.
Only non-bot accounts can have relationships.
Attributes
-----------
user: :class:`User`
The user you have the relationship with.
type: :class:`RelationshipType`
The type of relationship you have.
"""
__slots__ = ("type", "user", "_state")
def __init__(self, *, state, data):
self._state = state
self.type = try_enum(RelationshipType, data["type"])
self.user = state.store_user(data["user"])
def __repr__(self):
return "<Relationship user={0.user!r} type={0.type!r}>".format(self)
async def delete(self):
"""|coro|
Deletes the relationship.
Raises
------
HTTPException
Deleting the relationship failed.
"""
await self._state.http.remove_relationship(self.user.id)
async def accept(self):
"""|coro|
Accepts the relationship request. e.g. accepting a
friend request.
Raises
-------
HTTPException
Accepting the relationship failed.
"""
await self._state.http.add_relationship(self.user.id)

View File

@@ -1,297 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from .permissions import Permissions
from .errors import InvalidArgument
from .colour import Colour
from .mixins import Hashable
from .utils import snowflake_time
class Role(Hashable):
"""Represents a Discord role in a :class:`Guild`.
.. container:: operations
.. describe:: x == y
Checks if two roles are equal.
.. describe:: x != y
Checks if two roles are not equal.
.. describe:: x > y
Checks if a role is higher than another in the hierarchy.
.. describe:: x < y
Checks if a role is lower than another in the hierarchy.
.. describe:: x >= y
Checks if a role is higher or equal to another in the hierarchy.
.. describe:: x <= y
Checks if a role is lower or equal to another in the hierarchy.
.. describe:: hash(x)
Return the role's hash.
.. describe:: str(x)
Returns the role's name.
Attributes
----------
id: :class:`int`
The ID for the role.
name: :class:`str`
The name of the role.
permissions: :class:`Permissions`
Represents the role's permissions.
guild: :class:`Guild`
The guild the role belongs to.
colour: :class:`Colour`
Represents the role colour. An alias exists under ``color``.
hoist: :class:`bool`
Indicates if the role will be displayed separately from other members.
position: :class:`int`
The position of the role. This number is usually positive. The bottom
role has a position of 0.
managed: :class:`bool`
Indicates if the role is managed by the guild through some form of
integrations such as Twitch.
mentionable: :class:`bool`
Indicates if the role can be mentioned by users.
"""
__slots__ = (
"id",
"name",
"permissions",
"color",
"colour",
"position",
"managed",
"mentionable",
"hoist",
"guild",
"_state",
)
def __init__(self, *, guild, state, data):
self.guild = guild
self._state = state
self.id = int(data["id"])
self._update(data)
def __str__(self):
return self.name
def __repr__(self):
return "<Role id={0.id} name={0.name!r}>".format(self)
def __lt__(self, other):
if not isinstance(other, Role) or not isinstance(self, Role):
return NotImplemented
if self.guild != other.guild:
raise RuntimeError("cannot compare roles from two different guilds.")
# the @everyone role is always the lowest role in hierarchy
guild_id = self.guild.id
if self.id == guild_id:
# everyone_role < everyone_role -> False
return other.id != guild_id
if self.position < other.position:
return True
if self.position == other.position:
return int(self.id) > int(other.id)
return False
def __le__(self, other):
r = Role.__lt__(other, self)
if r is NotImplemented:
return NotImplemented
return not r
def __gt__(self, other):
return Role.__lt__(other, self)
def __ge__(self, other):
r = Role.__lt__(self, other)
if r is NotImplemented:
return NotImplemented
return not r
def _update(self, data):
self.name = data["name"]
self.permissions = Permissions(data.get("permissions", 0))
self.position = data.get("position", 0)
self.colour = Colour(data.get("color", 0))
self.hoist = data.get("hoist", False)
self.managed = data.get("managed", False)
self.mentionable = data.get("mentionable", False)
self.color = self.colour
def is_default(self):
"""Checks if the role is the default role."""
return self.guild.id == self.id
@property
def created_at(self):
"""Returns the role's creation time in UTC."""
return snowflake_time(self.id)
@property
def mention(self):
"""Returns a string that allows you to mention a role."""
return "<@&%s>" % self.id
@property
def members(self):
"""Returns a :class:`list` of :class:`Member` with this role."""
all_members = self.guild.members
if self.is_default():
return all_members
role_id = self.id
return [member for member in all_members if member._roles.has(role_id)]
async def _move(self, position, reason):
if position <= 0:
raise InvalidArgument("Cannot move role to position 0 or below")
if self.is_default():
raise InvalidArgument("Cannot move default role")
if self.position == position:
return # Save discord the extra request.
http = self._state.http
change_range = range(min(self.position, position), max(self.position, position) + 1)
roles = [
r.id for r in self.guild.roles[1:] if r.position in change_range and r.id != self.id
]
if self.position > position:
roles.insert(0, self.id)
else:
roles.append(self.id)
payload = [{"id": z[0], "position": z[1]} for z in zip(roles, change_range)]
await http.move_role_position(self.guild.id, payload, reason=reason)
async def edit(self, *, reason=None, **fields):
"""|coro|
Edits the role.
You must have the :attr:`~Permissions.manage_roles` permission to
use this.
All fields are optional.
Parameters
-----------
name: str
The new role name to change to.
permissions: :class:`Permissions`
The new permissions to change to.
colour: :class:`Colour`
The new colour to change to. (aliased to color as well)
hoist: bool
Indicates if the role should be shown separately in the member list.
mentionable: bool
Indicates if the role should be mentionable by others.
position: int
The new role's position. This must be below your top role's
position or it will fail.
reason: Optional[str]
The reason for editing this role. Shows up on the audit log.
Raises
-------
Forbidden
You do not have permissions to change the role.
HTTPException
Editing the role failed.
InvalidArgument
An invalid position was given or the default
role was asked to be moved.
"""
position = fields.get("position")
if position is not None:
await self._move(position, reason=reason)
self.position = position
try:
colour = fields["colour"]
except KeyError:
colour = fields.get("color", self.colour)
payload = {
"name": fields.get("name", self.name),
"permissions": fields.get("permissions", self.permissions).value,
"color": colour.value,
"hoist": fields.get("hoist", self.hoist),
"mentionable": fields.get("mentionable", self.mentionable),
}
data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload)
self._update(data)
async def delete(self, *, reason=None):
"""|coro|
Deletes the role.
You must have the :attr:`~Permissions.manage_roles` permission to
use this.
Parameters
-----------
reason: Optional[str]
The reason for deleting this role. Shows up on the audit log.
Raises
--------
Forbidden
You do not have permissions to delete the role.
HTTPException
Deleting the role failed.
"""
await self._state.http.delete_role(self.guild.id, self.id, reason=reason)

View File

@@ -1,370 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import asyncio
import itertools
import logging
import websockets
from .state import AutoShardedConnectionState
from .client import Client
from .gateway import *
from .errors import ClientException, InvalidArgument
from . import utils
from .enums import Status
log = logging.getLogger(__name__)
class Shard:
def __init__(self, ws, client):
self.ws = ws
self._client = client
self.loop = self._client.loop
self._current = self.loop.create_future()
self._current.set_result(None) # we just need an already done future
self._pending = asyncio.Event(loop=self.loop)
self._pending_task = None
@property
def id(self):
return self.ws.shard_id
def is_pending(self):
return not self._pending.is_set()
def complete_pending_reads(self):
self._pending.set()
async def _pending_reads(self):
try:
while self.is_pending():
await self.poll()
except asyncio.CancelledError:
pass
def launch_pending_reads(self):
self._pending_task = asyncio.ensure_future(self._pending_reads(), loop=self.loop)
def wait(self):
return self._pending_task
async def poll(self):
try:
await self.ws.poll_event()
except ResumeWebSocket:
log.info("Got a request to RESUME the websocket at Shard ID %s.", self.id)
coro = DiscordWebSocket.from_client(
self._client,
resume=True,
shard_id=self.id,
session=self.ws.session_id,
sequence=self.ws.sequence,
)
self.ws = await asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
def get_future(self):
if self._current.done():
self._current = asyncio.ensure_future(self.poll(), loop=self.loop)
return self._current
class AutoShardedClient(Client):
"""A client similar to :class:`Client` except it handles the complications
of sharding for the user into a more manageable and transparent single
process bot.
When using this client, you will be able to use it as-if it was a regular
:class:`Client` with a single shard when implementation wise internally it
is split up into multiple shards. This allows you to not have to deal with
IPC or other complicated infrastructure.
It is recommended to use this client only if you have surpassed at least
1000 guilds.
If no :attr:`shard_count` is provided, then the library will use the
Bot Gateway endpoint call to figure out how many shards to use.
If a ``shard_ids`` parameter is given, then those shard IDs will be used
to launch the internal shards. Note that :attr:`shard_count` must be provided
if this is used. By default, when omitted, the client will launch shards from
0 to ``shard_count - 1``.
Attributes
------------
shard_ids: Optional[List[:class:`int`]]
An optional list of shard_ids to launch the shards with.
"""
def __init__(self, *args, loop=None, **kwargs):
kwargs.pop("shard_id", None)
self.shard_ids = kwargs.pop("shard_ids", None)
super().__init__(*args, loop=loop, **kwargs)
if self.shard_ids is not None:
if self.shard_count is None:
raise ClientException(
"When passing manual shard_ids, you must provide a shard_count."
)
elif not isinstance(self.shard_ids, (list, tuple)):
raise ClientException("shard_ids parameter must be a list or a tuple.")
self._connection = AutoShardedConnectionState(
dispatch=self.dispatch,
chunker=self._chunker,
handlers=self._handlers,
syncer=self._syncer,
http=self.http,
loop=self.loop,
**kwargs
)
# instead of a single websocket, we have multiple
# the key is the shard_id
self.shards = {}
def _get_websocket(guild_id):
i = (guild_id >> 22) % self.shard_count
return self.shards[i].ws
self._connection._get_websocket = _get_websocket
async def _chunker(self, guild, *, shard_id=None):
try:
guild_id = guild.id
shard_id = shard_id or guild.shard_id
except AttributeError:
guild_id = [s.id for s in guild]
payload = {"op": 8, "d": {"guild_id": guild_id, "query": "", "limit": 0}}
ws = self.shards[shard_id].ws
await ws.send_as_json(payload)
@property
def latency(self):
""":class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds.
This operates similarly to :meth:`.Client.latency` except it uses the average
latency of every shard's latency. To get a list of shard latency, check the
:attr:`latencies` property. Returns ``nan`` if there are no shards ready.
"""
if not self.shards:
return float("nan")
return sum(latency for _, latency in self.latencies) / len(self.shards)
@property
def latencies(self):
"""List[Tuple[:class:`int`, :class:`float`]]: A list of latencies between a HEARTBEAT and a HEARTBEAT_ACK in seconds.
This returns a list of tuples with elements ``(shard_id, latency)``.
"""
return [(shard_id, shard.ws.latency) for shard_id, shard in self.shards.items()]
async def request_offline_members(self, *guilds):
r"""|coro|
Requests previously offline members from the guild to be filled up
into the :attr:`Guild.members` cache. This function is usually not
called. It should only be used if you have the ``fetch_offline_members``
parameter set to ``False``.
When the client logs on and connects to the websocket, Discord does
not provide the library with offline members if the number of members
in the guild is larger than 250. You can check if a guild is large
if :attr:`Guild.large` is ``True``.
Parameters
-----------
\*guilds
An argument list of guilds to request offline members for.
Raises
-------
InvalidArgument
If any guild is unavailable or not large in the collection.
"""
if any(not g.large or g.unavailable for g in guilds):
raise InvalidArgument("An unavailable or non-large guild was passed.")
_guilds = sorted(guilds, key=lambda g: g.shard_id)
for shard_id, sub_guilds in itertools.groupby(_guilds, key=lambda g: g.shard_id):
sub_guilds = list(sub_guilds)
await self._connection.request_offline_members(sub_guilds, shard_id=shard_id)
async def launch_shard(self, gateway, shard_id):
try:
coro = websockets.connect(
gateway, loop=self.loop, klass=DiscordWebSocket, compression=None
)
ws = await asyncio.wait_for(coro, loop=self.loop, timeout=180.0)
except Exception:
log.info("Failed to connect for shard_id: %s. Retrying...", shard_id)
await asyncio.sleep(5.0, loop=self.loop)
return await self.launch_shard(gateway, shard_id)
ws.token = self.http.token
ws._connection = self._connection
ws._dispatch = self.dispatch
ws.gateway = gateway
ws.shard_id = shard_id
ws.shard_count = self.shard_count
ws._max_heartbeat_timeout = self._connection.heartbeat_timeout
try:
# OP HELLO
await asyncio.wait_for(ws.poll_event(), loop=self.loop, timeout=180.0)
await asyncio.wait_for(ws.identify(), loop=self.loop, timeout=180.0)
except asyncio.TimeoutError:
log.info("Timed out when connecting for shard_id: %s. Retrying...", shard_id)
await asyncio.sleep(5.0, loop=self.loop)
return await self.launch_shard(gateway, shard_id)
# keep reading the shard while others connect
self.shards[shard_id] = ret = Shard(ws, self)
ret.launch_pending_reads()
await asyncio.sleep(5.0, loop=self.loop)
async def launch_shards(self):
if self.shard_count is None:
self.shard_count, gateway = await self.http.get_bot_gateway()
else:
gateway = await self.http.get_gateway()
self._connection.shard_count = self.shard_count
shard_ids = self.shard_ids if self.shard_ids else range(self.shard_count)
for shard_id in shard_ids:
await self.launch_shard(gateway, shard_id)
shards_to_wait_for = []
for shard in self.shards.values():
shard.complete_pending_reads()
shards_to_wait_for.append(shard.wait())
# wait for all pending tasks to finish
await utils.sane_wait_for(shards_to_wait_for, timeout=300.0, loop=self.loop)
async def _connect(self):
await self.launch_shards()
while True:
pollers = [shard.get_future() for shard in self.shards.values()]
done, _ = await asyncio.wait(
pollers, loop=self.loop, return_when=asyncio.FIRST_COMPLETED
)
for f in done:
# we wanna re-raise to the main Client.connect handler if applicable
f.result()
async def close(self):
"""|coro|
Closes the connection to discord.
"""
if self.is_closed():
return
self._closed.set()
for vc in self.voice_clients:
try:
await vc.disconnect()
except Exception:
pass
to_close = [shard.ws.close() for shard in self.shards.values()]
if to_close:
await asyncio.wait(to_close, loop=self.loop)
await self.http.close()
async def change_presence(self, *, activity=None, status=None, afk=False, shard_id=None):
"""|coro|
Changes the client's presence.
The activity parameter is a :class:`Activity` object (not a string) that represents
the activity being done currently. This could also be the slimmed down versions,
:class:`Game` and :class:`Streaming`.
Example: ::
game = discord.Game("with the API")
await client.change_presence(status=discord.Status.idle, activity=game)
Parameters
----------
activity: Optional[Union[:class:`Game`, :class:`Streaming`, :class:`Activity`]]
The activity being done. ``None`` if no currently active activity is done.
status: Optional[:class:`Status`]
Indicates what status to change to. If None, then
:attr:`Status.online` is used.
afk: bool
Indicates if you are going AFK. This allows the discord
client to know how to handle push notifications better
for you in case you are actually idle and not lying.
shard_id: Optional[int]
The shard_id to change the presence to. If not specified
or ``None``, then it will change the presence of every
shard the bot can see.
Raises
------
InvalidArgument
If the ``activity`` parameter is not of proper type.
"""
if status is None:
status = "online"
status_enum = Status.online
elif status is Status.offline:
status = "invisible"
status_enum = Status.offline
else:
status_enum = status
status = str(status)
if shard_id is None:
for shard in self.shards.values():
await shard.ws.change_presence(activity=activity, status=status, afk=afk)
guilds = self._connection.guilds
else:
shard = self.shards[shard_id]
await shard.ws.change_presence(activity=activity, status=status, afk=afk)
guilds = [g for g in self._connection.guilds if g.shard_id == shard_id]
for guild in guilds:
me = guild.me
if me is None:
continue
me.activities = (activity,)
me.status = status_enum

File diff suppressed because it is too large Load Diff

View File

@@ -1,699 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from collections import namedtuple
import discord.abc
from .utils import snowflake_time, _bytes_to_base64_data, parse_time, valid_icon_size
from .enums import DefaultAvatar, RelationshipType, UserFlags, HypeSquadHouse
from .errors import ClientException, InvalidArgument
from .colour import Colour
VALID_STATIC_FORMATS = {"jpeg", "jpg", "webp", "png"}
VALID_AVATAR_FORMATS = VALID_STATIC_FORMATS | {"gif"}
class Profile(namedtuple("Profile", "flags user mutual_guilds connected_accounts premium_since")):
__slots__ = ()
@property
def nitro(self):
return self.premium_since is not None
premium = nitro
def _has_flag(self, o):
v = o.value
return (self.flags & v) == v
@property
def staff(self):
return self._has_flag(UserFlags.staff)
@property
def partner(self):
return self._has_flag(UserFlags.partner)
@property
def bug_hunter(self):
return self._has_flag(UserFlags.bug_hunter)
@property
def early_supporter(self):
return self._has_flag(UserFlags.early_supporter)
@property
def hypesquad(self):
return self._has_flag(UserFlags.hypesquad)
@property
def hypesquad_houses(self):
flags = (
UserFlags.hypesquad_bravery,
UserFlags.hypesquad_brilliance,
UserFlags.hypesquad_balance,
)
return [house for house, flag in zip(HypeSquadHouse, flags) if self._has_flag(flag)]
_BaseUser = discord.abc.User
class BaseUser(_BaseUser):
__slots__ = ("name", "id", "discriminator", "avatar", "bot", "_state")
def __init__(self, *, state, data):
self._state = state
self.name = data["username"]
self.id = int(data["id"])
self.discriminator = data["discriminator"]
self.avatar = data["avatar"]
self.bot = data.get("bot", False)
def __str__(self):
return "{0.name}#{0.discriminator}".format(self)
def __eq__(self, other):
return isinstance(other, _BaseUser) and other.id == self.id
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return self.id >> 22
@classmethod
def _copy(cls, user):
self = cls.__new__(cls) # bypass __init__
self.name = user.name
self.id = user.id
self.discriminator = user.discriminator
self.avatar = user.avatar
self.bot = user.bot
self._state = user._state
return self
@property
def avatar_url(self):
"""Returns a friendly URL version of the avatar the user has.
If the user does not have a traditional avatar, their default
avatar URL is returned instead.
This is equivalent to calling :meth:`avatar_url_as` with
the default parameters (i.e. webp/gif detection and a size of 1024).
"""
return self.avatar_url_as(format=None, size=1024)
def is_avatar_animated(self):
""":class:`bool`: Returns True if the user has an animated avatar."""
return bool(self.avatar and self.avatar.startswith("a_"))
def avatar_url_as(self, *, format=None, static_format="webp", size=1024):
"""Returns a friendly URL version of the avatar the user has.
If the user does not have a traditional avatar, their default
avatar URL is returned instead.
The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif', and
'gif' is only valid for animated avatars. The size must be a power of 2
between 16 and 1024.
Parameters
-----------
format: Optional[str]
The format to attempt to convert the avatar to.
If the format is ``None``, then it is automatically
detected into either 'gif' or static_format depending on the
avatar being animated or not.
static_format: 'str'
Format to attempt to convert only non-animated avatars to.
Defaults to 'webp'
size: int
The size of the image to display.
Returns
--------
str
The resulting CDN URL.
Raises
------
InvalidArgument
Bad image format passed to ``format`` or ``static_format``, or
invalid ``size``.
"""
if not valid_icon_size(size):
raise InvalidArgument("size must be a power of 2 between 16 and 1024")
if format is not None and format not in VALID_AVATAR_FORMATS:
raise InvalidArgument("format must be None or one of {}".format(VALID_AVATAR_FORMATS))
if format == "gif" and not self.is_avatar_animated():
raise InvalidArgument("non animated avatars do not support gif format")
if static_format not in VALID_STATIC_FORMATS:
raise InvalidArgument("static_format must be one of {}".format(VALID_STATIC_FORMATS))
if self.avatar is None:
return self.default_avatar_url
if format is None:
if self.is_avatar_animated():
format = "gif"
else:
format = static_format
return "https://cdn.discordapp.com/avatars/{0.id}/{0.avatar}.{1}?size={2}".format(
self, format, size
)
@property
def default_avatar(self):
"""Returns the default avatar for a given user. This is calculated by the user's discriminator"""
return DefaultAvatar(int(self.discriminator) % len(DefaultAvatar))
@property
def default_avatar_url(self):
"""Returns a URL for a user's default avatar."""
return "https://cdn.discordapp.com/embed/avatars/{}.png".format(self.default_avatar.value)
@property
def colour(self):
"""A property that returns a :class:`Colour` denoting the rendered colour
for the user. This always returns :meth:`Colour.default`.
There is an alias for this under ``color``.
"""
return Colour.default()
color = colour
@property
def mention(self):
"""Returns a string that allows you to mention the given user."""
return "<@{0.id}>".format(self)
def permissions_in(self, channel):
"""An alias for :meth:`abc.GuildChannel.permissions_for`.
Basically equivalent to:
.. code-block:: python3
channel.permissions_for(self)
Parameters
-----------
channel
The channel to check your permissions for.
"""
return channel.permissions_for(self)
@property
def created_at(self):
"""Returns the user's creation time in UTC.
This is when the user's discord account was created."""
return snowflake_time(self.id)
@property
def display_name(self):
"""Returns the user's display name.
For regular users this is just their username, but
if they have a guild specific nickname then that
is returned instead.
"""
return self.name
def mentioned_in(self, message):
"""Checks if the user is mentioned in the specified message.
Parameters
-----------
message : :class:`Message`
The message to check if you're mentioned in.
"""
if message.mention_everyone:
return True
for user in message.mentions:
if user.id == self.id:
return True
return False
class ClientUser(BaseUser):
"""Represents your Discord user.
.. container:: operations
.. describe:: x == y
Checks if two users are equal.
.. describe:: x != y
Checks if two users are not equal.
.. describe:: hash(x)
Return the user's hash.
.. describe:: str(x)
Returns the user's name with discriminator.
Attributes
-----------
name: :class:`str`
The user's username.
id: :class:`int`
The user's unique ID.
discriminator: :class:`str`
The user's discriminator. This is given when the username has conflicts.
avatar: Optional[:class:`str`]
The avatar hash the user has. Could be None.
bot: :class:`bool`
Specifies if the user is a bot account.
verified: :class:`bool`
Specifies if the user is a verified account.
email: Optional[:class:`str`]
The email the user used when registering.
mfa_enabled: :class:`bool`
Specifies if the user has MFA turned on and working.
premium: :class:`bool`
Specifies if the user is a premium user (e.g. has Discord Nitro).
"""
__slots__ = ("email", "verified", "mfa_enabled", "premium", "_relationships")
def __init__(self, *, state, data):
super().__init__(state=state, data=data)
self.verified = data.get("verified", False)
self.email = data.get("email")
self.mfa_enabled = data.get("mfa_enabled", False)
self.premium = data.get("premium", False)
self._relationships = {}
def __repr__(self):
return (
"<ClientUser id={0.id} name={0.name!r} discriminator={0.discriminator!r}"
" bot={0.bot} verified={0.verified} mfa_enabled={0.mfa_enabled}>".format(self)
)
def get_relationship(self, user_id):
"""Retrieves the :class:`Relationship` if applicable.
Parameters
-----------
user_id: int
The user ID to check if we have a relationship with them.
Returns
--------
Optional[:class:`Relationship`]
The relationship if available or ``None``
"""
return self._relationships.get(user_id)
@property
def relationships(self):
"""Returns a :class:`list` of :class:`Relationship` that the user has."""
return list(self._relationships.values())
@property
def friends(self):
r"""Returns a :class:`list` of :class:`User`\s that the user is friends with."""
return [r.user for r in self._relationships.values() if r.type is RelationshipType.friend]
@property
def blocked(self):
r"""Returns a :class:`list` of :class:`User`\s that the user has blocked."""
return [r.user for r in self._relationships.values() if r.type is RelationshipType.blocked]
async def edit(self, **fields):
"""|coro|
Edits the current profile of the client.
If a bot account is used then a password field is optional,
otherwise it is required.
Note
-----
To upload an avatar, a :term:`py:bytes-like object` must be passed in that
represents the image being uploaded. If this is done through a file
then the file must be opened via ``open('some_filename', 'rb')`` and
the :term:`py:bytes-like object` is given through the use of ``fp.read()``.
The only image formats supported for uploading is JPEG and PNG.
Parameters
-----------
password : str
The current password for the client's account.
Only applicable to user accounts.
new_password: str
The new password you wish to change to.
Only applicable to user accounts.
email: str
The new email you wish to change to.
Only applicable to user accounts.
house: Optional[:class:`HypeSquadHouse`]
The hypesquad house you wish to change to.
Could be ``None`` to leave the current house.
Only applicable to user accounts.
username :str
The new username you wish to change to.
avatar: bytes
A :term:`py:bytes-like object` representing the image to upload.
Could be ``None`` to denote no avatar.
Raises
------
HTTPException
Editing your profile failed.
InvalidArgument
Wrong image format passed for ``avatar``.
ClientException
Password is required for non-bot accounts.
House field was not a HypeSquadHouse.
"""
try:
avatar_bytes = fields["avatar"]
except KeyError:
avatar = self.avatar
else:
if avatar_bytes is not None:
avatar = _bytes_to_base64_data(avatar_bytes)
else:
avatar = None
not_bot_account = not self.bot
password = fields.get("password")
if not_bot_account and password is None:
raise ClientException("Password is required for non-bot accounts.")
args = {
"password": password,
"username": fields.get("username", self.name),
"avatar": avatar,
}
if not_bot_account:
args["email"] = fields.get("email", self.email)
if "new_password" in fields:
args["new_password"] = fields["new_password"]
http = self._state.http
if "house" in fields:
house = fields["house"]
if house is None:
await http.leave_hypesquad_house()
elif not isinstance(house, HypeSquadHouse):
raise ClientException("`house` parameter was not a HypeSquadHouse")
else:
value = house.value
await http.change_hypesquad_house(value)
data = await http.edit_profile(**args)
if not_bot_account:
self.email = data["email"]
try:
http._token(data["token"], bot=False)
except KeyError:
pass
# manually update data by calling __init__ explicitly.
self.__init__(state=self._state, data=data)
async def create_group(self, *recipients):
r"""|coro|
Creates a group direct message with the recipients
provided. These recipients must be have a relationship
of type :attr:`RelationshipType.friend`.
Bot accounts cannot create a group.
Parameters
-----------
\*recipients
An argument :class:`list` of :class:`User` to have in
your group.
Return
-------
:class:`GroupChannel`
The new group channel.
Raises
-------
HTTPException
Failed to create the group direct message.
ClientException
Attempted to create a group with only one recipient.
This does not include yourself.
"""
from .channel import GroupChannel
if len(recipients) < 2:
raise ClientException("You must have two or more recipients to create a group.")
users = [str(u.id) for u in recipients]
data = await self._state.http.start_group(self.id, users)
return GroupChannel(me=self, data=data, state=self._state)
class User(BaseUser, discord.abc.Messageable):
"""Represents a Discord user.
.. container:: operations
.. describe:: x == y
Checks if two users are equal.
.. describe:: x != y
Checks if two users are not equal.
.. describe:: hash(x)
Return the user's hash.
.. describe:: str(x)
Returns the user's name with discriminator.
Attributes
-----------
name: :class:`str`
The user's username.
id: :class:`int`
The user's unique ID.
discriminator: :class:`str`
The user's discriminator. This is given when the username has conflicts.
avatar: Optional[:class:`str`]
The avatar hash the user has. Could be None.
bot: :class:`bool`
Specifies if the user is a bot account.
"""
__slots__ = ("__weakref__",)
def __repr__(self):
return "<User id={0.id} name={0.name!r} discriminator={0.discriminator!r} bot={0.bot}>".format(
self
)
async def _get_channel(self):
ch = await self.create_dm()
return ch
@property
def dm_channel(self):
"""Returns the :class:`DMChannel` associated with this user if it exists.
If this returns ``None``, you can create a DM channel by calling the
:meth:`create_dm` coroutine function.
"""
return self._state._get_private_channel_by_user(self.id)
async def create_dm(self):
"""Creates a :class:`DMChannel` with this user.
This should be rarely called, as this is done transparently for most
people.
"""
found = self.dm_channel
if found is not None:
return found
state = self._state
data = await state.http.start_private_message(self.id)
return state.add_dm_channel(data)
@property
def relationship(self):
"""Returns the :class:`Relationship` with this user if applicable, ``None`` otherwise."""
return self._state.user.get_relationship(self.id)
async def mutual_friends(self):
"""|coro|
Gets all mutual friends of this user. This can only be used by non-bot accounts
Returns
-------
List[:class:`User`]
The users that are mutual friends.
Raises
-------
Forbidden
Not allowed to get mutual friends of this user.
HTTPException
Getting mutual friends failed.
"""
state = self._state
mutuals = await state.http.get_mutual_friends(self.id)
return [User(state=state, data=friend) for friend in mutuals]
def is_friend(self):
""":class:`bool`: Checks if the user is your friend."""
r = self.relationship
if r is None:
return False
return r.type is RelationshipType.friend
def is_blocked(self):
""":class:`bool`: Checks if the user is blocked."""
r = self.relationship
if r is None:
return False
return r.type is RelationshipType.blocked
async def block(self):
"""|coro|
Blocks the user.
Raises
-------
Forbidden
Not allowed to block this user.
HTTPException
Blocking the user failed.
"""
await self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value)
async def unblock(self):
"""|coro|
Unblocks the user.
Raises
-------
Forbidden
Not allowed to unblock this user.
HTTPException
Unblocking the user failed.
"""
await self._state.http.remove_relationship(self.id)
async def remove_friend(self):
"""|coro|
Removes the user as a friend.
Raises
-------
Forbidden
Not allowed to remove this user as a friend.
HTTPException
Removing the user as a friend failed.
"""
await self._state.http.remove_relationship(self.id)
async def send_friend_request(self):
"""|coro|
Sends the user a friend request.
Raises
-------
Forbidden
Not allowed to send a friend request to the user.
HTTPException
Sending the friend request failed.
"""
await self._state.http.send_friend_request(
username=self.name, discriminator=self.discriminator
)
async def profile(self):
"""|coro|
Gets the user's profile. This can only be used by non-bot accounts.
Raises
-------
Forbidden
Not allowed to fetch profiles.
HTTPException
Fetching the profile failed.
Returns
--------
:class:`Profile`
The profile of the user.
"""
state = self._state
data = await state.http.get_user_profile(self.id)
def transform(d):
return state._get_guild(int(d["id"]))
since = data.get("premium_since")
mutual_guilds = list(filter(None, map(transform, data.get("mutual_guilds", []))))
return Profile(
flags=data["user"].get("flags", 0),
premium_since=parse_time(since),
mutual_guilds=mutual_guilds,
user=self,
connected_accounts=data["connected_accounts"],
)

View File

@@ -1,353 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import array
import asyncio
from base64 import b64encode
from bisect import bisect_left
import datetime
from email.utils import parsedate_to_datetime
import functools
from inspect import isawaitable as _isawaitable
import json
from re import split as re_split
import warnings
from .errors import InvalidArgument
DISCORD_EPOCH = 1420070400000
class cached_property:
def __init__(self, function):
self.function = function
self.__doc__ = getattr(function, "__doc__")
def __get__(self, instance, owner):
if instance is None:
return self
value = self.function(instance)
setattr(instance, self.function.__name__, value)
return value
class CachedSlotProperty:
def __init__(self, name, function):
self.name = name
self.function = function
self.__doc__ = getattr(function, "__doc__")
def __get__(self, instance, owner):
if instance is None:
return self
try:
return getattr(instance, self.name)
except AttributeError:
value = self.function(instance)
setattr(instance, self.name, value)
return value
def cached_slot_property(name):
def decorator(func):
return CachedSlotProperty(name, func)
return decorator
def parse_time(timestamp):
if timestamp:
return datetime.datetime(*map(int, re_split(r"[^\d]", timestamp.replace("+00:00", ""))))
return None
def deprecated(instead=None):
def actual_decorator(func):
@functools.wraps(func)
def decorated(*args, **kwargs):
warnings.simplefilter("always", DeprecationWarning) # turn off filter
if instead:
fmt = "{0.__name__} is deprecated, use {1} instead."
else:
fmt = "{0.__name__} is deprecated."
warnings.warn(fmt.format(func, instead), stacklevel=3, category=DeprecationWarning)
warnings.simplefilter("default", DeprecationWarning) # reset filter
return func(*args, **kwargs)
return decorated
return actual_decorator
def oauth_url(client_id, permissions=None, guild=None, redirect_uri=None):
"""A helper function that returns the OAuth2 URL for inviting the bot
into guilds.
Parameters
-----------
client_id : str
The client ID for your bot.
permissions : :class:`Permissions`
The permissions you're requesting. If not given then you won't be requesting any
permissions.
guild : :class:`Guild`
The guild to pre-select in the authorization screen, if available.
redirect_uri : str
An optional valid redirect URI.
"""
url = "https://discordapp.com/oauth2/authorize?client_id={}&scope=bot".format(client_id)
if permissions is not None:
url = url + "&permissions=" + str(permissions.value)
if guild is not None:
url = url + "&guild_id=" + str(guild.id)
if redirect_uri is not None:
from urllib.parse import urlencode
url = url + "&response_type=code&" + urlencode({"redirect_uri": redirect_uri})
return url
def snowflake_time(id):
"""Returns the creation date in UTC of a discord id."""
return datetime.datetime.utcfromtimestamp(((id >> 22) + DISCORD_EPOCH) / 1000)
def time_snowflake(datetime_obj, high=False):
"""Returns a numeric snowflake pretending to be created at the given date.
When using as the lower end of a range, use time_snowflake(high=False) - 1 to be inclusive, high=True to be exclusive
When using as the higher end of a range, use time_snowflake(high=True) + 1 to be inclusive, high=False to be exclusive
Parameters
-----------
datetime_obj
A timezone-naive datetime object representing UTC time.
high
Whether or not to set the lower 22 bit to high or low.
"""
unix_seconds = (datetime_obj - type(datetime_obj)(1970, 1, 1)).total_seconds()
discord_millis = int(unix_seconds * 1000 - DISCORD_EPOCH)
return (discord_millis << 22) + (2 ** 22 - 1 if high else 0)
def find(predicate, seq):
"""A helper to return the first element found in the sequence
that meets the predicate. For example: ::
member = find(lambda m: m.name == 'Mighty', channel.guild.members)
would find the first :class:`Member` whose name is 'Mighty' and return it.
If an entry is not found, then ``None`` is returned.
This is different from `filter`_ due to the fact it stops the moment it finds
a valid entry.
.. _filter: https://docs.python.org/3.6/library/functions.html#filter
Parameters
-----------
predicate
A function that returns a boolean-like result.
seq : iterable
The iterable to search through.
"""
for element in seq:
if predicate(element):
return element
return None
def get(iterable, **attrs):
r"""A helper that returns the first element in the iterable that meets
all the traits passed in ``attrs``. This is an alternative for
:func:`discord.utils.find`.
When multiple attributes are specified, they are checked using
logical AND, not logical OR. Meaning they have to meet every
attribute passed in and not one of them.
To have a nested attribute search (i.e. search by ``x.y``) then
pass in ``x__y`` as the keyword argument.
If nothing is found that matches the attributes passed, then
``None`` is returned.
Examples
---------
Basic usage:
.. code-block:: python3
member = discord.utils.get(message.guild.members, name='Foo')
Multiple attribute matching:
.. code-block:: python3
channel = discord.utils.get(guild.voice_channels, name='Foo', bitrate=64000)
Nested attribute matching:
.. code-block:: python3
channel = discord.utils.get(client.get_all_channels(), guild__name='Cool', name='general')
Parameters
-----------
iterable
An iterable to search through.
\*\*attrs
Keyword arguments that denote attributes to search with.
"""
def predicate(elem):
for attr, val in attrs.items():
nested = attr.split("__")
obj = elem
for attribute in nested:
obj = getattr(obj, attribute)
if obj != val:
return False
return True
return find(predicate, iterable)
def _unique(iterable):
seen = set()
adder = seen.add
return [x for x in iterable if not (x in seen or adder(x))]
def _get_as_snowflake(data, key):
try:
value = data[key]
except KeyError:
return None
else:
return value and int(value)
def _get_mime_type_for_image(data):
if data.startswith(b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"):
return "image/png"
elif data.startswith(b"\xFF\xD8") and data.rstrip(b"\0").endswith(b"\xFF\xD9"):
return "image/jpeg"
elif data.startswith(b"\x47\x49\x46\x38\x37\x61") or data.startswith(
b"\x47\x49\x46\x38\x39\x61"
):
return "image/gif"
elif data.startswith(b"RIFF") and data[8:12] == b"WEBP":
return "image/webp"
else:
raise InvalidArgument("Unsupported image type given")
def _bytes_to_base64_data(data):
fmt = "data:{mime};base64,{data}"
mime = _get_mime_type_for_image(data)
b64 = b64encode(data).decode("ascii")
return fmt.format(mime=mime, data=b64)
def to_json(obj):
return json.dumps(obj, separators=(",", ":"), ensure_ascii=True)
def _parse_ratelimit_header(request):
now = parsedate_to_datetime(request.headers["Date"])
reset = datetime.datetime.fromtimestamp(
int(request.headers["X-Ratelimit-Reset"]), datetime.timezone.utc
)
return (reset - now).total_seconds()
async def maybe_coroutine(f, *args, **kwargs):
value = f(*args, **kwargs)
if _isawaitable(value):
return await value
else:
return value
async def async_all(gen, *, check=_isawaitable):
for elem in gen:
if check(elem):
elem = await elem
if not elem:
return False
return True
async def sane_wait_for(futures, *, timeout, loop):
_, pending = await asyncio.wait(futures, timeout=timeout, loop=loop)
if len(pending) != 0:
raise asyncio.TimeoutError()
def valid_icon_size(size):
"""Icons must be power of 2 within [16, 2048]."""
return not size & (size - 1) and size in range(16, 2049)
class SnowflakeList(array.array):
"""Internal data storage class to efficiently store a list of snowflakes.
This should have the following characteristics:
- Low memory usage
- O(n) iteration (obviously)
- O(n log n) initial creation if data is unsorted
- O(log n) search and indexing
- O(n) insertion
"""
__slots__ = ()
def __new__(cls, data, *, is_sorted=False):
return array.array.__new__(cls, "Q", data if is_sorted else sorted(data))
def add(self, element):
i = bisect_left(self, element)
self.insert(i, element)
def get(self, element):
i = bisect_left(self, element)
return self[i] if i != len(self) and self[i] == element else None
def has(self, element):
i = bisect_left(self, element)
return i != len(self) and self[i] == element

View File

@@ -1,438 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
"""Some documentation to refer to:
- Our main web socket (mWS) sends opcode 4 with a guild ID and channel ID.
- The mWS receives VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE.
- We pull the session_id from VOICE_STATE_UPDATE.
- We pull the token, endpoint and server_id from VOICE_SERVER_UPDATE.
- Then we initiate the voice web socket (vWS) pointing to the endpoint.
- We send opcode 0 with the user_id, server_id, session_id and token using the vWS.
- The vWS sends back opcode 2 with an ssrc, port, modes(array) and hearbeat_interval.
- We send a UDP discovery packet to endpoint:port and receive our IP and our port in LE.
- Then we send our IP and port via vWS with opcode 1.
- When that's all done, we receive opcode 4 from the vWS.
- Finally we can transmit data to endpoint:port.
"""
import asyncio
import socket
import logging
import struct
import threading
from . import opus
from .backoff import ExponentialBackoff
from .gateway import *
from .errors import ClientException, ConnectionClosed
from .player import AudioPlayer, AudioSource
try:
import nacl.secret
has_nacl = True
except ImportError:
has_nacl = False
log = logging.getLogger(__name__)
class VoiceClient:
"""Represents a Discord voice connection.
You do not create these, you typically get them from
e.g. :meth:`VoiceChannel.connect`.
Warning
--------
In order to play audio, you must have loaded the opus library
through :func:`opus.load_opus`.
If you don't do this then the library will not be able to
transmit audio.
Attributes
-----------
session_id: :class:`str`
The voice connection session ID.
token: :class:`str`
The voice connection token.
endpoint: :class:`str`
The endpoint we are connecting to.
channel: :class:`abc.Connectable`
The voice channel connected to.
loop
The event loop that the voice client is running on.
"""
def __init__(self, state, timeout, channel):
if not has_nacl:
raise RuntimeError("PyNaCl library needed in order to use voice")
self.channel = channel
self.main_ws = None
self.timeout = timeout
self.ws = None
self.socket = None
self.loop = state.loop
self._state = state
# this will be used in the AudioPlayer thread
self._connected = threading.Event()
self._handshake_complete = asyncio.Event(loop=self.loop)
self._connections = 0
self.sequence = 0
self.timestamp = 0
self._runner = None
self._player = None
self.encoder = opus.Encoder()
warn_nacl = not has_nacl
@property
def guild(self):
"""Optional[:class:`Guild`]: The guild we're connected to, if applicable."""
return getattr(self.channel, "guild", None)
@property
def user(self):
""":class:`ClientUser`: The user connected to voice (i.e. ourselves)."""
return self._state.user
def checked_add(self, attr, value, limit):
val = getattr(self, attr)
if val + value > limit:
setattr(self, attr, 0)
else:
setattr(self, attr, val + value)
# connection related
async def start_handshake(self):
log.info("Starting voice handshake...")
guild_id, channel_id = self.channel._get_voice_state_pair()
state = self._state
self.main_ws = ws = state._get_websocket(guild_id)
self._connections += 1
# request joining
await ws.voice_state(guild_id, channel_id)
try:
await asyncio.wait_for(
self._handshake_complete.wait(), timeout=self.timeout, loop=self.loop
)
except asyncio.TimeoutError:
await self.terminate_handshake(remove=True)
raise
log.info(
"Voice handshake complete. Endpoint found %s (IP: %s)", self.endpoint, self.endpoint_ip
)
async def terminate_handshake(self, *, remove=False):
guild_id, channel_id = self.channel._get_voice_state_pair()
self._handshake_complete.clear()
await self.main_ws.voice_state(guild_id, None, self_mute=True)
log.info(
"The voice handshake is being terminated for Channel ID %s (Guild ID %s)",
channel_id,
guild_id,
)
if remove:
log.info(
"The voice client has been removed for Channel ID %s (Guild ID %s)",
channel_id,
guild_id,
)
key_id, _ = self.channel._get_voice_client_key()
self._state._remove_voice_client(key_id)
async def _create_socket(self, server_id, data):
self._connected.clear()
self.session_id = self.main_ws.session_id
self.server_id = server_id
self.token = data.get("token")
endpoint = data.get("endpoint")
if endpoint is None or self.token is None:
log.warning(
"Awaiting endpoint... This requires waiting. "
"If timeout occurred considering raising the timeout and reconnecting."
)
return
self.endpoint = endpoint.replace(":80", "")
self.endpoint_ip = socket.gethostbyname(self.endpoint)
if self.socket:
try:
self.socket.close()
except Exception:
pass
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.setblocking(False)
if self._handshake_complete.is_set():
# terminate the websocket and handle the reconnect loop if necessary.
self._handshake_complete.clear()
await self.ws.close(4000)
return
self._handshake_complete.set()
async def connect(self, *, reconnect=True, _tries=0, do_handshake=True):
log.info("Connecting to voice...")
try:
del self.secret_key
except AttributeError:
pass
if do_handshake:
await self.start_handshake()
try:
self.ws = await DiscordVoiceWebSocket.from_client(self)
self._connected.clear()
while not hasattr(self, "secret_key"):
await self.ws.poll_event()
self._connected.set()
except (ConnectionClosed, asyncio.TimeoutError):
if reconnect and _tries < 5:
log.exception("Failed to connect to voice... Retrying...")
await asyncio.sleep(1 + _tries * 2.0, loop=self.loop)
await self.terminate_handshake()
await self.connect(reconnect=reconnect, _tries=_tries + 1)
else:
raise
if self._runner is None:
self._runner = self.loop.create_task(self.poll_voice_ws(reconnect))
async def poll_voice_ws(self, reconnect):
backoff = ExponentialBackoff()
while True:
try:
await self.ws.poll_event()
except (ConnectionClosed, asyncio.TimeoutError) as exc:
if isinstance(exc, ConnectionClosed):
if exc.code == 1000:
await self.disconnect()
break
if not reconnect:
await self.disconnect()
raise
retry = backoff.delay()
log.exception("Disconnected from voice... Reconnecting in %.2fs.", retry)
self._connected.clear()
await asyncio.sleep(retry, loop=self.loop)
await self.terminate_handshake()
try:
await self.connect(reconnect=True)
except asyncio.TimeoutError:
# at this point we've retried 5 times... let's continue the loop.
log.warning("Could not connect to voice... Retrying...")
continue
async def disconnect(self, *, force=False):
"""|coro|
Disconnects this voice client from voice.
"""
if not force and not self._connected.is_set():
return
self.stop()
self._connected.clear()
try:
if self.ws:
await self.ws.close()
await self.terminate_handshake(remove=True)
finally:
if self.socket:
self.socket.close()
async def move_to(self, channel):
"""|coro|
Moves you to a different voice channel.
Parameters
-----------
channel: :class:`abc.Snowflake`
The channel to move to. Must be a voice channel.
"""
guild_id, _ = self.channel._get_voice_state_pair()
await self.main_ws.voice_state(guild_id, channel.id)
def is_connected(self):
""":class:`bool`: Indicates if the voice client is connected to voice."""
return self._connected.is_set()
# audio related
def _get_voice_packet(self, data):
header = bytearray(12)
nonce = bytearray(24)
box = nacl.secret.SecretBox(bytes(self.secret_key))
# Formulate header
header[0] = 0x80
header[1] = 0x78
struct.pack_into(">H", header, 2, self.sequence)
struct.pack_into(">I", header, 4, self.timestamp)
struct.pack_into(">I", header, 8, self.ssrc)
# Copy header to nonce's first 12 bytes
nonce[:12] = header
# Encrypt and return the data
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext
def play(self, source, *, after=None):
"""Plays an :class:`AudioSource`.
The finalizer, ``after`` is called after the source has been exhausted
or an error occurred.
If an error happens while the audio player is running, the exception is
caught and the audio player is then stopped.
Parameters
-----------
source: :class:`AudioSource`
The audio source we're reading from.
after
The finalizer that is called after the stream is exhausted.
All exceptions it throws are silently discarded. This function
must have a single parameter, ``error``, that denotes an
optional exception that was raised during playing.
Raises
-------
ClientException
Already playing audio or not connected.
TypeError
source is not a :class:`AudioSource` or after is not a callable.
"""
if not self._connected:
raise ClientException("Not connected to voice.")
if self.is_playing():
raise ClientException("Already playing audio.")
if not isinstance(source, AudioSource):
raise TypeError("source must an AudioSource not {0.__class__.__name__}".format(source))
self._player = AudioPlayer(source, self, after=after)
self._player.start()
def is_playing(self):
"""Indicates if we're currently playing audio."""
return self._player is not None and self._player.is_playing()
def is_paused(self):
"""Indicates if we're playing audio, but if we're paused."""
return self._player is not None and self._player.is_paused()
def stop(self):
"""Stops playing audio."""
if self._player:
self._player.stop()
self._player = None
def pause(self):
"""Pauses the audio playing."""
if self._player:
self._player.pause()
def resume(self):
"""Resumes the audio playing."""
if self._player:
self._player.resume()
@property
def source(self):
"""Optional[:class:`AudioSource`]: The audio source being played, if playing.
This property can also be used to change the audio source currently being played.
"""
return self._player.source if self._player else None
@source.setter
def source(self, value):
if not isinstance(value, AudioSource):
raise TypeError("expected AudioSource not {0.__class__.__name__}.".format(value))
if self._player is None:
raise ValueError("Not playing anything.")
self._player._set_source(value)
def send_audio_packet(self, data, *, encode=True):
"""Sends an audio packet composed of the data.
You must be connected to play audio.
Parameters
----------
data: bytes
The :term:`py:bytes-like object` denoting PCM or Opus voice data.
encode: bool
Indicates if ``data`` should be encoded into Opus.
Raises
-------
ClientException
You are not connected.
OpusError
Encoding the data failed.
"""
self.checked_add("sequence", 1, 65535)
if encode:
encoded_data = self.encoder.encode(data, self.encoder.SAMPLES_PER_FRAME)
else:
encoded_data = data
packet = self._get_voice_packet(encoded_data)
try:
self.socket.sendto(packet, (self.endpoint_ip, self.voice_port))
except BlockingIOError:
log.warning(
"A packet has been dropped (seq: %s, timestamp: %s)", self.sequence, self.timestamp
)
self.checked_add("timestamp", self.encoder.SAMPLES_PER_FRAME, 4294967295)

View File

@@ -1,703 +0,0 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2017 Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import asyncio
import json
import time
import re
import aiohttp
from . import utils
from .errors import InvalidArgument, HTTPException, Forbidden, NotFound
from .user import BaseUser, User
__all__ = ["WebhookAdapter", "AsyncWebhookAdapter", "RequestsWebhookAdapter", "Webhook"]
class WebhookAdapter:
"""Base class for all webhook adapters.
Attributes
------------
webhook: :class:`Webhook`
The webhook that owns this adapter.
"""
BASE = "https://discordapp.com/api/v7"
def _prepare(self, webhook):
self._webhook_id = webhook.id
self._webhook_token = webhook.token
self._request_url = "{0.BASE}/webhooks/{1}/{2}".format(self, webhook.id, webhook.token)
self.webhook = webhook
def request(self, verb, url, payload=None, multipart=None):
"""Actually does the request.
Subclasses must implement this.
Parameters
-----------
verb: str
The HTTP verb to use for the request.
url: str
The URL to send the request to. This will have
the query parameters already added to it, if any.
multipart: Optional[dict]
A dict containing multipart form data to send with
the request. If a filename is being uploaded, then it will
be under a ``file`` key which will have a 3-element :class:`tuple`
denoting ``(filename, file, content_type)``.
payload: Optional[dict]
The JSON to send with the request, if any.
"""
raise NotImplementedError()
def delete_webhook(self):
return self.request("DELETE", self._request_url)
def edit_webhook(self, **payload):
return self.request("PATCH", self._request_url, payload=payload)
def handle_execution_response(self, data, *, wait):
"""Transforms the webhook execution response into something
more meaningful.
This is mainly used to convert the data into a :class:`Message`
if necessary.
Subclasses must implement this.
Parameters
------------
data
The data that was returned from the request.
wait: bool
Whether the webhook execution was asked to wait or not.
"""
raise NotImplementedError()
def store_user(self, data):
# mocks a ConnectionState for appropriate use for Message
return BaseUser(state=self, data=data)
def execute_webhook(self, *, payload, wait=False, file=None, files=None):
if file is not None:
multipart = {"file": file, "payload_json": utils.to_json(payload)}
data = None
elif files is not None:
multipart = {"payload_json": utils.to_json(payload)}
for i, file in enumerate(files, start=1):
multipart["file%i" % i] = file
data = None
else:
data = payload
multipart = None
url = "%s?wait=%d" % (self._request_url, wait)
maybe_coro = self.request("POST", url, multipart=multipart, payload=data)
return self.handle_execution_response(maybe_coro, wait=wait)
class AsyncWebhookAdapter(WebhookAdapter):
"""A webhook adapter suited for use with aiohttp.
.. note::
You are responsible for cleaning up the client session.
Parameters
-----------
session: aiohttp.ClientSession
The session to use to send requests.
"""
def __init__(self, session):
self.session = session
self.loop = session.loop
async def request(self, verb, url, payload=None, multipart=None):
headers = {}
data = None
if payload:
headers["Content-Type"] = "application/json"
data = utils.to_json(payload)
if multipart:
data = aiohttp.FormData()
for key, value in multipart.items():
if key.startswith("file"):
data.add_field(key, value[1], filename=value[0], content_type=value[2])
else:
data.add_field(key, value)
for tries in range(5):
async with self.session.request(verb, url, headers=headers, data=data) as r:
data = await r.text(encoding="utf-8")
if r.headers["Content-Type"] == "application/json":
data = json.loads(data)
# check if we have rate limit header information
remaining = r.headers.get("X-Ratelimit-Remaining")
if remaining == "0" and r.status != 429:
delta = utils._parse_ratelimit_header(r)
await asyncio.sleep(delta, loop=self.loop)
if 300 > r.status >= 200:
return data
# we are being rate limited
if r.status == 429:
retry_after = data["retry_after"] / 1000.0
await asyncio.sleep(retry_after, loop=self.loop)
continue
if r.status in (500, 502):
await asyncio.sleep(1 + tries * 2, loop=self.loop)
continue
if r.status == 403:
raise Forbidden(r, data)
elif r.status == 404:
raise NotFound(r, data)
else:
raise HTTPException(r, data)
async def handle_execution_response(self, response, *, wait):
data = await response
if not wait:
return data
# transform into Message object
from .message import Message
return Message(data=data, state=self, channel=self.webhook.channel)
class RequestsWebhookAdapter(WebhookAdapter):
"""A webhook adapter suited for use with ``requests``.
Only versions of requests higher than 2.13.0 are supported.
Parameters
-----------
session: Optional[`requests.Session <http://docs.python-requests.org/en/latest/api/#requests.Session>`_]
The requests session to use for sending requests. If not given then
each request will create a new session. Note if a session is given,
the webhook adapter **will not** clean it up for you. You must close
the session yourself.
sleep: bool
Whether to sleep the thread when encountering a 429 or pre-emptive
rate limit or a 5xx status code. Defaults to ``True``. If set to
``False`` then this will raise an :exc:`HTTPException` instead.
"""
def __init__(self, session=None, *, sleep=True):
import requests
self.session = session or requests
self.sleep = sleep
def request(self, verb, url, payload=None, multipart=None):
headers = {}
data = None
if payload:
headers["Content-Type"] = "application/json"
data = utils.to_json(payload)
if multipart is not None:
data = {"payload_json": multipart.pop("payload_json")}
for tries in range(5):
r = self.session.request(verb, url, headers=headers, data=data, files=multipart)
r.encoding = "utf-8"
data = r.text
# compatibility with aiohttp
r.status = r.status_code
if r.headers["Content-Type"] == "application/json":
data = json.loads(data)
# check if we have rate limit header information
remaining = r.headers.get("X-Ratelimit-Remaining")
if remaining == "0" and r.status != 429 and self.sleep:
delta = utils._parse_ratelimit_header(r)
time.sleep(delta)
if 300 > r.status >= 200:
return data
# we are being rate limited
if r.status == 429:
if self.sleep:
retry_after = data["retry_after"] / 1000.0
time.sleep(retry_after)
continue
else:
raise HTTPException(r, data)
if self.sleep and r.status in (500, 502):
time.sleep(1 + tries * 2)
continue
if r.status == 403:
raise Forbidden(r, data)
elif r.status == 404:
raise NotFound(r, data)
else:
raise HTTPException(r, data)
def handle_execution_response(self, response, *, wait):
if not wait:
return response
# transform into Message object
from .message import Message
return Message(data=response, state=self, channel=self.webhook.channel)
class Webhook:
"""Represents a Discord webhook.
Webhooks are a form to send messages to channels in Discord without a
bot user or authentication.
There are two main ways to use Webhooks. The first is through the ones
received by the library such as :meth:`.Guild.webhooks` and
:meth:`.TextChannel.webhooks`. The ones received by the library will
automatically have an adapter bound using the library's HTTP session.
Those webhooks will have :meth:`~.Webhook.send`, :meth:`~.Webhook.delete` and
:meth:`~.Webhook.edit` as coroutines.
The second form involves creating a webhook object manually without having
it bound to a websocket connection using the :meth:`~.Webhook.from_url` or
:meth:`~.Webhook.partial` classmethods. This form allows finer grained control
over how requests are done, allowing you to mix async and sync code using either
``aiohttp`` or ``requests``.
For example, creating a webhook from a URL and using ``aiohttp``:
.. code-block:: python3
from discord import Webhook, AsyncWebhookAdapter
import aiohttp
async def foo():
async with aiohttp.ClientSession() as session:
webhook = Webhook.from_url('url-here', adapter=AsyncWebhookAdapter(session))
await webhook.send('Hello World', username='Foo')
Or creating a webhook from an ID and token and using ``requests``:
.. code-block:: python3
import requests
from discord import Webhook, RequestsWebhookAdapter
webhook = Webhook.partial(123456, 'abcdefg', adapter=RequestsWebhookAdapter())
webhook.send('Hello World', username='Foo')
Attributes
------------
id: :class:`int`
The webhook's ID
token: :class:`str`
The authentication token of the webhook.
guild_id: Optional[:class:`int`]
The guild ID this webhook is for.
channel_id: Optional[:class:`int`]
The channel ID this webhook is for.
user: Optional[:class:`abc.User`]
The user this webhook was created by. If the webhook was
received without authentication then this will be ``None``.
name: Optional[:class:`str`]
The default name of the webhook.
avatar: Optional[:class:`str`]
The default avatar of the webhook.
"""
__slots__ = (
"id",
"guild_id",
"channel_id",
"user",
"name",
"avatar",
"token",
"_state",
"_adapter",
)
def __init__(self, data, *, adapter, state=None):
self.id = int(data["id"])
self.channel_id = utils._get_as_snowflake(data, "channel_id")
self.guild_id = utils._get_as_snowflake(data, "guild_id")
self.name = data.get("name")
self.avatar = data.get("avatar")
self.token = data["token"]
self._state = state
self._adapter = adapter
self._adapter._prepare(self)
user = data.get("user")
if user is None:
self.user = None
elif state is None:
self.user = BaseUser(state=None, data=user)
else:
self.user = User(state=state, data=user)
def __repr__(self):
return "<Webhook id=%r>" % self.id
@property
def url(self):
"""Returns the webhook's url."""
return "https://discordapp.com/api/webhooks/{}/{}".format(self.id, self.token)
@classmethod
def partial(cls, id, token, *, adapter):
"""Creates a partial :class:`Webhook`.
A partial webhook is just a webhook object with an ID and a token.
Parameters
-----------
id: int
The ID of the webhook.
token: str
The authentication token of the webhook.
adapter: :class:`WebhookAdapter`
The webhook adapter to use when sending requests. This is
typically :class:`AsyncWebhookAdapter` for ``aiohttp`` or
:class:`RequestsWebhookAdapter` for ``requests``.
"""
if not isinstance(adapter, WebhookAdapter):
raise TypeError("adapter must be a subclass of WebhookAdapter")
data = {"id": id, "token": token}
return cls(data, adapter=adapter)
@classmethod
def from_url(cls, url, *, adapter):
"""Creates a partial :class:`Webhook` from a webhook URL.
Parameters
------------
url: str
The URL of the webhook.
adapter: :class:`WebhookAdapter`
The webhook adapter to use when sending requests. This is
typically :class:`AsyncWebhookAdapter` for ``aiohttp`` or
:class:`RequestsWebhookAdapter` for ``requests``.
Raises
-------
InvalidArgument
The URL is invalid.
"""
m = re.search(
r"discordapp.com/api/webhooks/(?P<id>[0-9]{17,21})/(?P<token>[A-Za-z0-9\.\-\_]{60,68})",
url,
)
if m is None:
raise InvalidArgument("Invalid webhook URL given.")
return cls(m.groupdict(), adapter=adapter)
@classmethod
def from_state(cls, data, state):
return cls(data, adapter=AsyncWebhookAdapter(session=state.http._session), state=state)
@property
def guild(self):
"""Optional[:class:`Guild`]: The guild this webhook belongs to.
If this is a partial webhook, then this will always return ``None``.
"""
return self._state and self._state._get_guild(self.guild_id)
@property
def channel(self):
"""Optional[:class:`TextChannel`]: The text channel this webhook belongs to.
If this is a partial webhook, then this will always return ``None``.
"""
guild = self.guild
return guild and guild.get_channel(self.channel_id)
@property
def created_at(self):
"""Returns the webhook's creation time in UTC."""
return utils.snowflake_time(self.id)
@property
def avatar_url(self):
"""Returns a friendly URL version of the avatar the webhook has.
If the webhook does not have a traditional avatar, their default
avatar URL is returned instead.
This is equivalent to calling :meth:`avatar_url_as` with the
default parameters.
"""
return self.avatar_url_as()
def avatar_url_as(self, *, format=None, size=1024):
"""Returns a friendly URL version of the avatar the webhook has.
If the webhook does not have a traditional avatar, their default
avatar URL is returned instead.
The format must be one of 'jpeg', 'jpg', or 'png'.
The size must be a power of 2 between 16 and 1024.
Parameters
-----------
format: Optional[str]
The format to attempt to convert the avatar to.
If the format is ``None``, then it is equivalent to png.
size: int
The size of the image to display.
Returns
--------
str
The resulting CDN URL.
Raises
------
InvalidArgument
Bad image format passed to ``format`` or invalid ``size``.
"""
if self.avatar is None:
# Default is always blurple apparently
return "https://cdn.discordapp.com/embed/avatars/0.png"
if not utils.valid_icon_size(size):
raise InvalidArgument("size must be a power of 2 between 16 and 1024")
format = format or "png"
if format not in ("png", "jpg", "jpeg"):
raise InvalidArgument("format must be one of 'png', 'jpg', or 'jpeg'.")
return "https://cdn.discordapp.com/avatars/{0.id}/{0.avatar}.{1}?size={2}".format(
self, format, size
)
def delete(self):
"""|maybecoro|
Deletes this Webhook.
If the webhook is constructed with a :class:`RequestsWebhookAdapter` then this is
not a coroutine.
Raises
-------
HTTPException
Deleting the webhook failed.
NotFound
This webhook does not exist.
Forbidden
You do not have permissions to delete this webhook.
"""
return self._adapter.delete_webhook()
def edit(self, **kwargs):
"""|maybecoro|
Edits this Webhook.
If the webhook is constructed with a :class:`RequestsWebhookAdapter` then this is
not a coroutine.
Parameters
-------------
name: Optional[str]
The webhook's new default name.
avatar: Optional[bytes]
A :term:`py:bytes-like object` representing the webhook's new default avatar.
Raises
-------
HTTPException
Editing the webhook failed.
NotFound
This webhook does not exist.
Forbidden
You do not have permissions to edit this webhook.
"""
payload = {}
try:
name = kwargs["name"]
except KeyError:
pass
else:
if name is not None:
payload["name"] = str(name)
else:
payload["name"] = None
try:
avatar = kwargs["avatar"]
except KeyError:
pass
else:
if avatar is not None:
payload["avatar"] = utils._bytes_to_base64_data(avatar)
else:
payload["avatar"] = None
return self._adapter.edit_webhook(**payload)
def send(
self,
content=None,
*,
wait=False,
username=None,
avatar_url=None,
tts=False,
file=None,
files=None,
embed=None,
embeds=None
):
"""|maybecoro|
Sends a message using the webhook.
If the webhook is constructed with a :class:`RequestsWebhookAdapter` then this is
not a coroutine.
The content must be a type that can convert to a string through ``str(content)``.
To upload a single file, the ``file`` parameter should be used with a
single :class:`File` object.
If the ``embed`` parameter is provided, it must be of type :class:`Embed` and
it must be a rich embed type. You cannot mix the ``embed`` parameter with the
``embeds`` parameter, which must be a :class:`list` of :class:`Embed` objects to send.
Parameters
------------
content
The content of the message to send.
wait: bool
Whether the server should wait before sending a response. This essentially
means that the return type of this function changes from ``None`` to
a :class:`Message` if set to ``True``.
username: str
The username to send with this message. If no username is provided
then the default username for the webhook is used.
avatar_url: str
The avatar URL to send with this message. If no avatar URL is provided
then the default avatar for the webhook is used.
tts: bool
Indicates if the message should be sent using text-to-speech.
file: :class:`File`
The file to upload. This cannot be mixed with ``files`` parameter.
files: List[:class:`File`]
A list of files to send with the content. This cannot be mixed with the
``file`` parameter.
embed: :class:`Embed`
The rich embed for the content to send. This cannot be mixed with
``embeds`` parameter.
embeds: List[:class:`Embed`]
A list of embeds to send with the content. Maximum of 10. This cannot
be mixed with the ``embed`` parameter.
Raises
--------
HTTPException
Sending the message failed.
NotFound
This webhook was not found.
Forbidden
The authorization token for the webhook is incorrect.
InvalidArgument
You specified both ``embed`` and ``embeds`` or the length of
``embeds`` was invalid.
Returns
---------
Optional[:class:`Message`]
The message that was sent.
"""
payload = {}
if files is not None and file is not None:
raise InvalidArgument("Cannot mix file and files keyword arguments.")
if embeds is not None and embed is not None:
raise InvalidArgument("Cannot mix embed and embeds keyword arguments.")
if embeds is not None:
if len(embeds) > 10:
raise InvalidArgument("embeds has a maximum of 10 elements.")
payload["embeds"] = [e.to_dict() for e in embeds]
if embed is not None:
payload["embeds"] = [embed.to_dict()]
if content is not None:
payload["content"] = str(content)
payload["tts"] = tts
if avatar_url:
payload["avatar_url"] = avatar_url
if username:
payload["username"] = username
if file is not None:
try:
to_pass = (file.filename, file.open_file(), "application/octet-stream")
return self._adapter.execute_webhook(wait=wait, file=to_pass, payload=payload)
finally:
file.close()
elif files is not None:
try:
to_pass = [
(file.filename, file.open_file(), "application/octet-stream") for file in files
]
return self._adapter.execute_webhook(wait=wait, files=to_pass, payload=payload)
finally:
for file in files:
file.close()
else:
return self._adapter.execute_webhook(wait=wait, payload=payload)
def execute(self, *args, **kwargs):
"""An alias for :meth:`~.Webhook.send`."""
return self.send(*args, **kwargs)

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -3,7 +3,7 @@
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = python -msphinx
SPHINXBUILD = python3 -msphinx
SPHINXPROJ = Red-DiscordBot
SOURCEDIR = .
BUILDDIR = _build
@@ -17,4 +17,4 @@ help:
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

18
docs/_templates/layout.html vendored Normal file
View File

@@ -0,0 +1,18 @@
{% extends '!layout.html' %}
{% block document %}
{% if version_slug == 'latest' %}
<div class="admonition warning">
<p class="first admonition-title">Warning</p>
<p class="last">
This document is for Red's development version, which can be significantly different from previous releases.
If you're a regular user, you should read the <a href="{{ dict(versions)['stable'] }}">Red documentation for the current stable release</a>.
</p>
</div>
{% endif %}
{{ super() }}
<a href="https://github.com/Cog-Creators/Red-DiscordBot">
<img style="position: absolute; top: 0; right: 0; border: 0;"
src="https://github.blog/wp-content/uploads/2008/12/forkme_right_darkblue_121621.png?resize=149%2C149"
class="attachment-full size-full" alt="Fork me on GitHub">
</a>
{% endblock %}

39
docs/about_venv.rst Normal file
View File

@@ -0,0 +1,39 @@
.. _about-venvs:
==========================
About Virtual Environments
==========================
Creating a virtual environment is really easy and usually prevents many common installation
problems.
**What Are Virtual Environments For?**
Virtual environments allow you to isolate Red's library dependencies, cog dependencies and python
binaries from the rest of your system. There is no performance overhead to using virtual environment
and it saves you from a lot of troubles during setup. It also makes sure Red and its dependencies
are installed to a predictable location which makes uninstalling Red as simple as removing a single folder,
without worrying about losing your data or other things on your system becoming broken.
--------------------------------------------
Virtual Environments with Multiple Instances
--------------------------------------------
If you are running multiple instances of Red on the same machine, you have the option of either
using the same virtual environment for all of them, or creating separate ones.
.. note::
This only applies for multiple instances of V3. If you are running a V2 instance as well,
you **must** use separate virtual environments.
The advantages of using a *single* virtual environment for all of your V3 instances are:
- When updating Red, you will only need to update it once for all instances (however you will still need to restart all instances for the changes to take effect)
- It will save space on your hard drive
On the other hand, you may wish to update each of your instances individually.
.. important::
Windows users with multiple instances should create *separate* virtual environments, as
updating multiple running instances at once is likely to cause errors.

44
docs/autostart_pm2.rst Normal file
View File

@@ -0,0 +1,44 @@
.. pm2 service guide
==============================================
Setting up auto-restart using pm2 on Linux
==============================================
.. note:: This guide is for setting up PM2 on a Linux environment. This guide assumes that you already have a working Red instance.
--------------
Installing PM2
--------------
Start by installing Node.JS and NPM via your favorite package distributor. From there run the following command:
:code:`npm install pm2 -g`
After PM2 is installed, run the following command to enable your Red instance to be managed by PM2. Replace the brackets with the required information.
You can add additional Red based arguments after the instance, such as :code:`--dev`.
.. code-block:: none
pm2 start redbot --name "<Insert a name here>" --interpreter "<Location to your Python Interpreter>" --interpreter-args "-O" -- <Red Instance> --no-prompt
.. code-block:: none
Arguments to replace.
<Insert a name here>
A name to identify the bot within pm2, this is not your Red instance.
<Location to your Python Interpreter>
The location of your Python interpreter, to find out where that is use the following command inside activated venv:
which python
<Red Instance>
The name of your Red instance.
------------------------------
Ensuring that PM2 stays online
------------------------------
To make sure that PM2 stays online and persistence between machine restarts, run the following commands:
:code:`pm2 save` & :code:`pm2 startup`

View File

@@ -1,4 +1,4 @@
.. systemd service guide
.. _systemd-service-guide:
==============================================
Setting up auto-restart using systemd on Linux
@@ -8,26 +8,49 @@ Setting up auto-restart using systemd on Linux
Creating the service file
-------------------------
Create the new service file:
In order to create the service file, you will first need to know two things, your Linux :code:`username` and your Python :code:`path`
First, your Linux :code:`username` can be fetched with the following command:
.. code-block:: bash
whoami
Next, your python :code:`path` can be fetched with the following commands:
.. code-block:: bash
# If redbot is installed in a venv
source ~/redenv/bin/activate
which python
# If redbot is installed in a pyenv virtualenv
pyenv shell <virtualenv_name>
pyenv which python
Then create the new service file:
:code:`sudo -e /etc/systemd/system/red@.service`
Paste the following and replace all instances of :code:`username` with the username your bot is running under (hopefully not root):
Paste the following in the file, and replace all instances of :code:`username` with the Linux username you retrieved above, and :code:`path` with the python path you retrieved above.
.. code-block:: none
[Unit]
Description=%I redbot
After=multi-user.target
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/home/username/.local/bin/redbot %I --no-prompt
ExecStart=path -O -m redbot %I --no-prompt
User=username
Group=username
Type=idle
Restart=always
RestartSec=15
RestartPreventExitStatus=0
TimeoutStopSec=10
[Install]
WantedBy=multi-user.target
@@ -48,6 +71,14 @@ To set the bot to start on boot, you must enable the service, again adding the i
:code:`sudo systemctl enable red@instancename`
If you need to shutdown the bot, you can use the ``[p]shutdown`` command or
type the following command in the terminal, still by adding the instance name after the **@**:
:code:`sudo systemctl stop red@instancename`
.. warning:: If the service doesn't stop in the next 10 seconds, the process is killed.
Check your logs to know the cause of the error that prevents the shutdown.
To view Reds log, you can acccess through journalctl:
:code:`sudo journalctl -u red@instancename`
:code:`sudo journalctl -eu red@instancename`

232
docs/changelog_3_1_0.rst Normal file
View File

@@ -0,0 +1,232 @@
.. v3.1.0 Changelog
####################
v3.1.0 Release Notes
####################
----------------------
Mongo Driver Migration
----------------------
Due to the required changes of the Mongo driver for Config, all existing Mongo users will need to
complete the below instructions to continue to use Mongo after updating to 3.1.
This includes **all** users, regardless of any prior migration attempt to a development version of
3.1.
#. Upgrade to 3.1
#. Convert all existing Mongo instances to JSON using the new converters
#. Start each bot instance while using JSON and load any and all cogs you have in order to successfully preserve data.
#. Turn each instance off and convert back to Mongo.
**NOTE:** No data is wiped from your Mongo database when converting to JSON.
You may want to use a *new* database name when converting back to Mongo in order to not have duplicate data.
-------------
Setup Utility
-------------
New commands were introduced to simplify the conversion/editing/removal process both on our end and the users end.
Please use ``redbot-setup --help`` to learn how to use the new features.
.. HINT::
Converting to JSON: ``redbot-setup convert <instance_name> json``
Converting to Mongo: ``redbot-setup convert <instance_name> mongo``
################
v3.1.0 Changelog
################
-----
Audio
-----
* Add Spotify support (`#2328`_)
* Play local folders via text command (`#2457`_)
* Change pause to a toggle (`#2461`_)
* Remove aliases (`#2462`_)
* Add track length restriction (`#2465`_)
* Seek command can now seek to position (`#2470`_)
* Add option for dc at queue end (`#2472`_)
* Emptydisconnect and status refactor (`#2473`_)
* Queue clean and queue clear addition (`#2476`_)
* Fix for audioset status (`#2481`_)
* Playlist download addition (`#2482`_)
* Add songs when search-queuing (`#2513`_)
* Match v2 behavior for channel change (`#2521`_)
* Bot will no longer complain about permissions when trying to connect to user-limited channel, if it has "Move Members" permission (`#2525`_)
* Fix issue on audiostats command when more than 20 servers to display (`#2533`_)
* Fix for prev command display (`#2556`_)
* Fix for localtrack playing (`#2557`_)
* Fix for playlist queue when not playing (`#2586`_)
* Track search and append fixes (`#2591`_)
* DJ role should ask for a role (`#2606`_)
----
Core
----
* Warn on usage of ``yaml.load`` (`#2326`_)
* New Event dispatch: ``on_message_without_command`` (`#2338`_)
* Improve output format of cooldown messages (`#2412`_)
* Delete cooldown messages when expired (`#2469`_)
* Fix local blacklist/whitelist management (`#2531`_)
* ``[p]set locale`` now only accepts actual locales (`#2553`_)
* ``[p]listlocales`` now displays ``en-US`` (`#2553`_)
* ``redbot --version`` will now give you current version of Red (`#2567`_)
* Redesign help and related formatter (`#2628`_)
* Default locale changed from ``en`` to ``en-US`` (`#2642`_)
* New command ``[p]datapath`` that prints the bot's datapath (`#2652`_)
------
Config
------
* Updated Mongo driver to support large guilds (`#2536`_)
* Introduced ``init_custom`` method on Config objects (`#2545`_)
* We now record custom group primary key lengths in the core config object (`#2550`_)
* Migrated internal UUIDs to maintain cross platform consistency (`#2604`_)
-------------
DataConverter
-------------
* It's dead jim (Removal) (`#2554`_)
----------
discord.py
----------
* No longer vendoring discord.py (`#2587`_)
* Upgraded discord.py dependency to version 1.0.1 (`#2587`_)
----------
Downloader
----------
* ``[p]cog install`` will now tell user that cog has to be loaded (`#2523`_)
* The message when libraries fail to install is now formatted (`#2576`_)
* Fixed bug, that caused Downloader to include submodules on cog list (`#2590`_)
* ``[p]cog uninstall`` allows to uninstall multiple cogs now (`#2592`_)
* ``[p]cog uninstall`` will now remove cog from installed cogs even if it can't find the cog in install path anymore (`#2595`_)
* ``[p]cog install`` will not allow to install cogs which aren't suitable for installed version of Red anymore (`#2605`_)
* Cog Developers now have to use ``min_bot_version`` in form of version string instead of ``bot_version`` in info.json and they can also use ``max_bot_version`` to specify maximum version of Red, more in :ref:`info-json-format`. (`#2605`_)
------
Filter
------
* Filter performs significantly better on large servers. (`#2509`_)
--------
Launcher
--------
* Fixed extras in the launcher (`#2588`_)
---
Mod
---
* Admins can now decide how many times message has to be repeated before ``deleterepeats`` removes it (`#2437`_)
* Fix: make ``[p]ban [days]`` optional as per the doc (`#2602`_)
* Added the command ``voicekick`` to kick members from a voice channel with optional mod case. (`#2639`_)
-----------
Permissions
-----------
* Removed: ``p`` alias for ``permissions`` command (`#2467`_)
-------------
Setup Scripts
-------------
* ``redbot-setup`` now uses the click CLI library (`#2579`_)
* ``redbot-setup convert`` now used to convert between libraries (`#2579`_)
* Backup support for Mongo is currently broken (`#2579`_)
-------
Streams
-------
* Add support for custom stream alert messages per guild (`#2600`_)
* Add ability to exclude rerun Twitch streams, and note rerun streams in embed status (`#2620`_)
-----
Tests
-----
* Test for ``trivia`` cog uses explicitly utf-8 encoding for checking yaml files (`#2565`_)
------
Trivia
------
* Fix of dead image link for Sao Tome and Principe in ``worldflags`` trivia (`#2540`_)
-----------------
Utility Functions
-----------------
* New: ``chat_formatting.humanize_timedelta`` (`#2412`_)
* ``Tunnel`` - Spelling correction of method name - changed ``files_from_attatch`` to ``files_from_attach`` (old name is left for backwards compatibility) (`#2496`_)
* ``Tunnel`` - fixed behavior of ``react_close()``, now when tunnel closes message will be sent to other end (`#2507`_)
* ``chat_formatting.humanize_list`` - Improved error handling of empty lists (`#2597`_)
.. _#2326: https://github.com/Cog-Creators/Red-DiscordBot/pull/2326
.. _#2328: https://github.com/Cog-Creators/Red-DiscordBot/pull/2328
.. _#2338: https://github.com/Cog-Creators/Red-DiscordBot/pull/2338
.. _#2412: https://github.com/Cog-Creators/Red-DiscordBot/pull/2412
.. _#2437: https://github.com/Cog-Creators/Red-DiscordBot/pull/2437
.. _#2457: https://github.com/Cog-Creators/Red-DiscordBot/pull/2457
.. _#2461: https://github.com/Cog-Creators/Red-DiscordBot/pull/2461
.. _#2462: https://github.com/Cog-Creators/Red-DiscordBot/pull/2462
.. _#2465: https://github.com/Cog-Creators/Red-DiscordBot/pull/2465
.. _#2467: https://github.com/Cog-Creators/Red-DiscordBot/pull/2467
.. _#2469: https://github.com/Cog-Creators/Red-DiscordBot/pull/2469
.. _#2470: https://github.com/Cog-Creators/Red-DiscordBot/pull/2470
.. _#2472: https://github.com/Cog-Creators/Red-DiscordBot/pull/2472
.. _#2473: https://github.com/Cog-Creators/Red-DiscordBot/pull/2473
.. _#2476: https://github.com/Cog-Creators/Red-DiscordBot/pull/2476
.. _#2481: https://github.com/Cog-Creators/Red-DiscordBot/pull/2481
.. _#2482: https://github.com/Cog-Creators/Red-DiscordBot/pull/2482
.. _#2496: https://github.com/Cog-Creators/Red-DiscordBot/pull/2496
.. _#2507: https://github.com/Cog-Creators/Red-DiscordBot/pull/2507
.. _#2509: https://github.com/Cog-Creators/Red-DiscordBot/pull/2509
.. _#2513: https://github.com/Cog-Creators/Red-DiscordBot/pull/2513
.. _#2521: https://github.com/Cog-Creators/Red-DiscordBot/pull/2521
.. _#2523: https://github.com/Cog-Creators/Red-DiscordBot/pull/2523
.. _#2525: https://github.com/Cog-Creators/Red-DiscordBot/pull/2525
.. _#2531: https://github.com/Cog-Creators/Red-DiscordBot/pull/2531
.. _#2533: https://github.com/Cog-Creators/Red-DiscordBot/pull/2533
.. _#2536: https://github.com/Cog-Creators/Red-DiscordBot/pull/2536
.. _#2540: https://github.com/Cog-Creators/Red-DiscordBot/pull/2540
.. _#2545: https://github.com/Cog-Creators/Red-DiscordBot/pull/2545
.. _#2550: https://github.com/Cog-Creators/Red-DiscordBot/pull/2550
.. _#2553: https://github.com/Cog-Creators/Red-DiscordBot/pull/2553
.. _#2554: https://github.com/Cog-Creators/Red-DiscordBot/pull/2554
.. _#2556: https://github.com/Cog-Creators/Red-DiscordBot/pull/2556
.. _#2557: https://github.com/Cog-Creators/Red-DiscordBot/pull/2557
.. _#2565: https://github.com/Cog-Creators/Red-DiscordBot/pull/2565
.. _#2567: https://github.com/Cog-Creators/Red-DiscordBot/pull/2567
.. _#2576: https://github.com/Cog-Creators/Red-DiscordBot/pull/2576
.. _#2579: https://github.com/Cog-Creators/Red-DiscordBot/pull/2579
.. _#2586: https://github.com/Cog-Creators/Red-DiscordBot/pull/2586
.. _#2587: https://github.com/Cog-Creators/Red-DiscordBot/pull/2587
.. _#2588: https://github.com/Cog-Creators/Red-DiscordBot/pull/2588
.. _#2590: https://github.com/Cog-Creators/Red-DiscordBot/pull/2590
.. _#2591: https://github.com/Cog-Creators/Red-DiscordBot/pull/2591
.. _#2592: https://github.com/Cog-Creators/Red-DiscordBot/pull/2592
.. _#2595: https://github.com/Cog-Creators/Red-DiscordBot/pull/2595
.. _#2597: https://github.com/Cog-Creators/Red-DiscordBot/pull/2597
.. _#2600: https://github.com/Cog-Creators/Red-DiscordBot/pull/2600
.. _#2602: https://github.com/Cog-Creators/Red-DiscordBot/pull/2602
.. _#2604: https://github.com/Cog-Creators/Red-DiscordBot/pull/2604
.. _#2605: https://github.com/Cog-Creators/Red-DiscordBot/pull/2605
.. _#2606: https://github.com/Cog-Creators/Red-DiscordBot/pull/2606
.. _#2620: https://github.com/Cog-Creators/Red-DiscordBot/pull/2620
.. _#2628: https://github.com/Cog-Creators/Red-DiscordBot/pull/2628
.. _#2639: https://github.com/Cog-Creators/Red-DiscordBot/pull/2639
.. _#2642: https://github.com/Cog-Creators/Red-DiscordBot/pull/2642
.. _#2652: https://github.com/Cog-Creators/Red-DiscordBot/pull/2652

565
docs/changelog_3_2_0.rst Normal file
View File

@@ -0,0 +1,565 @@
.. 3.2.x Changelogs
Redbot 3.2.3 (2020-01-17)
=========================
Core Bot Changes
----------------
- Further improvements have been made to bot startup and shutdown.
- Prefixes are now cached for performance.
- Added the means for cog creators to use a global preinvoke hook.
- The bot now ensures it has at least the bare neccessary permissions before running commands.
- Deleting instances works as intended again.
- Sinbad stopped fighting it and embraced the entrypoint madness.
Core Commands
-------------
- The servers command now also shows the ids.
Admin Cog
---------
- The selfrole command now has reasonable expectations about hierarchy.
Help Formatter
--------------
- ``[botname]`` is now replaced with the bot's display name in help text.
- New features added for cog creators to further customize help behavior.
- Check out our command reference for details on new ``format_help_for_context`` method.
- Embed settings are now consistent.
Downloader
----------
- Improved a few user facing messages.
- Added pagination of output on cog update.
- Added logging of failures.
Docs
----
There's more detail to the below changes, so go read the docs.
For some reason, documenting documentation changes is hard.
- Added instructions about git version.
- Clarified instructions for installation and update.
- Added more details to the API key reference.
- Fixed some typos and versioning mistakes.
Audio
-----
Draper did things.
- No seriously, Draper did things.
- Wait you wanted details? Ok, I guess we can share those.
- Audio properly disconnects with autodisconnect, even if notify is being used.
- Symbolic links now work as intended for local tracks.
- Bump play now shows the correct time till next track.
- Multiple user facing messages have been made more correct.
Redbot 3.2.2 (2020-01-10)
=========================
Hotfixes
--------
- Fix Help Pagination issue
Docs
----
- Correct venv docs
Redbot 3.2.1 (2020-01-10)
=========================
Hotfixes
--------
- Fix Mongo conversion from being incorrectly blocked
- Fix announcer not creating a message for success feedback
- Log an error with creating case types rather than crash
Redbot 3.2.0 (2020-01-09)
=========================
Core Bot Changes
----------------
Breaking Changes
~~~~~~~~~~~~~~~~
- Modlog casetypes no longer have an attribute for auditlog action type. (`#2897 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2897>`_)
- Removed ``redbot.core.modlog.get_next_case_number()``. (`#2908 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2908>`_)
- Removed ``bank.MAX_BALANCE``, use ``bank.get_max_balance()`` from now on. (`#2926 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2926>`_)
- The main bot config is no longer directly accessible to cogs. New methods have been added for use where this is concerned.
New methods for this include
- ``bot.get_shared_api_tokens``
- ``bot.set_shared_api_tokens``
- ``bot.get_embed_color``
- ``bot.get_embed_colour``
- ``bot.get_admin_roles``
- ``bot.get_admin_role_ids``
- ``bot.get_mod_roles``
- ``bot.get_mod_role_ids`` (`#2967 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2967>`_)
- Reserved some command names for internal Red use. These are available programatically as ``redbot.core.commands.RESERVED_COMMAND_NAMES``. (`#2973 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2973>`_)
- Removed ``bot._counter``, Made a few more attrs private (``cog_mgr``, ``main_dir``). (`#2976 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2976>`_)
- Extension's ``setup()`` function should no longer assume that we are, or even will be connected to Discord.
This also means that cog creators should no longer use ``bot.wait_until_ready()`` inside it. (`#3073 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3073>`_)
- Removed the mongo driver. (`#3099 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3099>`_)
Bug Fixes
~~~~~~~~~
- Help now properly hides disabled commands. (`#2863 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2863>`_)
- Fixed ``bot.remove_command`` throwing an error when trying to remove a non-existent command. (`#2888 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2888>`_)
- ``Command.can_see`` now works as intended for disabled commands. (`#2892 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2892>`_)
- Modlog entries now show up properly without the mod cog loaded. (`#2897 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2897>`_)
- Fixed an error in ``[p]reason`` when setting the reason for a case without a moderator. (`#2908 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2908>`_)
- Bank functions now check the recipient balance before transferring and stop the transfer if the recipient's balance will go above the maximum allowed balance. (`#2923 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2923>`_)
- Removed potential for additional bad API calls per ban/unban. (`#2945 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2945>`_)
- The ``[p]invite`` command no longer errors when a user has the bot blocked or DMs disabled in the server. (`#2948 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2948>`_)
- Stopped using the ``:`` character in backup's filename - Windows doesn't accept it. (`#2954 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2954>`_)
- ``redbot-setup delete`` no longer errors with "unexpected keyword argument". (`#2955 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2955>`_)
- ``redbot-setup delete`` no longer prompts about backup when the user passes the option ``--no-prompt``. (`#2956 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2956>`_)
- Cleaned up the ``[p]inviteset public`` and ``[p]inviteset perms`` help strings. (`#2963 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2963>`_)
- ```[p]embedset user`` now only affects DM's. (`#2966 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2966>`_)
- Fixed an unfriendly error when the provided instance name doesn't exist. (`#2968 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2968>`_)
- Fixed the help text and response of ``[p]set usebotcolor`` to accurately reflect what the command is doing. (`#2974 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2974>`_)
- Red no longer types infinitely when a command with a cooldown is called within the last second of a cooldown. (`#2985 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2985>`_)
- Removed f-string usage in the launcher to prevent our error handling from causing an error. (`#3002 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3002>`_)
- Fixed ``MessagePredicate.greater`` and ``MessagePredicate.less`` allowing any valid int instead of only valid ints/floats that are greater/less than the given value. (`#3004 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3004>`_)
- Fixed an error in ``[p]uptime`` when the uptime is under a second. (`#3009 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3009>`_)
- Added quotation marks to the response of ``[p]helpset tagline`` so that two consecutive full stops do not appear. (`#3010 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3010>`_)
- Fixed an issue with clearing rules in permissions. (`#3014 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3014>`_)
- Lavalink will now be restarted after an unexpected shutdown. (`#3033 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3033>`_)
- Added a 3rd-party lib folder to ``sys.path`` before loading cogs. This prevents issues with 3rd-party cogs failing to load when Downloader is not loaded to install requirements. (`#3036 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3036>`_)
- Escaped track descriptions so that they do not break markdown. (`#3047 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3047>`_)
- Red will now properly send a message when the invoked command is guild-only. (`#3057 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3057>`_)
- Arguments ``--co-owner`` and ``--load-cogs`` now properly require at least one argument to be passed. (`#3060 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3060>`_)
- Now always appends the 3rd-party lib folder to the end of ``sys.path`` to avoid shadowing Red's dependencies. (`#3062 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3062>`_)
- Fixed ``is_automod_immune``'s handling of the guild check and added support for checking webhooks. (`#3100 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3100>`_)
- Fixed the generation of the ``repos.json`` file in the backup process. (`#3114 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3114>`_)
- Fixed an issue where calling audio commands when not in a voice channel could result in a crash. (`#3120 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3120>`_)
- Added handling for invalid folder names in the data path gracefully in ``redbot-setup`` and ``redbot --edit``. (`#3171 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3171>`_)
- ``--owner`` and ``-p`` cli flags now work when added from launcher. (`#3174 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3174>`_)
- Red will now prevent users from locking themselves out with localblacklist. (`#3207 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3207>`_)
- Fixed help ending up a little too large for discord embed limits. (`#3208 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3208>`_)
- Fixed formatting issues in commands that list whitelisted/blacklisted users/roles when the list is empty. (`#3219 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3219>`_)
- Red will now prevent users from locking the guild owner out with localblacklist (unless the command caller is bot owner). (`#3221 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3221>`_)
- Guild owners are no longer affected by the local whitelist and blacklist. (`#3221 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3221>`_)
- Fixed an attribute error that can be raised in ``humanize_timedelta`` if ``seconds = 0``. (`#3231 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3231>`_)
- Fixed ``ctx.clean_prefix`` issues resulting from undocumented changes from discord. (`#3249 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3249>`_)
- ``redbot.core.bot.Bot.owner_id`` is now set in the post connection startup. (`#3273 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3273>`_)
- ``redbot.core.bot.Bot.send_to_owners()`` and ``redbot.core.bot.Bot.get_owner_notification_destinations()`` now wait until Red is done with post connection startup to ensure owner ID is available. (`#3273 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3273>`_)
Enhancements
~~~~~~~~~~~~
- Added the option to modify the RPC port with the ``--rpc-port`` flag. (`#2429 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2429>`_)
- Slots now has a 62.5% expected payout and will not inflate economy when spammed. (`#2875 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2875>`_)
- Allowed passing ``cls`` in the ``redbot.core.commands.group()`` decorator. (`#2881 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2881>`_)
- Red's Help Formatter is now considered to have a stable API. (`#2892 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2892>`_)
- Modlog no longer generates cases without being told to for actions the bot did. (`#2897 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2897>`_)
- Some generic modlog casetypes are now pre-registered for cog creator use. (`#2897 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2897>`_)
- ModLog is now much faster at creating cases, especially in large servers. (`#2908 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2908>`_)
- JSON config files are now stored without indentation, this is to reduce the file size and increase the performance of write operations. (`#2921 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2921>`_)
- ``--[no-]backup``, ``--[no-]drop-db`` and ``--[no-]remove-datapath`` in the ``redbot-setup delete`` command are now on/off flags. (`#2958 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2958>`_)
- The confirmation prompts in ``redbot-setup`` now have default values for user convenience. (`#2958 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2958>`_)
- ``redbot-setup delete`` now has the option to leave Red's data untouched on database backends. (`#2962 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2962>`_)
- Red now takes less time to fetch cases, unban members, and list warnings. (`#2964 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2964>`_)
- Red now handles more things prior to connecting to discord to reduce issues during the initial load. (`#3045 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3045>`_)
- ``bot.send_filtered`` now returns the message that is sent. (`#3052 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3052>`_)
- Red will now send a message when the invoked command is DM-only. (`#3057 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3057>`_)
- All ``y/n`` confirmations in cli commands are now unified. (`#3060 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3060>`_)
- Changed ``[p]info`` to say "This bot is an..." instead of "This is an..." for clarity. (`#3121 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3121>`_)
- ``redbot-setup`` will now use the instance name in default data paths to avoid creating a second instance with the same data path. (`#3171 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3171>`_)
- Instance names can now only include characters A-z, numbers, underscores, and hyphens. Old instances are unaffected by this change. (`#3171 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3171>`_)
- Clarified that ``[p]backup`` saves the **bot's** data in the help text. (`#3172 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3172>`_)
- Added ``redbot --debuginfo`` flag which shows useful information for debugging. (`#3183 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3183>`_)
- Added the Python executable field to ``[p]debuginfo``. (`#3184 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3184>`_)
- When Red prompts for a token, it will now print a link to the guide explaining how to obtain a token. (`#3204 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3204>`_)
- ``redbot-setup`` will no longer log to disk. (`#3269 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3269>`_)
- ``redbot.core.bot.Bot.send_to_owners()`` and ``redbot.core.bot.Bot.get_owner_notification_destinations()`` now log when they are not able to find the owner notification destination. (`#3273 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3273>`_)
- The lib folder is now cleared on minor Python version changes. ``[p]cog reinstallreqs`` in Downloader can be used to regenerate the lib folder for a new Python version. (`#3274 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3274>`_)
- If Red detects operating system or architecture change, it will now warn the owner about possible problems with the lib folder. (`#3274 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3274>`_)
- ``[p]playlist download`` will now compress playlists larger than the server attachment limit and attempt to send that. (`#3279 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3279>`_)
New Features
~~~~~~~~~~~~
- Added functions to acquire locks on Config groups and values. These locks are acquired by default when calling a value as a context manager. See ``Value.get_lock`` for details. (`#2654 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2654>`_)
- Added a config driver for PostgreSQL. (`#2723 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2723>`_)
- Added methods to Config for accessing things by id without mocked objects
- ``Config.guild_from_id``
- ``Config.user_from_id``
- ``Config.role_from_id``
- ``Config.channel_from_id``
- ``Config.member_from_ids``
- This one requires multiple ids, one for the guild, one for the user
- Consequence of discord's object model (`#2804 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2804>`_)
- New method ``humanize_number`` in ``redbot.core.utils.chat_formatting`` to convert numbers into text that respects the current locale. (`#2836 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2836>`_)
- Added new commands to Economy
- ``[p]bank prune user`` - This will delete a user's bank account.
- ``[p]bank prune local`` - This will prune the bank of accounts for users who are no longer in the server.
- ``[p]bank prune global`` - This will prune the global bank of accounts for users who do not share any servers with the bot. (`#2845 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2845>`_)
- Red now uses towncrier for changelog generation. (`#2872 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2872>`_)
- Added ``redbot.core.modlog.get_latest_case`` to fetch the case object for the most recent ModLog case. (`#2908 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2908>`_)
- Added ``[p]bankset maxbal`` to set the maximum bank balance. (`#2926 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2926>`_)
- Added a few methods and classes replacing direct config access (which is no longer supported)
- ``redbot.core.Red.allowed_by_whitelist_blacklist``
- ``redbot.core.Red.get_valid_prefixes``
- ``redbot.core.Red.clear_shared_api_tokens``
- ``redbot.core.commands.help.HelpSettings`` (`#2976 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2976>`_)
- Added the cli flag ``redbot --edit`` which is used to edit the instance name, token, owner, and datapath. (`#3060 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3060>`_)
- Added ``[p]licenseinfo``. (`#3090 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3090>`_)
- Ensured that people can migrate from MongoDB. (`#3108 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3108>`_)
- Added a command to list disabled commands globally or per guild. (`#3118 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3118>`_)
- New event ``on_red_api_tokens_update`` is now dispatched when shared api keys for a service are updated. (`#3134 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3134>`_)
- Added ``redbot-setup backup``. (`#3235 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3235>`_)
- Added the method ``redbot.core.bot.Bot.wait_until_red_ready()`` that waits until Red's post connection startup is done. (`#3273 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3273>`_)
Removals
~~~~~~~~
- ``[p]set owner`` and ``[p]set token`` have been removed in favor of managing server side. (`#2928 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2928>`_)
- Shared libraries are marked for removal in Red 3.4. (`#3106 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3106>`_)
- Removed ``[p]backup``. Use the cli command ``redbot-setup backup`` instead. (`#3235 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3235>`_)
- Removed the functions ``safe_delete``, ``fuzzy_command_search``, ``format_fuzzy_results`` and ``create_backup`` from ``redbot.core.utils``. (`#3240 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3240>`_)
- Removed a lot of the launcher's handled behavior. (`#3289 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3289>`_)
Miscellaneous changes
~~~~~~~~~~~~~~~~~~~~~
- `#2527 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2527>`_, `#2571 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2571>`_, `#2723 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2723>`_, `#2836 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2836>`_, `#2849 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2849>`_, `#2861 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2861>`_, `#2885 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2885>`_, `#2890 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2890>`_, `#2897 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2897>`_, `#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_, `#2924 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2924>`_, `#2939 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2939>`_, `#2940 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2940>`_, `#2941 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2941>`_, `#2949 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2949>`_, `#2953 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2953>`_, `#2964 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2964>`_, `#2986 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2986>`_, `#2993 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2993>`_, `#2997 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2997>`_, `#3008 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3008>`_, `#3017 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3017>`_, `#3048 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3048>`_, `#3059 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3059>`_, `#3080 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3080>`_, `#3089 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3089>`_, `#3104 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3104>`_, `#3106 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3106>`_, `#3129 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3129>`_, `#3152 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3152>`_, `#3160 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3160>`_, `#3168 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3168>`_, `#3173 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3173>`_, `#3176 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3176>`_, `#3186 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3186>`_, `#3192 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3192>`_, `#3193 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3193>`_, `#3195 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3195>`_, `#3202 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3202>`_, `#3214 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3214>`_, `#3223 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3223>`_, `#3229 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3229>`_, `#3245 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3245>`_, `#3247 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3247>`_, `#3248 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3248>`_, `#3250 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3250>`_, `#3254 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3254>`_, `#3255 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3255>`_, `#3256 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3256>`_, `#3258 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3258>`_, `#3261 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3261>`_, `#3275 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3275>`_, `#3276 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3276>`_, `#3293 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3293>`_, `#3278 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3278>`_, `#3285 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3285>`_, `#3296 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3296>`_,
Dependency changes
~~~~~~~~~~~~~~~~~~~~~~~
- Added ``pytest-mock`` requirement to ``tests`` extra. (`#2571 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2571>`_)
- Updated the python minimum requirement to 3.8.1, updated JRE to Java 11. (`#3245 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3245>`_)
- Bumped dependency versions. (`#3288 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3288>`_)
- Bumped red-lavalink version. (`#3290 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3290>`_)
Documentation Changes
~~~~~~~~~~~~~~~~~~~~~
- Started the user guides covering cogs and the user interface of the bot. This includes, for now, a "Getting started" guide. (`#1734 <https://github.com/Cog-Creators/Red-DiscordBot/issues/1734>`_)
- Added documentation for PM2 support. (`#2105 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2105>`_)
- Updated linux install docs, adding sections for Fedora Linux, Debian/Raspbian Buster, and openSUSE. (`#2558 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2558>`_)
- Created documentation covering what we consider a developer facing breaking change and the guarantees regarding them. (`#2882 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2882>`_)
- Fixed the user parameter being labeled as ``discord.TextChannel`` instead of ``discord.abc.User`` in ``redbot.core.utils.predicates``. (`#2914 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2914>`_)
- Updated towncrier info in the contribution guidelines to explain how to create a changelog for a standalone PR. (`#2915 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2915>`_)
- Reworded the virtual environment guide to make it sound less scary. (`#2920 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2920>`_)
- Driver docs no longer show twice. (`#2972 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2972>`_)
- Added more information about ``redbot.core.utils.humanize_timedelta`` into the docs. (`#2986 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2986>`_)
- Added a direct link to the "Installing Red" section in "Installing using powershell and chocolatey". (`#2995 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2995>`_)
- Updated Git PATH install (Windows), capitalized some words, stopped mentioning the launcher. (`#2998 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2998>`_)
- Added autostart documentation for Red users who installed Red inside of a virtual environment. (`#3005 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3005>`_)
- Updated the Cog Creation guide with a note regarding the Develop version as well as the folder layout for local cogs. (`#3021 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3021>`_)
- Added links to the getting started guide at the end of installation guides. (`#3025 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3025>`_)
- Added proper docstrings to enums that show in drivers docs. (`#3035 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3035>`_)
- Discord.py doc links will now always use the docs for the currently used version of discord.py. (`#3053 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3053>`_)
- Added ``|DPY_VERSION|`` substitution that will automatically get replaced by the current discord.py version. (`#3053 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3053>`_)
- Added missing descriptions for function returns. (`#3054 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3054>`_)
- Stopped overwriting the ``docs/prolog.txt`` file in ``conf.py``. (`#3082 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3082>`_)
- Fixed some typos and wording, added MS Azure to the host list. (`#3083 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3083>`_)
- Updated the docs footer copyright to 2019. (`#3105 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3105>`_)
- Added a deprecation note about shared libraries in the Downloader Framework docs. (`#3106 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3106>`_)
- Updated the apikey framework documentation. Changed ``bot.get_shared_api_keys()`` to ``bot.get_shared_api_tokens()``. (`#3110 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3110>`_)
- Added information about ``info.json``'s ``min_python_version`` key in Downloader Framework docs. (`#3124 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3124>`_)
- Added an event reference for the ``on_red_api_tokens_update`` event in the Shared API Keys docs. (`#3134 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3134>`_)
- Added notes explaining the best practices with config. (`#3149 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3149>`_)
- Documented additional attributes in Context. (`#3151 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3151>`_)
- Updated Windows docs with up to date dependency instructions. (`#3188 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3188>`_)
- Added a "Publishing cogs for V3" document explaining how to make user's cogs work with Downloader. (`#3234 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3234>`_)
- Fixed broken docs for ``redbot.core.commands.Context.react_quietly``. (`#3257 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3257>`_)
- Updated copyright notices on License and RTD config to 2020. (`#3259 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3259>`_)
- Added a line about setuptools and wheel. (`#3262 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3262>`_)
- Ensured development builds are not advertised to the wrong audience. (`#3292 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3292>`_)
- Clarified the usage intent of some of the chat formatting functions. (`#3292 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3292>`_)
Admin
-----
Breaking Changes
~~~~~~~~~~~~~~~~
- Changed ``[p]announce ignore`` and ``[p]announce channel`` to ``[p]announceset ignore`` and ``[p]announceset channel``. (`#3250 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3250>`_)
- Changed ``[p]selfrole <role>`` to ``[p]selfrole add <role>``, changed ``[p]selfrole add`` to ``[p]selfroleset add`` , and changed ``[p]selfrole delete`` to ``[p]selfroleset remove``. (`#3250 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3250>`_)
Bug Fixes
~~~~~~~~~
- Fixed ``[p]announce`` failing after encountering an error attempting to message the bot owner. (`#3166 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3166>`_)
- Improved the clarity of user facing messages when the user is not allowed to do something due to Discord hierarchy rules. (`#3250 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3250>`_)
- Fixed some role managing commands not properly checking if Red had ``manage_roles`` perms before attempting to manage roles. (`#3250 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3250>`_)
- Fixed ``[p]editrole`` commands not checking if roles to be edited are higher than Red's highest role before trying to edit them. (`#3250 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3250>`_)
- Fixed ``[p]announce ignore`` and ``[p]announce channel`` not being able to be used by guild owners and administrators. (`#3250 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3250>`_)
Enhancements
~~~~~~~~~~~~
- Added custom issue messages for adding and removing roles, this makes it easier to create translations. (`#3016 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3016>`_)
Audio
-----
Bug Fixes
~~~~~~~~~
- ``[p]playlist remove`` now removes the playlist url if the playlist was created through ``[p]playlist save``. (`#2861 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2861>`_)
- Users are no longer able to accidentally overwrite existing playlist if a new one with the same name is created/renamed. (`#2861 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2861>`_)
- ``[p]audioset settings`` no longer shows lavalink JAR version. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- Fixed a ``KeyError: loadType`` when trying to play tracks. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]audioset settings`` now uses ``ctx.is_owner()`` to check if the context author is the bot owner. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- Fixed track indexs being off by 1 in ``[p]search``. (`#2940 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2940>`_)
- Fixed an issue where updating your Spotify and YouTube Data API tokens did not refresh them. (`#3047 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3047>`_)
- Fixed an issue where the blacklist was not being applied correctly. (`#3047 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3047>`_)
- Fixed an issue in ``[p]audioset restrictions blacklist list`` where it would call the list a ``Whitelist``. (`#3047 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3047>`_)
- Red's status is now properly cleared on emptydisconnect. (`#3050 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3050>`_)
- Fixed a console spam caused sometimes when auto disconnect and auto pause are used. (`#3123 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3123>`_)
- Fixed an error that was thrown when running ``[p]audioset dj``. (`#3165 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3165>`_)
- Fixed a crash that could happen when the bot can't connect to the lavalink node. (`#3238 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3238>`_)
- Restricted the number of songs shown in the queue to first 500 to avoid heartbeats. (`#3279 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3279>`_)
- Added more cooldowns to playlist commands and restricted the queue and playlists to 10k songs to avoid bot errors. (`#3286 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3286>`_)
Enhancements
~~~~~~~~~~~~
- ``[p]playlist upload`` will now load playlists generated via ``[p]playlist download`` much faster if the playlist uses the new scheme. (`#2861 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2861>`_)
- ``[p]playlist`` commands now can be used by everyone regardless of DJ settings, however it will respect DJ settings when creating/modifying playlists in the server scope. (`#2861 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2861>`_)
- Spotify, Youtube Data, and Lavalink API calls can be cached to avoid repeated calls in the future, see ``[p]audioset cache``. (`#2890 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2890>`_)
- Playlists will now start playing as soon as first track is loaded. (`#2890 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2890>`_)
- ``[p]audioset localpath`` can set a path anywhere in your machine now. Note: This path needs to be visible by ``Lavalink.jar``. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]queue`` now works when there are no tracks in the queue, showing the track currently playing. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]audioset settings`` now reports Red Lavalink version. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- Adding and removing reactions in Audio is no longer a blocking action. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- When shuffle is on, queue now shows the correct play order. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]seek`` and ``[p]skip`` can be used by user if they are the song requester while DJ mode is enabled and votes are disabled. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- Adding a playlist and an album to a saved playlist skips tracks already in the playlist. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- DJ mode is now turned off if the DJ role is deleted. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- When playing a localtrack, ``[p]play`` and ``[p]bumpplay`` no longer require the use of the prefix "localtracks\\".
Before: ``[p]bumpplay localtracks\\ENM\\501 - Inside The Machine.mp3``
Now: ``[p]bumpplay ENM\\501 - Inside The Machine.mp3``
Now nested folders: ``[p]bumpplay Parent Folder\\Nested Folder\\track.mp3`` (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- Removed commas in explanations about how to set API keys. (`#2905 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2905>`_)
- Expanded local track support to all file formats (m3u, m4a, mp4, etc). (`#2940 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2940>`_)
- Cooldowns are now reset upon failure of commands that have a cooldown timer. (`#2940 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2940>`_)
- Improved the explanation in the help string for ``[p]audioset emptydisconnect``. (`#3051 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3051>`_)
- Added a typing indicator to playlist dedupe. (`#3058 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3058>`_)
- Exposed clearer errors to users in the play commands. (`#3085 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3085>`_)
- Better error handling when the player is unable to play multiple tracks in the sequence. (`#3165 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3165>`_)
New Features
~~~~~~~~~~~~
- Added support for nested folders in the localtrack folder. (`#270 <https://github.com/Cog-Creators/Red-DiscordBot/issues/270>`_)
- Now auto pauses the queue when the voice channel is empty. (`#721 <https://github.com/Cog-Creators/Red-DiscordBot/issues/721>`_)
- All Playlist commands now accept optional arguments, use ``[p]help playlist <subcommand>`` for more details. (`#2861 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2861>`_)
- ``[p]playlist rename`` will now allow users to rename existing playlists. (`#2861 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2861>`_)
- ``[p]playlist update`` will now allow users to update non-custom Playlists to the latest available tracks. (`#2861 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2861>`_)
- There are now 3 different scopes of playlist. To define them, use the ``--scope`` argument.
``Global Playlist``
- These playlists will be available in all servers the bot is in.
- These can be managed by the Bot Owner only.
``Server Playlist``
- These playlists will only be available in the server they were created in.
- These can be managed by the Bot Owner, Guild Owner, Mods, Admins, DJs, and the Creator (if the DJ role is disabled).
``User Playlist``
- These playlists will be available in all servers both the bot and the creator are in.
- These can be managed by the Bot Owner and Creator only. (`#2861 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2861>`_)
- ``[p]audioset cache`` can be used to set the cache level. **It's off by default**. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]genre`` can be used to play spotify playlists. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]audioset cacheage`` can be used to set the maximum age of an entry in the cache. **Default is 365 days**. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]audioset autoplay`` can be used to enable auto play once the queue runs out. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- New events dispatched by Audio.
- ``on_red_audio_track_start(guild: discord.Guild, track: lavalink.Track, requester: discord.Member)``
- ``on_red_audio_track_end(guild: discord.Guild, track: lavalink.Track, requester: discord.Member)``
- ``on_red_audio_track_enqueue(guild: discord.Guild, track: lavalink.Track, requester: discord.Member)``
- ``on_red_audio_track_auto_play(guild: discord.Guild, track: lavalink.Track, requester: discord.Member)``
- ``on_red_audio_queue_end(guild: discord.Guild, track: lavalink.Track, requester: discord.Member)``
- ``on_red_audio_audio_disconnect(guild: discord.Guild)``
- ``on_red_audio_skip_track(guild: discord.Guild, track: lavalink.Track, requester: discord.Member)`` (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]queue shuffle`` can be used to shuffle the queue manually. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]queue clean self`` can be used to remove all songs you requested from the queue. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]audioset restrictions`` can be used to add or remove keywords which songs must have or are not allowed to have. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]playlist dedupe`` can be used to remove duplicated tracks from a playlist. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]autoplay`` can be used to play a random song. (`#2904 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2904>`_)
- ``[p]bumpplay`` can be used to add a song to the front of the queue. (`#2940 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2940>`_)
- ``[p]shuffle`` has an additional argument to tell the bot whether it should shuffle bumped tracks. (`#2940 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2940>`_)
- Added global whitelist/blacklist commands. (`#3047 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3047>`_)
- Added self-managed daily playlists in the GUILD scope, these are called "Daily playlist - YYYY-MM-DD" and auto delete after 7 days. (`#3199 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3199>`_)
CustomCom
---------
Enhancements
~~~~~~~~~~~~
- The group command ``[p]cc create`` can now be used to create simple CCs without specifying "simple". (`#1767 <https://github.com/Cog-Creators/Red-DiscordBot/issues/1767>`_)
- Added a query option for CC typehints for URL-based CCs. (`#3228 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3228>`_)
- Now uses the ``humanize_list`` utility for iterable parameter results, e.g. ``{#:Role.members}``. (`#3277 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3277>`_)
Downloader
----------
Bug Fixes
~~~~~~~~~
- Made the regex for repo names use raw strings to stop causing a ``DeprecationWarning`` for invalid escape sequences. (`#2571 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2571>`_)
- Downloader will no longer attempt to install cogs that are already installed. (`#2571 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2571>`_)
- Repo names can now only contain the characters listed in the help text (A-Z, 0-9, underscores, and hyphens). (`#2827 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2827>`_)
- ``[p]findcog`` no longer attempts to find a cog for commands without a cog. (`#2902 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2902>`_)
- Downloader will no longer attempt to install a cog with same name as another cog that is already installed. (`#2927 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2927>`_)
- Added error handling for when a remote repository or branch is deleted, now notifies the which repository failed and continues to update the others. (`#2936 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2936>`_)
- ``[p]cog install`` will no longer error if a cog has an empty install message. (`#3024 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3024>`_)
- Made ``redbot.cogs.downloader.repo_manager.Repo.clean_url`` work with relative urls. This property is ``str`` type now. (`#3141 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3141>`_)
- Fixed an error on repo add from empty string values for the ``install_msg`` info.json field. (`#3153 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3153>`_)
- Disabled all git auth prompts when adding/updating a repo with Downloader. (`#3159 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3159>`_)
- ``[p]findcog`` now properly works for cogs with less typical folder structure. (`#3177 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3177>`_)
- ``[p]cog uninstall`` now fully unloads cog - the bot will not try to load it on next startup. (`#3179 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3179>`_)
Enhancements
~~~~~~~~~~~~
- Downloader will now check if the Python and bot versions match requirements in ``info.json`` during update. (`#1866 <https://github.com/Cog-Creators/Red-DiscordBot/issues/1866>`_)
- ``[p]cog install`` now accepts multiple cog names. (`#2527 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2527>`_)
- When passing cogs to ``[p]cog update``, it will now only update those cogs, not all cogs from the repo those cogs are from. (`#2527 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2527>`_)
- Added error messages for failures when installing/reinstalling requirements and copying cogs and shared libraries. (`#2571 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2571>`_)
- ``[p]findcog`` now uses sanitized urls (without HTTP Basic Auth fragments). (`#3129 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3129>`_)
- ``[p]repo info`` will now show the repo's url, branch, and authors. (`#3225 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3225>`_)
- ``[p]cog info`` will now show cog authors. (`#3225 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3225>`_)
- ``[p]findcog`` will now show the repo's branch. (`#3225 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3225>`_)
New Features
~~~~~~~~~~~~
- Added ``[p]repo update [repos]`` which updates repos without updating the cogs from them. (`#2527 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2527>`_)
- Added ``[p]cog installversion <repo_name> <revision> <cogs>`` which installs cogs from a specified revision (commit, tag) of the given repo. When using this command, the cog will automatically be pinned. (`#2527 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2527>`_)
- Added ``[p]cog pin <cogs>`` and ``[p]cog unpin <cogs>`` for pinning cogs. Cogs that are pinned will not be updated when using update commands. (`#2527 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2527>`_)
- Added ``[p]cog checkforupdates`` that lists which cogs can be updated (including pinned cog) without updating them. (`#2527 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2527>`_)
- Added ``[p]cog updateallfromrepos <repos>`` that updates all cogs from the given repos. (`#2527 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2527>`_)
- Added ``[p]cog updatetoversion <repo_name> <revision> [cogs]`` that updates all cogs or ones of user's choosing to chosen revision of the given repo. (`#2527 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2527>`_)
- Added ``[p]cog reinstallreqs`` that reinstalls cog requirements and shared libraries for all installed cogs. (`#3167 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3167>`_)
Documentation Changes
~~~~~~~~~~~~~~~~~~~~~
- Added ``redbot.cogs.downloader.installable.InstalledModule`` to Downloader's framework docs. (`#2527 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2527>`_)
- Removed API References for Downloader. (`#3234 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3234>`_)
Image
-----
Enhancements
~~~~~~~~~~~~
- Updated the giphycreds command to match the formatting of the other API commands. (`#2905 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2905>`_)
- Removed commas from explanations about how to set API keys. (`#2905 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2905>`_)
Mod
---
Bug Fixes
~~~~~~~~~
- ``[p]userinfo`` no longer breaks when a user has an absurd numbers of roles. (`#2910 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2910>`_)
- Fixed Mod cog not recording username changes for ``[p]names`` and ``[p]userinfo`` commands. (`#2918 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2918>`_)
- Fixed ``[p]modset deletedelay`` deleting non-command messages. (`#2924 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2924>`_)
- Fixed an error when reloading Mod. (`#2932 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2932>`_)
Enhancements
~~~~~~~~~~~~
- Slowmode now accepts integer-only inputs as seconds. (`#2884 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2884>`_)
Permissions
-----------
Bug Fixes
~~~~~~~~~
- Defaults are now cleared properly when clearing all rules. (`#3037 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3037>`_)
Enhancements
~~~~~~~~~~~~
- Better explained the usage of commands with the ``<who_or_what>`` argument. (`#2991 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2991>`_)
Streams
-------
Bug Fixes
~~~~~~~~~
- Fixed a ``TypeError`` in the ``TwitchStream`` class when calling Twitch client_id from Red shared APIs tokens. (`#3042 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3042>`_)
- Changed the ``stream_alert`` function for Twitch alerts to make it work with how the ``TwitchStream`` class works now. (`#3042 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3042>`_)
Enhancements
~~~~~~~~~~~~
- Removed commas from explanations about how to set API keys. (`#2905 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2905>`_)
Trivia
------
Bug Fixes
~~~~~~~~~
- Fixed a typo in Ahsoka Tano's name in the Starwars trivia list. (`#2909 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2909>`_)
- Fixed a bug where ``[p]trivia leaderboard`` failed to run. (`#2911 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2911>`_)
- Fixed a typo in the Greek mythology trivia list regarding Hermes' staff. (`#2994 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2994>`_)
- Fixed a question in the Overwatch trivia list that accepted blank responses. (`#2996 <https://github.com/Cog-Creators/Red-DiscordBot/issues/2996>`_)
- Fixed questions and answers that were incorrect in the Clash Royale trivia list. (`#3236 <https://github.com/Cog-Creators/Red-DiscordBot/issues/3236>`_)
Enhancements
~~~~~~~~~~~~
- Added trivia lists for Prince and Michael Jackson lyrics. (`#12 <https://github.com/Cog-Creators/Red-DiscordBot/issues/12>`_)

432
docs/changelog_3_3_0.rst Normal file
View File

@@ -0,0 +1,432 @@
.. 3.3.x Changelogs
Redbot 3.3.5 (2020-04-09)
=========================
| Thanks to all these amazing people that contributed to this release:
| :ghuser:`jack1142`, :ghuser:`Kowlin`
End-user changelog
------------------
Core Bot
********
- "Outdated" field no longer shows in ``[p]info`` when Red is up-to-date (:issue:`3730`)
Alias
*****
- Fixed regression in ``[p]alias add`` that caused it to reject commands containing arguments (:issue:`3734`)
Redbot 3.3.4 (2020-04-05)
=========================
| Thanks to all these amazing people that contributed to this release:
| :ghuser:`jack1142`, :ghuser:`kennnyshiwa`
End-user changelog
------------------
Core Bot
********
- Fixed checks related to bank's global state that were used in commands in Bank, Economy and Trivia cogs (:issue:`3707`)
Alias
*****
- ``[p]alias add`` now sends an error when command user tries to alias doesn't exist (:issue:`3710`, :issue:`3545`)
Developer changelog
-------------------
Core Bot
********
- Bump dependencies, including update to discord.py 1.3.3 (:issue:`3723`)
Utility Functions
*****************
- `redbot.core.utils.common_filters.filter_invites` now filters ``discord.io/discord.li`` invites links (:issue:`3717`)
- Fixed false-positives in `redbot.core.utils.common_filters.filter_invites` (:issue:`3717`)
Documentation changes
---------------------
- Versions of pre-requirements are now included in Windows install guide (:issue:`3708`)
Redbot 3.3.3 (2020-03-28)
=========================
| Thanks to all these amazing people that contributed to this release:
| :ghuser:`AnonGuy`, :ghuser:`Dav-Git`, :ghuser:`FancyJesse`, :ghuser:`Ianardo-DiCaprio`, :ghuser:`jack1142`, :ghuser:`kennnyshiwa`, :ghuser:`Kowlin`, :ghuser:`NeuroAssassin`, :ghuser:`PredaaA`, :ghuser:`Stonedestroyer`, :ghuser:`TrustyJAID`
End-user changelog
------------------
Core Bot
********
- Delete delay for command messages has been moved from Mod cog to Core (:issue:`3638`, :issue:`3636`)
- Fixed various bugs with blacklist and whitelist (:issue:`3643`, :issue:`3642`)
- Added ``[p]set regionalformat`` command that allows users to set regional formatting that is different from bot's locale (:issue:`3677`, :issue:`3588`)
- ``[p]set locale`` allows any valid locale now, not just locales for which Red has translations (:issue:`3676`, :issue:`3596`)
- Permissions for commands in Bank, Economy and Trivia cogs can now be overriden by Permissions cog (:issue:`3672`, :issue:`3233`)
- Outages of ``pypi.org`` no longer prevent the bot from starting (:issue:`3663`)
- Fixed formatting of help strings in fuzzy search results (:issue:`3673`, :issue:`3507`)
- Fixed few deprecation warnings related to menus and uvloop (:issue:`3644`, :issue:`3700`)
Core Commands
*************
- ``[p]set game`` no longer errors when trying to clear the status (:issue:`3630`, :issue:`3628`)
- All owner notifcations in Core now use proper prefixes in messages (:issue:`3632`)
- Added ``[p]set playing`` and ``[p]set streaming`` aliases for respectively ``[p]set game`` and ``[p]set stream`` (:issue:`3646`, :issue:`3590`)
ModLog
******
- Modlog's cases now keep last known username to prevent losing that information from case's message on edit (:issue:`3674`, :issue:`3443`)
CustomCom
*********
- Added ``[p]cc search`` command that allows users to search through created custom commands (:issue:`2573`)
Cleanup
*******
- Added ``[p]cleanup spam`` command that deletes duplicate messages from the last X messages and keeps only one copy (:issue:`3688`)
- Removed regex support in ``[p]cleanup self`` (:issue:`3704`)
Downloader
**********
- ``[p]cog checkforupdates`` now includes information about cogs that can't be installed due to Red/Python version requirements (:issue:`3678`, :issue:`3448`)
General
*******
- Added more detailed mode to ``[p]serverinfo`` command that can be accessed with ``[p]serverinfo 1`` (:issue:`2382`, :issue:`3659`)
Image
*****
- Users can now specify how many images should be returned in ``[p]imgur search`` and ``[p]imgur subreddit`` using ``[count]`` argument (:issue:`3667`, :issue:`3044`)
- ``[p]imgur search`` and ``[p]imgur subreddit`` now return one image by default (:issue:`3667`, :issue:`3044`)
Mod
***
- ``[p]userinfo`` now shows user's activities (:issue:`3669`)
- ``[p]userinfo`` now shows status icon near the username (:issue:`3669`)
- Muting no longer fails if user leaves while applying overwrite (:issue:`3627`)
- Fixed error that happened when Mod cog was loaded for the first time during bot startup (:issue:`3632`, :issue:`3626`)
Permissions
***********
- Commands for setting default rules now error when user tries to deny access to command designated as being always available (:issue:`3504`, :issue:`3465`)
Streams
*******
- Fixed an error that happened when no game was set on Twitch stream (:issue:`3631`)
- Preview picture for YouTube stream alerts is now bigger (:issue:`3689`, :issue:`3685`)
- YouTube channels with a livestream that doesn't have any current viewer are now properly showing as streaming (:issue:`3690`)
- Failures in Twitch API authentication are now logged (:issue:`3657`)
Trivia
******
- Added ``[p]triviaset custom upload/delete/list`` commands for managing custom trivia lists from Discord (:issue:`3420`, :issue:`3307`)
- Trivia sessions no longer error on payout when winner's balance would exceed max balance (:issue:`3666`, :issue:`3584`)
Warnings
********
- Sending warnings to warned user can now be disabled with ``[p]warnset toggledm`` command (:issue:`2929`, :issue:`2800`)
- Added ``[p]warnset warnchannel`` command that allows to set a channel where warnings should be sent to instead of the channel command was called in (:issue:`2929`, :issue:`2800`)
- Added ``[p]warnset togglechannel`` command that allows to disable sending warn message in guild channel (:issue:`2929`, :issue:`2800`)
- ``[p]warn`` now tells the moderator when bot wasn't able to send the warning to the user (:issue:`3653`, :issue:`3633`)
Developer changelog
-------------------
Core Bot
********
- Deprecation warnings issued by Red now use correct stack level so that the cog developers can find the cause of them (:issue:`3644`)
Dev Cog
*******
- Add ``__name__`` to environment's globals (:issue:`3649`, :issue:`3648`)
Documentation changes
---------------------
- Fixed install instructions for Mac in `install_linux_mac` (:issue:`3675`, :issue:`3436`)
- Windows install instructions now use ``choco upgrade`` commands instead of ``choco install`` to ensure up-to-date packages (:issue:`3684`)
Miscellaneous
-------------
- **Core Bot** - Command errors (i.e. command on cooldown, dm-only and guild-only commands, etc) can now be translated (:issue:`3665`, :issue:`2988`)
- **Core Bot** - ``redbot-setup`` now prints link to Getting started guide at the end of the setup (:issue:`3027`)
- **Core Bot** - Whitelist and blacklist commands now properly require passing at least one user (or role in case of local whitelist/blacklist) (:issue:`3652`, :issue:`3645`)
- **Downloader** - Fix misleading error appearing when repo name is already taken in ``[p]repo add`` (:issue:`3695`)
- **Downloader** - Improved error messages for unexpected errors in ``[p]repo add`` (:issue:`3656`)
- **Downloader** - Prevent encoding errors from crashing ``[p]cog update`` (:issue:`3639`, :issue:`3637`)
- **Trivia** - Non-finite numbers can no longer be passed to ``[p]triviaset timelimit``, ``[p]triviaset stopafter`` and ``[p]triviaset payout`` (:issue:`3668`, :issue:`3583`)
- **Utility Functions** - `redbot.core.utils.menus.menu()` now checks permissions *before* trying to clear reactions (:issue:`3589`, :issue:`3145`)
Redbot 3.3.2 (2020-02-28)
=========================
| Thanks to all these amazing people that contributed to this release:
| :ghuser:`aikaterna`, :ghuser:`chasehult`, :ghuser:`Dav-Git`, :ghuser:`DiscordLiz`, :ghuser:`Drapersniper`, :ghuser:`fixator10`, :ghuser:`Flame442`, :ghuser:`Hedlund01`, :ghuser:`jack1142`, :ghuser:`Kowlin`, :ghuser:`mikeshardmind`, :ghuser:`PredaaA`, :ghuser:`Stonedestroyer`, :ghuser:`trundleroo`, :ghuser:`TrustyJAID`, :ghuser:`zephyrkul`
End-user changelog
------------------
Core Bot
********
- Ignored guilds/channels and whitelist/blacklist are now cached for performance (:issue:`3472`)
- Ignored guilds/channels have been moved from Mod cog to Core (:issue:`3472`)
- ``[p]ignore channel`` command can now also ignore channel categories (:issue:`3472`)
Core Commands
*************
- Core cogs will now send bot mention prefix properly in places where discord doesn't render mentions (:issue:`3579`, :issue:`3591`, :issue:`3499`)
- Fix a bug with ``[p]blacklist add`` that made it impossible to blacklist users that bot doesn't share a server with (:issue:`3472`, :issue:`3220`)
- Improve user experience of ``[p]set game/listening/watching/`` commands (:issue:`3562`)
- Add ``[p]licenceinfo`` alias for ``[p]licenseinfo`` command to conform with non-American English (:issue:`3460`)
Admin
*****
- ``[p]announce`` will now only send error message if an actual errors occurs (:issue:`3514`, :issue:`3513`)
Alias
*****
- ``[p]alias help`` will now properly work in non-English locales (:issue:`3546`)
Audio
*****
- Users should be able to play age-restricted tracks from YouTube again (:issue:`3620`)
Economy
*******
- Next payday time will now be adjusted for users when payday time is changed (:issue:`3496`, :issue:`3438`)
Downloader
**********
- Downloader will no longer fail because of invalid ``info.json`` files (:issue:`3533`, :issue:`3456`)
- Add better logging of errors when Downloader fails to add a repo (:issue:`3558`)
Image
*****
- Fix load error for users that updated Red from version lower than 3.1 to version 3.2 or newer (:issue:`3617`)
Mod
***
- ``[p]hackban`` and ``[p]unban`` commands support user mentions now (:issue:`3524`)
- Ignored guilds/channels have been moved from Mod cog to Core (:issue:`3472`)
Streams
*******
- Fix stream alerts for Twitch (:issue:`3487`)
- Significantly reduce the quota usage for YouTube stream alerts (:issue:`3237`)
- Add ``[p]streamset timer`` command which can be used to control how often the cog checks for live streams (:issue:`3237`)
Trivia
******
- Add better handling for errors in trivia session (:issue:`3606`)
Trivia Lists
************
- Remove empty answers in trivia lists (:issue:`3581`)
Warnings
********
- Users can now pass a reason to ``[p]unwarn`` command (:issue:`3490`, :issue:`3093`)
Developer changelog
-------------------
Core Bot
********
- Updated all our dependencies - we're using discord.py 1.3.2 now (:issue:`3609`)
- Add traceback logging to task exception handling (:issue:`3517`)
- Developers can now create a command from an async function wrapped in `functools.partial` (:issue:`3542`)
- Bot will now show deprecation warnings in logs (:issue:`3527`, :issue:`3615`)
- Subcommands of command group with ``invoke_without_command=True`` will again inherit this group's checks (:issue:`3614`)
Config
******
- Fix Config's singletons (:issue:`3137`, :issue:`3136`)
Utility Functions
*****************
- Add clearer error when page is of a wrong type in `redbot.core.utils.menus.menu()` (:issue:`3571`)
Dev Cog
*******
- Allow for top-level `await`, `async for` and `async with` in ``[p]debug`` and ``[p]repl`` commands (:issue:`3508`)
Downloader
**********
- Downloader will now replace ``[p]`` with clean prefix same as it does in help command (:issue:`3592`)
- Add schema validation to ``info.json`` file processing - it should now be easier to notice any issues with those files (:issue:`3533`, :issue:`3442`)
Documentation changes
---------------------
- Add guidelines for Cog Creators in `guide_cog_creation` document (:issue:`3568`)
- Restructure virtual environment instructions to improve user experience (:issue:`3495`, :issue:`3411`, :issue:`3412`)
- Getting started guide now explain use of quotes for arguments with spaces (:issue:`3555`, :issue:`3111`)
- ``latest`` version of docs now displays a warning about possible differences from current stable release (:issue:`3570`)
- Make systemd guide clearer on obtaining username and python path (:issue:`3537`, :issue:`3462`)
- Indicate instructions for different venv types in systemd guide better (:issue:`3538`)
- Service file in `autostart_systemd` now also waits for network connection to be ready (:issue:`3549`)
- Hide alias of ``randomize_colour`` in docs (:issue:`3491`)
- Add separate headers for each event predicate class for better navigation (:issue:`3595`, :issue:`3164`)
- Improve wording of explanation for ``required_cogs`` key in `guide_publish_cogs` (:issue:`3520`)
Miscellaneous
-------------
- Use more reliant way of checking if command is bot owner only in ``[p]warnaction`` (Warnings cog) (:issue:`3516`, :issue:`3515`)
- Update PyPI domain in ``[p]info`` and update checker (:issue:`3607`)
- Stop using deprecated code in core (:issue:`3610`)
Redbot 3.3.1 (2020-02-05)
=========================
Core Bot
--------
- Add a cli flag for setting a max size of message cache
- Allow to edit prefix from command line using ``redbot --edit``.
- Some functions have been changed to no longer use deprecated asyncio functions
Core Commands
-------------
- The short help text for dm has been made more useful
- dm no longer allows owners to have the bot attempt to DM itself
Utils
-----
- Passing the event loop explicitly in utils is deprecated (Removal in 3.4)
Mod Cog
-------
- Hackban now works properly without being provided a number of days
Documentation Changes
---------------------
- Add ``-e`` flag to ``journalctl`` command in systemd guide so that it takes the user to the end of logs automatically.
- Added section to install docs for CentOS 8
- Improve usage of apt update in docs
Redbot 3.3.0 (2020-01-26)
=========================
Core Bot
--------
- The bot's description is now configurable.
- We now use discord.py 1.3.1, this comes with added teams support.
- The commands module has been slightly restructured to provide more useful data to developers.
- Help is now self consistent in the extra formatting used.
Core Commands
-------------
- Slowmode should no longer error on nonsensical time quantities.
- Embed use can be configured per channel as well.
Documentation
-------------
- We've made some small fixes to inaccurate instructions about installing with pyenv.
- Notes about deprecating in 3.3 have been altered to 3.4 to match the intended timeframe.
Admin
-----
- Gives feedback when adding or removing a role doesn't make sense.
Audio
-----
- Playlist finding is more intuitive.
- disconnect and repeat commands no longer interfere with eachother.
CustomCom
---------
- No longer errors when exiting an interactive menu.
Cleanup
-------
- A rare edge case involving messages which are deleted during cleanup and are the only message was fixed.
Downloader
----------
- Some user facing messages were improved.
- Downloader's initialization can no longer time out at startup.
General
-------
- Roll command will no longer attempt to roll obscenely large amounts.
Mod
---
- You can set a default amount of days to clean up when banning.
- Ban and hackban now use that default.
- Users can now optionally be DMed their ban reason.
Permissions
-----------
- Now has stronger enforcement of prioritizing botwide settings.

View File

@@ -1,62 +0,0 @@
.. Importing data from a V2 install
================================
Importing data from a V2 install
================================
----------------
What you'll need
----------------
1. A Running V3 bot
2. The path where your V2 bot is installed
--------------
Importing data
--------------
.. important::
Unless otherwise specified, the V2 data will take priority over V3 data for the same entires
.. important::
For the purposes of this guide, your prefix will be denoted as
[p]
You should swap whatever you made your prefix in for this.
All of the below are commands to be entered in discord where the bot can
see them.
The dataconverter cog is not loaded by default. To start, load it with
.. code-block:: none
[p]load dataconverter
Next, you'll need to give it the path where your V2 install is.
On linux and OSX, it may look something like:
.. code-block:: none
/home/username/Red-DiscordBot/
On Windows it will look something like:
.. code-block:: none
C:\Users\yourusername\Red-DiscordBot
Once you have that path, give it to the bot with the following command
(make sure to swap your own path in)
.. code-block:: none
[p]convertdata /home/username/Red-DiscordBot/
From here, if the path is correct, you will be prompted with an interactive menu asking you
what data you would like to import
You can select an entry by number, or quit with any of 'quit', 'exit', 'q', '-1', or 'cancel'

View File

@@ -1,9 +0,0 @@
.. Downloader Cog Reference
Downloader Cog Reference
========================
.. automodule:: redbot.cogs.downloader
.. autoclass:: redbot.cogs.downloader.downloader.Downloader
:members:

View File

@@ -72,15 +72,15 @@ Locking the ``[p]play`` command to approved server(s) as a bot owner:
.. code-block:: none
[p]permissions setglobaldefault play deny
[p]permissions setdefaultglobalrule deny play
[p]permissions addglobalrule allow play [server ID or name]
Locking the ``[p]play`` command to specific voice channel(s) as a serverowner or admin:
.. code-block:: none
[p]permissions setserverdefault deny play
[p]permissions setserverdefault deny "playlist start"
[p]permissions setdefaultserverrule deny play
[p]permissions setdefaultserverrule deny "playlist start"
[p]permissions addserverrule allow play [voice channel ID or name]
[p]permissions addserverrule allow "playlist start" [voice channel ID or name]

View File

@@ -36,11 +36,12 @@ os.environ["BUILDING_DOCS"] = "1"
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.extlinks",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinx.ext.napoleon",
"sphinx.ext.doctest",
"sphinxcontrib.asyncio",
"sphinxcontrib_trio",
]
# Add any paths that contain templates here, relative to this directory.
@@ -57,7 +58,7 @@ master_doc = "index"
# General information about the project.
project = "Red - Discord Bot"
copyright = "2018, Cog Creators"
copyright = "2018-2020, Cog Creators"
author = "Cog Creators"
# The version info for the project you're documenting, acts as replacement for
@@ -65,6 +66,7 @@ author = "Cog Creators"
# built documents.
#
from redbot.core import __version__
from discord import __version__ as dpy_version
# The short X.Y version.
version = __version__
@@ -92,6 +94,12 @@ todo_include_todos = False
# Role which is assigned when you make a simple reference within backticks
default_role = "any"
# Includes substitutions for all files
with open("prolog.txt", "r") as file:
rst_prolog = file.read()
# Adds d.py version to available substitutions in all files
rst_prolog += f"\n.. |DPY_VERSION| replace:: {dpy_version}"
# -- Options for HTML output ----------------------------------------------
@@ -100,6 +108,9 @@ default_role = "any"
#
html_theme = "sphinx_rtd_theme"
# This will be needed until sphinx_rtd_theme supports the html5 writer
html4_writer = True
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
@@ -195,19 +206,34 @@ texinfo_documents = [
# A list of regular expressions that match URIs that should not be
# checked when doing a linkcheck build.
linkcheck_ignore = [r"https://java.com*"]
linkcheck_ignore = [r"https://java.com*", r"https://chocolatey.org*"]
linkcheck_retries = 3
# -- Options for extensions -----------------------------------------------
# Intersphinx
intersphinx_mapping = {
"python": ("https://docs.python.org/3.6", None),
"dpy": ("https://discordpy.readthedocs.io/en/rewrite/", None),
"python": ("https://docs.python.org/3", None),
"dpy": (f"https://discordpy.readthedocs.io/en/v{dpy_version}/", None),
"motor": ("https://motor.readthedocs.io/en/stable/", None),
"babel": ("http://babel.pocoo.org/en/stable/", None),
}
# Extlinks
# This allows to create links to d.py docs with
# :dpy_docs:`link text <site_name.html>`
extlinks = {
"dpy_docs": (f"https://discordpy.readthedocs.io/en/v{dpy_version}/%s", None),
"issue": ("https://github.com/Cog-Creators/Red-DiscordBot/issues/%s", "#"),
"ghuser": ("https://github.com/%s", "@"),
}
# Doctest
# If this string is non-empty, all blocks with ``>>>`` in them will be
# tested, not just the ones explicitly marked with ``.. doctest::``
doctest_test_doctest_blocks = ""
# Autodoc options
autodoc_default_options = {"show-inheritance": True}
autodoc_typehints = "none"

View File

@@ -0,0 +1,75 @@
.. V3 Shared API Key Reference
===============
Shared API Keys
===============
Red has a central API key storage utilising the core bots config. This allows cog creators to add a single location to store API keys for their cogs which may be shared between other cogs.
There needs to be some consistency between cog creators when using shared API keys between cogs. To help make this easier service should be all **lowercase** and the key names should match the naming convention of the API being accessed.
Example:
Twitch has a client ID and client secret so a user should be asked to input
``[p]set api twitch client_id,1234ksdjf client_secret,1234aldlfkd``
and when accessed in the code it should be done by
.. code-block:: python
await self.bot.get_shared_api_tokens("twitch")
Each service has its own dict of key, value pairs for each required key type. If there's only one key required then a name for the key is still required for storing and accessing.
Example:
``[p]set api youtube api_key,1234ksdjf``
and when accessed in the code it should be done by
.. code-block:: python
await self.bot.get_shared_api_tokens("youtube")
***********
Basic Usage
***********
.. code-block:: python
class MyCog:
@commands.command()
async def youtube(self, ctx, user: str):
youtube_keys = await self.bot.get_shared_api_tokens("youtube")
if youtube_keys.get("api_key") is None:
return await ctx.send("The YouTube API key has not been set.")
# Use the API key to access content as you normally would
***************
Event Reference
***************
.. function:: on_red_api_tokens_update(service_name, api_tokens)
Dispatched when service's api keys are updated.
:param service_name: Name of the service.
:type service_name: :class:`str`
:param api_tokens: New Mapping of token names to tokens. This contains api tokens that weren't changed too.
:type api_tokens: Mapping[:class:`str`, :class:`str`]
*********************
Additional References
*********************
.. py:currentmodule:: redbot.core.bot
.. automethod:: Red.get_shared_api_tokens
.. automethod:: Red.set_shared_api_tokens
.. automethod:: Red.remove_shared_api_tokens

View File

@@ -16,15 +16,16 @@ Basic Usage
.. code-block:: python
from redbot.core import bank
from redbot.core import bank, commands
import discord
class MyCog:
class MyCog(commands.Cog):
@commands.command()
async def balance(self, ctx, user: discord.Member=None):
async def balance(self, ctx, user: discord.Member = None):
if user is None:
user = ctx.author
bal = bank.get_balance(user)
currency = bank.get_currency_name(ctx.guild)
bal = await bank.get_balance(user)
currency = await bank.get_currency_name(ctx.guild)
await ctx.send(
"{}'s balance is {} {}".format(
user.display_name, bal, currency
@@ -40,3 +41,7 @@ Bank
.. automodule:: redbot.core.bank
:members:
:exclude-members: cost
.. autofunction:: cost
:decorator:

View File

@@ -7,7 +7,7 @@ Commands Package
This package acts almost identically to :doc:`discord.ext.commands <dpy:ext/commands/api>`; i.e.
all of the attributes from discord.py's are also in ours.
Some of these attributes, however, have been slightly modified, while others have been added to
extend functionlities used throughout the bot, as outlined below.
extend functionalities used throughout the bot, as outlined below.
.. autofunction:: redbot.core.commands.command
@@ -15,6 +15,7 @@ extend functionlities used throughout the bot, as outlined below.
.. autoclass:: redbot.core.commands.Command
:members:
:inherited-members: format_help_for_context
.. autoclass:: redbot.core.commands.Group
:members:
@@ -22,5 +23,14 @@ extend functionlities used throughout the bot, as outlined below.
.. autoclass:: redbot.core.commands.Context
:members:
.. autoclass:: redbot.core.commands.GuildContext
.. autoclass:: redbot.core.commands.DMContext
.. automodule:: redbot.core.commands.requires
:members: PrivilegeLevel, PermState, Requires
.. automodule:: redbot.core.commands.converter
:members:
:exclude-members: convert
:no-undoc-members:

View File

@@ -11,6 +11,9 @@ Config was introduced in V3 as a way to make data storage easier and safer for a
It will take some getting used to as the syntax is entirely different from what Red has used before, but we believe
Config will be extremely beneficial to both cog developers and end users in the long run.
.. note:: While config is great for storing data safely, there are some caveats to writing performant code which uses it.
Make sure to read the section on best practices for more of these details.
***********
Basic Usage
***********
@@ -364,6 +367,30 @@ much the same way they would in V2. The following examples will demonstrate how
await cog.load_data()
bot.add_cog(cog)
************************************
Best practices and performance notes
************************************
Config prioritizes being a safe data store without developers needing to
know how end users have configured their bot.
This does come with some performance costs, so keep the following in mind when choosing to
develop using config
* Config use in events should be kept minimal and should only occur
after confirming the event needs to interact with config
* Caching frequently used things, especially things used by events,
results in faster and less event loop blocking code.
* Only use config's context managers when you intend to modify data.
* While config is a great general use option, it may not always be the right one for you.
As a cog developer, even though config doesn't require one,
you can choose to require a database or store to something such as an sqlite
database stored within your cog's datapath.
*************
API Reference
*************
@@ -416,20 +443,25 @@ Value
Driver Reference
****************
.. automodule:: redbot.core.drivers
.. autofunction:: redbot.core.drivers.get_driver
.. autoclass:: redbot.core.drivers.BackendType
:members:
.. autoclass:: redbot.core.drivers.ConfigCategory
:members:
Base Driver
^^^^^^^^^^^
.. autoclass:: redbot.core.drivers.red_base.BaseDriver
.. autoclass:: redbot.core.drivers.BaseDriver
:members:
JSON Driver
^^^^^^^^^^^
.. autoclass:: redbot.core.drivers.red_json.JSON
.. autoclass:: redbot.core.drivers.JsonDriver
:members:
Mongo Driver
^^^^^^^^^^^^
.. autoclass:: redbot.core.drivers.red_mongo.Mongo
Postgres Driver
^^^^^^^^^^^^^^^
.. autoclass:: redbot.core.drivers.PostgresDriver
:members:

View File

@@ -1,87 +0,0 @@
.. downloader framework reference
Downloader Framework
====================
Info.json
*********
The optional info.json file may exist inside every package folder in the repo,
as well as in the root of the repo. The following sections describe the valid
keys within an info file (and maybe how the Downloader cog uses them).
Keys common to both repo and cog info.json (case sensitive)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- ``author`` (list of strings) - list of names of authors of the cog or repo.
- ``description`` (string) - A long description of the cog or repo. For cogs, this
is displayed when a user executes ``!cog info``.
- ``install_msg`` (string) - The message that gets displayed when a cog
is installed or a repo is added
.. tip:: You can use the ``[p]`` key in your string to use the prefix
used for installing.
- ``short`` (string) - A short description of the cog or repo. For cogs, this info
is displayed when a user executes ``!cog list``
Keys specific to the cog info.json (case sensitive)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- ``bot_version`` (list of integer) - Min version number of Red in the format ``(MAJOR, MINOR, PATCH)``
- ``hidden`` (bool) - Determines if a cog is visible in the cog list for a repo.
- ``disabled`` (bool) - Determines if a cog is available for install.
- ``required_cogs`` (map of cogname to repo URL) - A map of required cogs that this cog depends on.
Downloader will not deal with this functionality but it may be useful for other cogs.
- ``requirements`` (list of strings) - list of required libraries that are
passed to pip on cog install. ``SHARED_LIBRARIES`` do NOT go in this
list.
- ``tags`` (list of strings) - A list of strings that are related to the
functionality of the cog. Used to aid in searching.
- ``type`` (string) - Optional, defaults to ``COG``. Must be either ``COG`` or
``SHARED_LIBRARY``. If ``SHARED_LIBRARY`` then ``hidden`` will be ``True``.
API Reference
*************
.. automodule:: redbot.cogs.downloader.json_mixins
.. autoclass RepoJSONMixin
:members
.. automodule:: redbot.cogs.downloader.installable
Installable
^^^^^^^^^^^
.. autoclass:: Installable
:members:
.. automodule:: redbot.cogs.downloader.repo_manager
Repo
^^^^
.. autoclass:: Repo
:members:
Repo Manager
^^^^^^^^^^^^
.. autoclass:: RepoManager
:members:
Exceptions
^^^^^^^^^^
.. automodule:: redbot.cogs.downloader.errors
:members:

View File

@@ -16,17 +16,17 @@ Basic Usage
.. code-block:: python
from redbot.core import modlog
from redbot.core import commands, modlog
import discord
class MyCog:
class MyCog(commands.Cog):
@commands.command()
@checks.admin_or_permissions(ban_members=True)
async def ban(self, ctx, user: discord.Member, reason: str=None):
async def ban(self, ctx, user: discord.Member, reason: str = None):
await ctx.guild.ban(user)
case = modlog.create_case(
ctx.guild, ctx.message.created_at, "ban", user,
ctx.author, reason, until=None, channel=None
case = await modlog.create_case(
ctx.bot, ctx.guild, ctx.message.created_at, action_type="ban",
user=user, moderator=ctx.author, reason=reason
)
await ctx.send("Done. It was about time.")
@@ -35,50 +35,60 @@ Basic Usage
Registering Case types
**********************
To register a single case type:
To register case types, use an asynchronous ``initialize()`` method and call
it from your setup function:
.. code-block:: python
from redbot.core import modlog
# mycog/mycog.py
from redbot.core import modlog, commands
import discord
class MyCog:
def __init__(self, bot):
class MyCog(commands.Cog):
async def initialize(self):
await self.register_casetypes()
@staticmethod
async def register_casetypes():
# Registering a single casetype
ban_case = {
"name": "ban",
"default_setting": True,
"image": ":hammer:",
"image": "\N{HAMMER}",
"case_str": "Ban",
"audit_type": "ban"
}
modlog.register_casetype(**ban_case)
try:
await modlog.register_casetype(**ban_case)
except RuntimeError:
pass
To register multiple case types:
.. code-block:: python
from redbot.core import modlog
import discord
class MyCog:
def __init__(self, bot):
# Registering multiple casetypes
new_types = [
{
"name": "ban",
"name": "hackban",
"default_setting": True,
"image": ":hammer:",
"case_str": "Ban",
"audit_type": "ban"
"image": "\N{BUST IN SILHOUETTE}\N{HAMMER}",
"case_str": "Hackban",
},
{
"name": "kick",
"default_setting": True,
"image": ":boot:",
"image": "\N{WOMANS BOOTS}",
"case_str": "Kick",
"audit_type": "kick"
}
]
modlog.register_casetypes(new_types)
await modlog.register_casetypes(new_types)
.. code-block:: python
# mycog/__init__.py
from .mycog import MyCog
async def setup(bot):
cog = MyCog()
await cog.initialize()
bot.add_cog(cog)
.. important::
Image should be the emoji you want to represent your case type with.

View File

@@ -21,6 +21,7 @@ Embed Helpers
.. automodule:: redbot.core.utils.embed
:members:
:exclude-members: randomize_color
Reaction Menus
==============
@@ -31,7 +32,16 @@ Reaction Menus
Event Predicates
================
.. automodule:: redbot.core.utils.predicates
MessagePredicate
****************
.. autoclass:: redbot.core.utils.predicates.MessagePredicate
:members:
ReactionPredicate
*****************
.. autoclass:: redbot.core.utils.predicates.ReactionPredicate
:members:
Mod Helpers
@@ -40,12 +50,6 @@ Mod Helpers
.. automodule:: redbot.core.utils.mod
:members:
V2 Data Conversion
==================
.. automodule:: redbot.core.utils.data_converter
:members: DataConverter
Tunnel
======

Some files were not shown because too many files have changed in this diff Show More