aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Dockerfile6
-rw-r--r--Pipfile11
-rw-r--r--Pipfile.lock321
-rwxr-xr-xREADME.md2
-rw-r--r--bot/bot.py6
-rw-r--r--bot/constants.py22
-rw-r--r--bot/exts/christmas/adventofcode.py2
-rw-r--r--bot/exts/easter/conversationstarters.py28
-rw-r--r--bot/exts/evergreen/conversationstarters.py71
-rw-r--r--bot/exts/evergreen/fun.py30
-rw-r--r--bot/exts/evergreen/reddit.py6
-rw-r--r--bot/exts/evergreen/snakes/snakes_cog.py4
-rw-r--r--bot/exts/evergreen/status_cats.py33
-rw-r--r--bot/exts/evergreen/wolfram.py278
-rw-r--r--bot/exts/halloween/candy_collection.py6
-rw-r--r--bot/resources/easter/starter.json24
-rw-r--r--bot/resources/evergreen/py_topics.yaml89
-rw-r--r--bot/resources/evergreen/starter.yaml22
-rw-r--r--bot/resources/evergreen/trivia_quiz.json30
-rw-r--r--bot/utils/decorators.py2
-rw-r--r--bot/utils/pagination.py4
-rw-r--r--bot/utils/randomization.py23
22 files changed, 777 insertions, 243 deletions
diff --git a/Dockerfile b/Dockerfile
index b8a8a1e4..1e84a56d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,6 +6,12 @@ ENV PIP_NO_CACHE_DIR=false \
PIPENV_IGNORE_VIRTUALENVS=1 \
PIPENV_NOSPIN=1
+# Install git to be able to dowload git dependencies in the Pipfile
+RUN apt-get -y update \
+ && apt-get install -y \
+ git \
+ && rm -rf /var/lib/apt/lists/*
+
# Install pipenv
RUN pip install -U pipenv
diff --git a/Pipfile b/Pipfile
index 9e391945..38044a0a 100644
--- a/Pipfile
+++ b/Pipfile
@@ -7,22 +7,23 @@ name = "pypi"
aiodns = "~=2.0"
arrow = "~=0.14"
beautifulsoup4 = "~=4.8"
-discord-py = "~=1.3.2"
+discord-py = {git = "https://github.com/Rapptz/discord.py.git",ref = "0bc15fa130b8f01fe2d67446a2184d474b0d0ba7"}
fuzzywuzzy = "~=0.17"
pillow = "~=6.2"
pytz = "~=2019.2"
sentry-sdk = "~=0.14.2"
+PyYAML = "~=5.3.1"
[dev-packages]
-flake8 = "~=3.7"
-flake8-annotations = "~=2.0"
+flake8 = "~=3.8"
+flake8-annotations = "~=2.3"
flake8-bugbear = "~=20.1"
flake8-docstrings = "~=1.5"
flake8-import-order = "~=0.18"
flake8-string-format = "~=0.3"
-flake8-tidy-imports = "~=4.0"
+flake8-tidy-imports = "~=4.1"
flake8-todo = "~=0.7"
-pep8-naming = "~=0.9"
+pep8-naming = "~=0.11"
pre-commit = "~=2.1"
[requires]
diff --git a/Pipfile.lock b/Pipfile.lock
index ad92fcf1..74931967 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "d1c0feb610f6742095c50eb3049fdd308603c17e1a785c03d9bf703eda0af985"
+ "sha256": "dfd24795dcdab1a05fd774b5034b195b69d6d6ed52fd6810db9c89247f8e0a43"
},
"pipfile-spec": 6,
"requires": {
@@ -39,21 +39,23 @@
"sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59",
"sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"
],
+ "markers": "python_full_version >= '3.5.3'",
"version": "==3.6.2"
},
"arrow": {
"hashes": [
- "sha256:5390e464e2c5f76971b60ffa7ee29c598c7501a294bc9f5e6dadcb251a5d027b",
- "sha256:70729bcc831da496ca3cb4b7e89472c8e2d27d398908155e0796179f6d2d41ee"
+ "sha256:271b8e05174d48e50324ed0dc5d74796c839c7e579a4f21cf1a7394665f9e94f",
+ "sha256:edc31dc051db12c95da9bac0271cd1027b8e36912daf6d4580af53b23e62721a"
],
"index": "pypi",
- "version": "==0.15.5"
+ "version": "==0.15.8"
},
"async-timeout": {
"hashes": [
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
+ "markers": "python_full_version >= '3.5.3'",
"version": "==3.0.1"
},
"attrs": {
@@ -61,56 +63,57 @@
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==19.3.0"
},
"beautifulsoup4": {
"hashes": [
- "sha256:05fd825eb01c290877657a56df4c6e4c311b3965bda790c613a3d6fb01a5462a",
- "sha256:9fbb4d6e48ecd30bcacc5b63b94088192dcda178513b2ae3c394229f8911b887",
- "sha256:e1505eeed31b0f4ce2dbb3bc8eb256c04cc2b3b72af7d551a4ab6efd5cbe5dae"
+ "sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7",
+ "sha256:a6237df3c32ccfaee4fd201c8f5f9d9df619b93121d01353a64a73ce8c6ef9a8",
+ "sha256:e718f2342e2e099b640a34ab782407b7b676f47ee272d6739e60b8ea23829f2c"
],
"index": "pypi",
- "version": "==4.8.2"
+ "version": "==4.9.1"
},
"certifi": {
"hashes": [
- "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
- "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
+ "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
+ "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
],
- "version": "==2019.11.28"
+ "version": "==2020.6.20"
},
"cffi": {
"hashes": [
- "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff",
- "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b",
- "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac",
- "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0",
- "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384",
- "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26",
- "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6",
- "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b",
- "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e",
- "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd",
- "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2",
- "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66",
- "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc",
- "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8",
- "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55",
- "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4",
- "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5",
- "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d",
- "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78",
- "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa",
- "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793",
- "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f",
- "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a",
- "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f",
- "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30",
- "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f",
- "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3",
- "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"
- ],
- "version": "==1.14.0"
+ "sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e",
+ "sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c",
+ "sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e",
+ "sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1",
+ "sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4",
+ "sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2",
+ "sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c",
+ "sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0",
+ "sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798",
+ "sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1",
+ "sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4",
+ "sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731",
+ "sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4",
+ "sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c",
+ "sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487",
+ "sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e",
+ "sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f",
+ "sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123",
+ "sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c",
+ "sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b",
+ "sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650",
+ "sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad",
+ "sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75",
+ "sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82",
+ "sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7",
+ "sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15",
+ "sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa",
+ "sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281"
+ ],
+ "version": "==1.14.2"
},
"chardet": {
"hashes": [
@@ -120,11 +123,8 @@
"version": "==3.0.4"
},
"discord-py": {
- "hashes": [
- "sha256:7424be26b07b37ecad4404d9383d685995a0e0b3df3f9c645bdd3a4d977b83b4"
- ],
- "index": "pypi",
- "version": "==1.3.2"
+ "git": "https://github.com/Rapptz/discord.py.git",
+ "ref": "0bc15fa130b8f01fe2d67446a2184d474b0d0ba7"
},
"fuzzywuzzy": {
"hashes": [
@@ -136,32 +136,34 @@
},
"idna": {
"hashes": [
- "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
- "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
+ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
+ "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
- "version": "==2.9"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.10"
},
"multidict": {
"hashes": [
- "sha256:317f96bc0950d249e96d8d29ab556d01dd38888fbe68324f46fd834b430169f1",
- "sha256:42f56542166040b4474c0c608ed051732033cd821126493cf25b6c276df7dd35",
- "sha256:4b7df040fb5fe826d689204f9b544af469593fb3ff3a069a6ad3409f742f5928",
- "sha256:544fae9261232a97102e27a926019100a9db75bec7b37feedd74b3aa82f29969",
- "sha256:620b37c3fea181dab09267cd5a84b0f23fa043beb8bc50d8474dd9694de1fa6e",
- "sha256:6e6fef114741c4d7ca46da8449038ec8b1e880bbe68674c01ceeb1ac8a648e78",
- "sha256:7774e9f6c9af3f12f296131453f7b81dabb7ebdb948483362f5afcaac8a826f1",
- "sha256:85cb26c38c96f76b7ff38b86c9d560dea10cf3459bb5f4caf72fc1bb932c7136",
- "sha256:a326f4240123a2ac66bb163eeba99578e9d63a8654a59f4688a79198f9aa10f8",
- "sha256:ae402f43604e3b2bc41e8ea8b8526c7fa7139ed76b0d64fc48e28125925275b2",
- "sha256:aee283c49601fa4c13adc64c09c978838a7e812f85377ae130a24d7198c0331e",
- "sha256:b51249fdd2923739cd3efc95a3d6c363b67bbf779208e9f37fd5e68540d1a4d4",
- "sha256:bb519becc46275c594410c6c28a8a0adc66fe24fef154a9addea54c1adb006f5",
- "sha256:c2c37185fb0af79d5c117b8d2764f4321eeb12ba8c141a95d0aa8c2c1d0a11dd",
- "sha256:dc561313279f9d05a3d0ffa89cd15ae477528ea37aa9795c4654588a3287a9ab",
- "sha256:e439c9a10a95cb32abd708bb8be83b2134fa93790a4fb0535ca36db3dda94d20",
- "sha256:fc3b4adc2ee8474cb3cd2a155305d5f8eda0a9c91320f83e55748e1fcb68f8e3"
- ],
- "version": "==4.7.5"
+ "sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a",
+ "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000",
+ "sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2",
+ "sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507",
+ "sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5",
+ "sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7",
+ "sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d",
+ "sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463",
+ "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19",
+ "sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3",
+ "sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b",
+ "sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c",
+ "sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87",
+ "sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7",
+ "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430",
+ "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255",
+ "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==4.7.6"
},
"pillow": {
"hashes": [
@@ -238,6 +240,7 @@
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.20"
},
"python-dateutil": {
@@ -245,6 +248,7 @@
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1"
},
"pytz": {
@@ -255,34 +259,54 @@
"index": "pypi",
"version": "==2019.3"
},
+ "pyyaml": {
+ "hashes": [
+ "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
+ "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
+ "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
+ "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
+ "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
+ "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
+ "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
+ "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
+ "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
+ "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
+ "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
+ ],
+ "index": "pypi",
+ "version": "==5.3.1"
+ },
"sentry-sdk": {
"hashes": [
- "sha256:23808d571d2461a4ce3784ec12bbee5bdb8c026c143fe79d36cef8a6d653e71f",
- "sha256:bb90a4e19c7233a580715fc986cc44be2c48fc10b31e71580a2037e1c94b6950"
+ "sha256:0e5e947d0f7a969314aa23669a94a9712be5a688ff069ff7b9fc36c66adc160c",
+ "sha256:799a8bf76b012e3030a881be00e97bc0b922ce35dde699c6537122b751d80e2c"
],
"index": "pypi",
- "version": "==0.14.3"
+ "version": "==0.14.4"
},
"six": {
"hashes": [
- "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
- "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
+ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
+ "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
- "version": "==1.14.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.15.0"
},
"soupsieve": {
"hashes": [
- "sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae",
- "sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69"
+ "sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55",
+ "sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232"
],
- "version": "==2.0"
+ "markers": "python_version >= '3.5'",
+ "version": "==2.0.1"
},
"urllib3": {
"hashes": [
- "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
- "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
+ "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
+ "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
],
- "version": "==1.25.8"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
+ "version": "==1.25.10"
},
"websockets": {
"hashes": [
@@ -309,65 +333,63 @@
"sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36",
"sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"
],
+ "markers": "python_full_version >= '3.6.1'",
"version": "==8.1"
},
"yarl": {
"hashes": [
- "sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce",
- "sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6",
- "sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce",
- "sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae",
- "sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d",
- "sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f",
- "sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b",
- "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b",
- "sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb",
- "sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462",
- "sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea",
- "sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70",
- "sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1",
- "sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a",
- "sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b",
- "sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080",
- "sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"
- ],
- "version": "==1.4.2"
+ "sha256:040b237f58ff7d800e6e0fd89c8439b841f777dd99b4a9cca04d6935564b9409",
+ "sha256:17668ec6722b1b7a3a05cc0167659f6c95b436d25a36c2d52db0eca7d3f72593",
+ "sha256:3a584b28086bc93c888a6c2aa5c92ed1ae20932f078c46509a66dce9ea5533f2",
+ "sha256:4439be27e4eee76c7632c2427ca5e73703151b22cae23e64adb243a9c2f565d8",
+ "sha256:48e918b05850fffb070a496d2b5f97fc31d15d94ca33d3d08a4f86e26d4e7c5d",
+ "sha256:9102b59e8337f9874638fcfc9ac3734a0cfadb100e47d55c20d0dc6087fb4692",
+ "sha256:9b930776c0ae0c691776f4d2891ebc5362af86f152dd0da463a6614074cb1b02",
+ "sha256:b3b9ad80f8b68519cc3372a6ca85ae02cc5a8807723ac366b53c0f089db19e4a",
+ "sha256:bc2f976c0e918659f723401c4f834deb8a8e7798a71be4382e024bcc3f7e23a8",
+ "sha256:c22c75b5f394f3d47105045ea551e08a3e804dc7e01b37800ca35b58f856c3d6",
+ "sha256:c52ce2883dc193824989a9b97a76ca86ecd1fa7955b14f87bf367a61b6232511",
+ "sha256:ce584af5de8830d8701b8979b18fcf450cef9a382b1a3c8ef189bedc408faf1e",
+ "sha256:da456eeec17fa8aa4594d9a9f27c0b1060b6a75f2419fe0c00609587b2695f4a",
+ "sha256:db6db0f45d2c63ddb1a9d18d1b9b22f308e52c83638c26b422d520a815c4b3fb",
+ "sha256:df89642981b94e7db5596818499c4b2219028f2a528c9c37cc1de45bf2fd3a3f",
+ "sha256:f18d68f2be6bf0e89f1521af2b1bb46e66ab0018faafa81d70f358153170a317",
+ "sha256:f379b7f83f23fe12823085cd6b906edc49df969eb99757f58ff382349a3303c6"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==1.5.1"
}
},
"develop": {
"appdirs": {
"hashes": [
- "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
- "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
+ "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
+ "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
],
- "version": "==1.4.3"
+ "version": "==1.4.4"
},
"attrs": {
"hashes": [
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==19.3.0"
},
"cfgv": {
"hashes": [
- "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53",
- "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513"
+ "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d",
+ "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"
],
- "version": "==3.1.0"
+ "markers": "python_full_version >= '3.6.1'",
+ "version": "==3.2.0"
},
"distlib": {
"hashes": [
- "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"
- ],
- "version": "==0.3.0"
- },
- "entrypoints": {
- "hashes": [
- "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
- "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
+ "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb",
+ "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"
],
- "version": "==0.3"
+ "version": "==0.3.1"
},
"filelock": {
"hashes": [
@@ -378,19 +400,19 @@
},
"flake8": {
"hashes": [
- "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb",
- "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"
+ "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c",
+ "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"
],
"index": "pypi",
- "version": "==3.7.9"
+ "version": "==3.8.3"
},
"flake8-annotations": {
"hashes": [
- "sha256:a38b44d01abd480586a92a02a2b0a36231ec42dcc5e114de78fa5db016d8d3f9",
- "sha256:d5b0e8704e4e7728b352fa1464e23539ff2341ba11cc153b536fa2cf921ee659"
+ "sha256:7816a5d8f65ffdf37b8e21e5b17e0fd1e492aa92638573276de066e889a22b26",
+ "sha256:8d18db74a750dd97f40b483cc3ef80d07d03f687525bad8fd83365dcd3bfd414"
],
"index": "pypi",
- "version": "==2.0.1"
+ "version": "==2.3.0"
},
"flake8-bugbear": {
"hashes": [
@@ -448,10 +470,11 @@
},
"identify": {
"hashes": [
- "sha256:a7577a1f55cee1d21953a5cf11a3c839ab87f5ef909a4cba6cf52ed72b4c6059",
- "sha256:ab246293e6585a1c6361a505b68d5b501a0409310932b7de2c2ead667b564d89"
+ "sha256:69c4769f085badafd0e04b1763e847258cbbf6d898e8678ebffc91abdb86f6c6",
+ "sha256:d6ae6daee50ba1b493e9ca4d36a5edd55905d2cf43548fdc20b2a14edef102e7"
],
- "version": "==1.4.13"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.4.28"
},
"mccabe": {
"hashes": [
@@ -462,46 +485,49 @@
},
"nodeenv": {
"hashes": [
- "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"
+ "sha256:4b0b77afa3ba9b54f4b6396e60b0c83f59eaeb2d63dc3cc7a70f7f4af96c82bc"
],
- "version": "==1.3.5"
+ "version": "==1.4.0"
},
"pep8-naming": {
"hashes": [
- "sha256:5d9f1056cb9427ce344e98d1a7f5665710e2f20f748438e308995852cfa24164",
- "sha256:f3b4a5f9dd72b991bf7d8e2a341d2e1aa3a884a769b5aaac4f56825c1763bf3a"
+ "sha256:a1dd47dd243adfe8a83616e27cf03164960b507530f155db94e10b36a6cd6724",
+ "sha256:f43bfe3eea7e0d73e8b5d07d6407ab47f2476ccaeff6937c84275cd30b016738"
],
"index": "pypi",
- "version": "==0.10.0"
+ "version": "==0.11.1"
},
"pre-commit": {
"hashes": [
- "sha256:487c675916e6f99d355ec5595ad77b325689d423ef4839db1ed2f02f639c9522",
- "sha256:c0aa11bce04a7b46c5544723aedf4e81a4d5f64ad1205a30a9ea12d5e81969e1"
+ "sha256:1657663fdd63a321a4a739915d7d03baedd555b25054449090f97bb0cb30a915",
+ "sha256:e8b1315c585052e729ab7e99dcca5698266bedce9067d21dc909c23e3ceed626"
],
"index": "pypi",
- "version": "==2.2.0"
+ "version": "==2.6.0"
},
"pycodestyle": {
"hashes": [
- "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
- "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
+ "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
+ "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
],
- "version": "==2.5.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.6.0"
},
"pydocstyle": {
"hashes": [
"sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586",
"sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"
],
+ "markers": "python_version >= '3.5'",
"version": "==5.0.2"
},
"pyflakes": {
"hashes": [
- "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
- "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
+ "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
+ "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
],
- "version": "==2.1.1"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.2.0"
},
"pyyaml": {
"hashes": [
@@ -517,14 +543,16 @@
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
+ "index": "pypi",
"version": "==5.3.1"
},
"six": {
"hashes": [
- "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
- "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
+ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
+ "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
- "version": "==1.14.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.15.0"
},
"snowballstemmer": {
"hashes": [
@@ -535,17 +563,18 @@
},
"toml": {
"hashes": [
- "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
- "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
+ "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
+ "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
],
- "version": "==0.10.0"
+ "version": "==0.10.1"
},
"virtualenv": {
"hashes": [
- "sha256:6f4c2882a943d20714076679f8dcc5675e953d6c29bfea3bc5d08bb6cdea5d36",
- "sha256:cb1dab893f9e39b3e68d9118c555dcd86526d531c128c3f72e1551939723b72f"
+ "sha256:7b54fd606a1b85f83de49ad8d80dbec08e983a2d2f96685045b262ebc7481ee5",
+ "sha256:8cd7b2a4850b003a11be2fc213e206419efab41115cc14bca20e69654f2ac08e"
],
- "version": "==20.0.14"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==20.0.30"
}
}
}
diff --git a/README.md b/README.md
index c1952286..2fc04cbb 100755
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# SeasonalBot
[![Build Status](https://dev.azure.com/python-discord/Python%20Discord/_apis/build/status/Seasonal%20Bot%20(Mainline))](https://dev.azure.com/python-discord/Python%20Discord/_build/latest?definitionId=3)
-[![Discord](https://img.shields.io/static/v1?label=Python%20Discord&logo=discord&message=%3E30k%20members&color=%237289DA&logoColor=white)](https://discord.gg/2B963hn)
+[![Discord](https://img.shields.io/static/v1?label=Python%20Discord&logo=discord&message=%3E60k%20members&color=%237289DA&logoColor=white)](https://discord.gg/2B963hn)
A Discord bot for the Python Discord community which changes with the seasons, and provides useful event features.
diff --git a/bot/bot.py b/bot/bot.py
index 39ed8bbe..ffaf4284 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -10,7 +10,7 @@ from aiohttp import AsyncResolver, ClientSession, TCPConnector
from discord import DiscordException, Embed, Guild, User
from discord.ext import commands
-from bot.constants import Channels, Client
+from bot.constants import Channels, Client, MODERATION_ROLES
from bot.utils.decorators import mock_in_debug
log = logging.getLogger(__name__)
@@ -103,7 +103,7 @@ class SeasonalBot(commands.Bot):
return False
else:
- log.info(f"Asset successfully applied")
+ log.info("Asset successfully applied")
return True
@mock_in_debug(return_value=True)
@@ -203,7 +203,9 @@ class SeasonalBot(commands.Bot):
await self._guild_available.wait()
+_allowed_roles = [discord.Object(id_) for id_ in MODERATION_ROLES]
bot = SeasonalBot(
command_prefix=Client.prefix,
activity=discord.Game(name=f"Commands: {Client.prefix}help"),
+ allowed_mentions=discord.AllowedMentions(everyone=False, roles=_allowed_roles),
)
diff --git a/bot/constants.py b/bot/constants.py
index bf6c5a40..6605882d 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -17,6 +17,7 @@ __all__ = (
"Month",
"Roles",
"Tokens",
+ "Wolfram",
"MODERATION_ROLES",
"STAFF_ROLES",
"WHITELISTED_CHANNELS",
@@ -92,10 +93,11 @@ class Colours:
dark_green = 0x1f8b4c
orange = 0xe67e22
pink = 0xcf84e0
+ purple = 0xb734eb
soft_green = 0x68c290
+ soft_orange = 0xf9cb54
soft_red = 0xcd6d6d
yellow = 0xf9f586
- purple = 0xb734eb
class Emojis:
@@ -106,12 +108,12 @@ class Emojis:
trashcan = "<:trashcan:637136429717389331>"
ok_hand = ":ok_hand:"
- terning1 = "<:terning1:431249668983488527>"
- terning2 = "<:terning2:462339216987127808>"
- terning3 = "<:terning3:431249694467948544>"
- terning4 = "<:terning4:579980271475228682>"
- terning5 = "<:terning5:431249716328792064>"
- terning6 = "<:terning6:431249726705369098>"
+ dice_1 = "<:dice_1:755891608859443290>"
+ dice_2 = "<:dice_2:755891608741740635>"
+ dice_3 = "<:dice_3:755891608251138158>"
+ dice_4 = "<:dice_4:755891607882039327>"
+ dice_5 = "<:dice_5:755891608091885627>"
+ dice_6 = "<:dice_6:755891607680843838>"
issue = "<:IssueOpen:629695470327037963>"
issue_closed = "<:IssueClosed:629695470570307614>"
@@ -187,6 +189,12 @@ class Tokens(NamedTuple):
github = environ.get("GITHUB_TOKEN")
+class Wolfram(NamedTuple):
+ user_limit_day = int(environ.get("WOLFRAM_USER_LIMIT_DAY", 10))
+ guild_limit_day = int(environ.get("WOLFRAM_GUILD_LIMIT_DAY", 67))
+ key = environ.get("WOLFRAM_API_KEY")
+
+
# Default role combinations
MODERATION_ROLES = Roles.moderator, Roles.admin, Roles.owner
STAFF_ROLES = Roles.helpers, Roles.moderator, Roles.admin, Roles.owner
diff --git a/bot/exts/christmas/adventofcode.py b/bot/exts/christmas/adventofcode.py
index 00607074..b3fe0623 100644
--- a/bot/exts/christmas/adventofcode.py
+++ b/bot/exts/christmas/adventofcode.py
@@ -58,7 +58,7 @@ async def countdown_status(bot: commands.Bot) -> None:
hours, minutes = aligned_seconds // 3600, aligned_seconds // 60 % 60
if aligned_seconds == 0:
- playing = f"right now!"
+ playing = "right now!"
elif aligned_seconds == COUNTDOWN_STEP:
playing = f"in less than {minutes} minutes"
elif hours == 0:
diff --git a/bot/exts/easter/conversationstarters.py b/bot/exts/easter/conversationstarters.py
deleted file mode 100644
index a5f40445..00000000
--- a/bot/exts/easter/conversationstarters.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import json
-import logging
-import random
-from pathlib import Path
-
-from discord.ext import commands
-
-log = logging.getLogger(__name__)
-
-with open(Path("bot/resources/easter/starter.json"), "r", encoding="utf8") as f:
- starters = json.load(f)
-
-
-class ConvoStarters(commands.Cog):
- """Easter conversation topics."""
-
- def __init__(self, bot: commands.Bot):
- self.bot = bot
-
- @commands.command()
- async def topic(self, ctx: commands.Context) -> None:
- """Responds with a random topic to start a conversation."""
- await ctx.send(random.choice(starters['starters']))
-
-
-def setup(bot: commands.Bot) -> None:
- """Conversation starters Cog load."""
- bot.add_cog(ConvoStarters(bot))
diff --git a/bot/exts/evergreen/conversationstarters.py b/bot/exts/evergreen/conversationstarters.py
new file mode 100644
index 00000000..576b8d76
--- /dev/null
+++ b/bot/exts/evergreen/conversationstarters.py
@@ -0,0 +1,71 @@
+from pathlib import Path
+
+import yaml
+from discord import Color, Embed
+from discord.ext import commands
+
+from bot.constants import WHITELISTED_CHANNELS
+from bot.utils.decorators import override_in_channel
+from bot.utils.randomization import RandomCycle
+
+SUGGESTION_FORM = 'https://forms.gle/zw6kkJqv8U43Nfjg9'
+
+with Path("bot/resources/evergreen/starter.yaml").open("r", encoding="utf8") as f:
+ STARTERS = yaml.load(f, Loader=yaml.FullLoader)
+
+with Path("bot/resources/evergreen/py_topics.yaml").open("r", encoding="utf8") as f:
+ # First ID is #python-general and the rest are top to bottom categories of Topical Chat/Help.
+ PY_TOPICS = yaml.load(f, Loader=yaml.FullLoader)
+
+ # Removing `None` from lists of topics, if not a list, it is changed to an empty one.
+ PY_TOPICS = {k: [i for i in v if i] if isinstance(v, list) else [] for k, v in PY_TOPICS.items()}
+
+ # All the allowed channels that the ".topic" command is allowed to be executed in.
+ ALL_ALLOWED_CHANNELS = list(PY_TOPICS.keys()) + list(WHITELISTED_CHANNELS)
+
+# Putting all topics into one dictionary and shuffling lists to reduce same-topic repetitions.
+ALL_TOPICS = {'default': STARTERS, **PY_TOPICS}
+TOPICS = {
+ channel: RandomCycle(topics or ['No topics found for this channel.'])
+ for channel, topics in ALL_TOPICS.items()
+}
+
+
+class ConvoStarters(commands.Cog):
+ """Evergreen conversation topics."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @commands.command()
+ @override_in_channel(ALL_ALLOWED_CHANNELS)
+ async def topic(self, ctx: commands.Context) -> None:
+ """
+ Responds with a random topic to start a conversation.
+
+ If in a Python channel, a python-related topic will be given.
+
+ Otherwise, a random conversation topic will be received by the user.
+ """
+ # No matter what, the form will be shown.
+ embed = Embed(description=f'Suggest more topics [here]({SUGGESTION_FORM})!', color=Color.blurple())
+
+ try:
+ # Fetching topics.
+ channel_topics = TOPICS[ctx.channel.id]
+
+ # If the channel isn't Python-related.
+ except KeyError:
+ embed.title = f'**{next(TOPICS["default"])}**'
+
+ # If the channel ID doesn't have any topics.
+ else:
+ embed.title = f'**{next(channel_topics)}**'
+
+ finally:
+ await ctx.send(embed=embed)
+
+
+def setup(bot: commands.Bot) -> None:
+ """Conversation starters Cog load."""
+ bot.add_cog(ConvoStarters(bot))
diff --git a/bot/exts/evergreen/fun.py b/bot/exts/evergreen/fun.py
index c5f8f9c8..2f575c1c 100644
--- a/bot/exts/evergreen/fun.py
+++ b/bot/exts/evergreen/fun.py
@@ -66,17 +66,13 @@ class Fun(Cog):
elif num_rolls < 1:
output = ":no_entry: You must roll at least once."
for _ in range(num_rolls):
- terning = f"terning{random.randint(1, 6)}"
- output += getattr(Emojis, terning, '')
+ dice = f"dice_{random.randint(1, 6)}"
+ output += getattr(Emojis, dice, '')
await ctx.send(output)
@commands.command(name="uwu", aliases=("uwuwize", "uwuify",))
async def uwu_command(self, ctx: Context, *, text: str) -> None:
- """
- Converts a given `text` into it's uwu equivalent.
-
- Also accepts a valid discord Message ID or link.
- """
+ """Converts a given `text` into it's uwu equivalent."""
conversion_func = functools.partial(
utils.replace_many, replacements=UWU_WORDS, ignore_case=True, match_case=True
)
@@ -92,11 +88,7 @@ class Fun(Cog):
@commands.command(name="randomcase", aliases=("rcase", "randomcaps", "rcaps",))
async def randomcase_command(self, ctx: Context, *, text: str) -> None:
- """
- Randomly converts the casing of a given `text`.
-
- Also accepts a valid discord Message ID or link.
- """
+ """Randomly converts the casing of a given `text`."""
def conversion_func(text: str) -> str:
"""Randomly converts the casing of a given string."""
return "".join(
@@ -194,12 +186,14 @@ class Fun(Cog):
Union[Embed, None]: The embed if found in the valid Message, else None
"""
embed = None
- message = await Fun._get_discord_message(ctx, text)
- if isinstance(message, Message):
- text = message.content
- # Take first embed because we can't send multiple embeds
- if message.embeds:
- embed = message.embeds[0]
+
+ # message = await Fun._get_discord_message(ctx, text)
+ # if isinstance(message, Message):
+ # text = message.content
+ # # Take first embed because we can't send multiple embeds
+ # if message.embeds:
+ # embed = message.embeds[0]
+
return (text, embed)
@staticmethod
diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py
index fe204419..49127bea 100644
--- a/bot/exts/evergreen/reddit.py
+++ b/bot/exts/evergreen/reddit.py
@@ -68,9 +68,9 @@ class Reddit(commands.Cog):
# -----------------------------------------------------------
# This code below is bound of change when the emojis are added.
- upvote_emoji = self.bot.get_emoji(638729835245731840)
- comment_emoji = self.bot.get_emoji(638729835073765387)
- user_emoji = self.bot.get_emoji(638729835442602003)
+ upvote_emoji = self.bot.get_emoji(755845219890757644)
+ comment_emoji = self.bot.get_emoji(755845255001014384)
+ user_emoji = self.bot.get_emoji(755845303822974997)
text_emoji = self.bot.get_emoji(676030265910493204)
video_emoji = self.bot.get_emoji(676030265839190047)
image_emoji = self.bot.get_emoji(676030265734201344)
diff --git a/bot/exts/evergreen/snakes/snakes_cog.py b/bot/exts/evergreen/snakes/snakes_cog.py
index b3896fcd..9bbad9fe 100644
--- a/bot/exts/evergreen/snakes/snakes_cog.py
+++ b/bot/exts/evergreen/snakes/snakes_cog.py
@@ -567,7 +567,7 @@ class Snakes(Cog):
antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote")
antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url)
antidote_embed.set_image(url="https://i.makeagif.com/media/7-12-2015/Cj1pts.gif")
- antidote_embed.add_field(name=f"You have created the snake antidote!",
+ antidote_embed.add_field(name="You have created the snake antidote!",
value=f"The solution was: {' '.join(antidote_answer)}\n"
f"You had {10 - antidote_tries} tries remaining.")
await board_id.edit(embed=antidote_embed)
@@ -1078,7 +1078,7 @@ class Snakes(Cog):
query = snake['name']
# Build the URL and make the request
- url = f'https://www.googleapis.com/youtube/v3/search'
+ url = 'https://www.googleapis.com/youtube/v3/search'
response = await self.bot.http_session.get(
url,
params={
diff --git a/bot/exts/evergreen/status_cats.py b/bot/exts/evergreen/status_cats.py
new file mode 100644
index 00000000..586b8378
--- /dev/null
+++ b/bot/exts/evergreen/status_cats.py
@@ -0,0 +1,33 @@
+from http import HTTPStatus
+
+import discord
+from discord.ext import commands
+
+
+class StatusCats(commands.Cog):
+ """Commands that give HTTP statuses described and visualized by cats."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @commands.command(aliases=['statuscat'])
+ async def http_cat(self, ctx: commands.Context, code: int) -> None:
+ """Sends an embed with an image of a cat, potraying the status code."""
+ embed = discord.Embed(title=f'**Status: {code}**')
+
+ try:
+ HTTPStatus(code)
+
+ except ValueError:
+ embed.set_footer(text='Inputted status code does not exist.')
+
+ else:
+ embed.set_image(url=f'https://http.cat/{code}.jpg')
+
+ finally:
+ await ctx.send(embed=embed)
+
+
+def setup(bot: commands.Bot) -> None:
+ """Load the StatusCats cog."""
+ bot.add_cog(StatusCats(bot))
diff --git a/bot/exts/evergreen/wolfram.py b/bot/exts/evergreen/wolfram.py
new file mode 100644
index 00000000..898e8d2a
--- /dev/null
+++ b/bot/exts/evergreen/wolfram.py
@@ -0,0 +1,278 @@
+import logging
+from io import BytesIO
+from typing import Callable, List, Optional, Tuple
+from urllib import parse
+
+import arrow
+import discord
+from discord import Embed
+from discord.ext import commands
+from discord.ext.commands import BucketType, Cog, Context, check, group
+
+from bot.constants import Colours, STAFF_ROLES, Wolfram
+from bot.utils.pagination import ImagePaginator
+
+log = logging.getLogger(__name__)
+
+APPID = Wolfram.key
+DEFAULT_OUTPUT_FORMAT = "JSON"
+QUERY = "http://api.wolframalpha.com/v2/{request}?{data}"
+WOLF_IMAGE = "https://www.symbols.com/gi.php?type=1&id=2886&i=1"
+
+MAX_PODS = 20
+
+# Allows for 10 wolfram calls pr user pr day
+usercd = commands.CooldownMapping.from_cooldown(Wolfram.user_limit_day, 60 * 60 * 24, BucketType.user)
+
+# Allows for max api requests / days in month per day for the entire guild (Temporary)
+guildcd = commands.CooldownMapping.from_cooldown(Wolfram.guild_limit_day, 60 * 60 * 24, BucketType.guild)
+
+
+async def send_embed(
+ ctx: Context,
+ message_txt: str,
+ colour: int = Colours.soft_red,
+ footer: str = None,
+ img_url: str = None,
+ f: discord.File = None
+) -> None:
+ """Generate & send a response embed with Wolfram as the author."""
+ embed = Embed(colour=colour)
+ embed.description = message_txt
+ embed.set_author(name="Wolfram Alpha",
+ icon_url=WOLF_IMAGE,
+ url="https://www.wolframalpha.com/")
+ if footer:
+ embed.set_footer(text=footer)
+
+ if img_url:
+ embed.set_image(url=img_url)
+
+ await ctx.send(embed=embed, file=f)
+
+
+def custom_cooldown(*ignore: List[int]) -> Callable:
+ """
+ Implement per-user and per-guild cooldowns for requests to the Wolfram API.
+
+ A list of roles may be provided to ignore the per-user cooldown
+ """
+ async def predicate(ctx: Context) -> bool:
+ if ctx.invoked_with == 'help':
+ # if the invoked command is help we don't want to increase the ratelimits since it's not actually
+ # invoking the command/making a request, so instead just check if the user/guild are on cooldown.
+ guild_cooldown = not guildcd.get_bucket(ctx.message).get_tokens() == 0 # if guild is on cooldown
+ if not any(r.id in ignore for r in ctx.author.roles): # check user bucket if user is not ignored
+ return guild_cooldown and not usercd.get_bucket(ctx.message).get_tokens() == 0
+ return guild_cooldown
+
+ user_bucket = usercd.get_bucket(ctx.message)
+
+ if all(role.id not in ignore for role in ctx.author.roles):
+ user_rate = user_bucket.update_rate_limit()
+
+ if user_rate:
+ # Can't use api; cause: member limit
+ cooldown = arrow.utcnow().shift(seconds=int(user_rate)).humanize(only_distance=True)
+ message = (
+ "You've used up your limit for Wolfram|Alpha requests.\n"
+ f"Cooldown: {cooldown}"
+ )
+ await send_embed(ctx, message)
+ return False
+
+ guild_bucket = guildcd.get_bucket(ctx.message)
+ guild_rate = guild_bucket.update_rate_limit()
+
+ # Repr has a token attribute to read requests left
+ log.debug(guild_bucket)
+
+ if guild_rate:
+ # Can't use api; cause: guild limit
+ message = (
+ "The max limit of requests for the server has been reached for today.\n"
+ f"Cooldown: {int(guild_rate)}"
+ )
+ await send_embed(ctx, message)
+ return False
+
+ return True
+
+ return check(predicate)
+
+
+async def get_pod_pages(ctx: Context, bot: commands.Bot, query: str) -> Optional[List[Tuple]]:
+ """Get the Wolfram API pod pages for the provided query."""
+ async with ctx.channel.typing():
+ url_str = parse.urlencode({
+ "input": query,
+ "appid": APPID,
+ "output": DEFAULT_OUTPUT_FORMAT,
+ "format": "image,plaintext"
+ })
+ request_url = QUERY.format(request="query", data=url_str)
+
+ async with bot.http_session.get(request_url) as response:
+ json = await response.json(content_type='text/plain')
+
+ result = json["queryresult"]
+
+ if result["error"]:
+ # API key not set up correctly
+ if result["error"]["msg"] == "Invalid appid":
+ message = "Wolfram API key is invalid or missing."
+ log.warning(
+ "API key seems to be missing, or invalid when "
+ f"processing a wolfram request: {url_str}, Response: {json}"
+ )
+ await send_embed(ctx, message)
+ return
+
+ message = "Something went wrong internally with your request, please notify staff!"
+ log.warning(f"Something went wrong getting a response from wolfram: {url_str}, Response: {json}")
+ await send_embed(ctx, message)
+ return
+
+ if not result["success"]:
+ message = f"I couldn't find anything for {query}."
+ await send_embed(ctx, message)
+ return
+
+ if not result["numpods"]:
+ message = "Could not find any results."
+ await send_embed(ctx, message)
+ return
+
+ pods = result["pods"]
+ pages = []
+ for pod in pods[:MAX_PODS]:
+ subs = pod.get("subpods")
+
+ for sub in subs:
+ title = sub.get("title") or sub.get("plaintext") or sub.get("id", "")
+ img = sub["img"]["src"]
+ pages.append((title, img))
+ return pages
+
+
+class Wolfram(Cog):
+ """Commands for interacting with the Wolfram|Alpha API."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @group(name="wolfram", aliases=("wolf", "wa"), invoke_without_command=True)
+ @custom_cooldown(*STAFF_ROLES)
+ async def wolfram_command(self, ctx: Context, *, query: str) -> None:
+ """Requests all answers on a single image, sends an image of all related pods."""
+ url_str = parse.urlencode({
+ "i": query,
+ "appid": APPID,
+ })
+ query = QUERY.format(request="simple", data=url_str)
+
+ # Give feedback that the bot is working.
+ async with ctx.channel.typing():
+ async with self.bot.http_session.get(query) as response:
+ status = response.status
+ image_bytes = await response.read()
+
+ f = discord.File(BytesIO(image_bytes), filename="image.png")
+ image_url = "attachment://image.png"
+
+ if status == 501:
+ message = "Failed to get response"
+ footer = ""
+ color = Colours.soft_red
+ elif status == 400:
+ message = "No input found"
+ footer = ""
+ color = Colours.soft_red
+ elif status == 403:
+ message = "Wolfram API key is invalid or missing."
+ footer = ""
+ color = Colours.soft_red
+ else:
+ message = ""
+ footer = "View original for a bigger picture."
+ color = Colours.soft_orange
+
+ # Sends a "blank" embed if no request is received, unsure how to fix
+ await send_embed(ctx, message, color, footer=footer, img_url=image_url, f=f)
+
+ @wolfram_command.command(name="page", aliases=("pa", "p"))
+ @custom_cooldown(*STAFF_ROLES)
+ async def wolfram_page_command(self, ctx: Context, *, query: str) -> None:
+ """
+ Requests a drawn image of given query.
+
+ Keywords worth noting are, "like curve", "curve", "graph", "pokemon", etc.
+ """
+ pages = await get_pod_pages(ctx, self.bot, query)
+
+ if not pages:
+ return
+
+ embed = Embed()
+ embed.set_author(name="Wolfram Alpha",
+ icon_url=WOLF_IMAGE,
+ url="https://www.wolframalpha.com/")
+ embed.colour = Colours.soft_orange
+
+ await ImagePaginator.paginate(pages, ctx, embed)
+
+ @wolfram_command.command(name="cut", aliases=("c",))
+ @custom_cooldown(*STAFF_ROLES)
+ async def wolfram_cut_command(self, ctx: Context, *, query: str) -> None:
+ """
+ Requests a drawn image of given query.
+
+ Keywords worth noting are, "like curve", "curve", "graph", "pokemon", etc.
+ """
+ pages = await get_pod_pages(ctx, self.bot, query)
+
+ if not pages:
+ return
+
+ if len(pages) >= 2:
+ page = pages[1]
+ else:
+ page = pages[0]
+
+ await send_embed(ctx, page[0], colour=Colours.soft_orange, img_url=page[1])
+
+ @wolfram_command.command(name="short", aliases=("sh", "s"))
+ @custom_cooldown(*STAFF_ROLES)
+ async def wolfram_short_command(self, ctx: Context, *, query: str) -> None:
+ """Requests an answer to a simple question."""
+ url_str = parse.urlencode({
+ "i": query,
+ "appid": APPID,
+ })
+ query = QUERY.format(request="result", data=url_str)
+
+ # Give feedback that the bot is working.
+ async with ctx.channel.typing():
+ async with self.bot.http_session.get(query) as response:
+ status = response.status
+ response_text = await response.text()
+
+ if status == 501:
+ message = "Failed to get response"
+ color = Colours.soft_red
+ elif status == 400:
+ message = "No input found"
+ color = Colours.soft_red
+ elif response_text == "Error 1: Invalid appid":
+ message = "Wolfram API key is invalid or missing."
+ color = Colours.soft_red
+ else:
+ message = response_text
+ color = Colours.soft_orange
+
+ await send_embed(ctx, message, color)
+
+
+def setup(bot: commands.Bot) -> None:
+ """Load the Wolfram cog."""
+ bot.add_cog(Wolfram(bot))
diff --git a/bot/exts/halloween/candy_collection.py b/bot/exts/halloween/candy_collection.py
index 2c7d2f23..caf0df11 100644
--- a/bot/exts/halloween/candy_collection.py
+++ b/bot/exts/halloween/candy_collection.py
@@ -212,9 +212,9 @@ class CandyCollection(commands.Cog):
e = discord.Embed(colour=discord.Colour.blurple())
e.add_field(name="Top Candy Records", value=value, inline=False)
e.add_field(name='\u200b',
- value=f"Candies will randomly appear on messages sent. "
- f"\nHit the candy when it appears as fast as possible to get the candy! "
- f"\nBut beware the ghosts...",
+ value="Candies will randomly appear on messages sent. "
+ "\nHit the candy when it appears as fast as possible to get the candy! "
+ "\nBut beware the ghosts...",
inline=False)
await ctx.send(embed=e)
diff --git a/bot/resources/easter/starter.json b/bot/resources/easter/starter.json
deleted file mode 100644
index 31e2cbc9..00000000
--- a/bot/resources/easter/starter.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "starters": [
- "What is your favourite Easter candy or treat?",
- "What is your earliest memory of Easter?",
- "What is the title of the last book you read?",
- "What is better: Milk, Dark or White chocolate?",
- "What is your favourite holiday?",
- "If you could have any superpower, what would it be?",
- "Name one thing you like about a person to your right.",
- "If you could be anyone else for one day, who would it be?",
- "What Easter tradition do you enjoy most?",
- "What is the best gift you've been given?",
- "Name one famous person you would like to have at your easter dinner.",
- "What was the last movie you saw in a cinema?",
- "What is your favourite food?",
- "If you could travel anywhere in the world, where would you go?",
- "Tell us 5 things you do well.",
- "What is your favourite place that you have visited?",
- "What is your favourite color?",
- "If you had $100 bill in your Easter Basket, what would you do with it?",
- "What would you do if you know you could succeed at anything you chose to do?",
- "If you could take only three things from your house, what would they be?"
- ]
-}
diff --git a/bot/resources/evergreen/py_topics.yaml b/bot/resources/evergreen/py_topics.yaml
new file mode 100644
index 00000000..1e53429a
--- /dev/null
+++ b/bot/resources/evergreen/py_topics.yaml
@@ -0,0 +1,89 @@
+# Conversation starters for Python-related channels.
+
+# python-general
+267624335836053506:
+ - What's your favorite PEP?
+ - What's your current text editor/IDE, and what functionality do you like about it the most when programming in Python?
+ - What functionality is your text editor/IDE missing for programming Python?
+ - What parts of your life has Python automated, if any?
+ - Which Python project are you the most proud of making?
+ - What made you want to learn Python?
+ - When did you start learning Python?
+ - What reasons are you learning Python for?
+ - Where's the strangest place you've seen Python?
+ - How has learning Python changed your life?
+ - Is there a package you wish existed but doesn't? What is it?
+ - What feature do you think should be added to Python?
+ - Has Python helped you in school? If so, how?
+ - What was the first thing you created with Python?
+
+# async
+630504881542791169:
+ - Are there any frameworks you wish were async?
+ - How have coroutines changed the way you write Python?
+
+# c-extensions
+728390945384431688:
+ -
+
+# computer-science
+650401909852864553:
+ -
+
+# databases
+342318764227821568:
+ - Where do you get your best data?
+
+# data-science
+366673247892275221:
+ -
+
+# discord.py
+343944376055103488:
+ - What unique features does your bot contain, if any?
+ - What commands/features are you proud of making?
+ - What feature would you be the most interested in making?
+ - What feature would you like to see added to the library? what feature in the library do you think is redundant?
+ - Do you think there's a way in which Discord could handle bots better?
+
+# esoteric-python
+470884583684964352:
+ - What's a common part of programming we can make harder?
+ - What are the pros and cons of messing with __magic__()?
+
+# game-development
+660625198390837248:
+ -
+
+# microcontrollers
+545603026732318730:
+ -
+
+# networking
+716325106619777044:
+ - If you could wish for a library involving networking, what would it be?
+
+# security
+366674035876167691:
+ - If you could wish for a library involving net-sec, what would it be?
+
+# software-testing
+463035728335732738:
+ -
+
+# tools-and-devops
+463035462760792066:
+ - What editor would you recommend to a beginner? Why?
+ - What editor would you recommend to be the most efficient? Why?
+
+# unix
+491523972836360192:
+ -
+
+# user-interfaces
+338993628049571840:
+ - What's the most impressive Desktop Application you've made with Python so far?
+
+# web-development
+366673702533988363:
+ - How has Python helped you in web development?
diff --git a/bot/resources/evergreen/starter.yaml b/bot/resources/evergreen/starter.yaml
new file mode 100644
index 00000000..53c89364
--- /dev/null
+++ b/bot/resources/evergreen/starter.yaml
@@ -0,0 +1,22 @@
+# Conversation starters for channels that are not Python-related.
+
+- What is your favourite Easter candy or treat?
+- What is your earliest memory of Easter?
+- What is the title of the last book you read?
+- "What is better: Milk, Dark or White chocolate?"
+- What is your favourite holiday?
+- If you could have any superpower, what would it be?
+- Name one thing you like about a person to your right.
+- If you could be anyone else for one day, who would it be?
+- What Easter tradition do you enjoy most?
+- What is the best gift you've been given?
+- Name one famous person you would like to have at your easter dinner.
+- What was the last movie you saw in a cinema?
+- What is your favourite food?
+- If you could travel anywhere in the world, where would you go?
+- Tell us 5 things you do well.
+- What is your favourite place that you have visited?
+- What is your favourite color?
+- If you had $100 bill in your Easter Basket, what would you do with it?
+- What would you do if you know you could succeed at anything you chose to do?
+- If you could take only three things from your house, what would they be?
diff --git a/bot/resources/evergreen/trivia_quiz.json b/bot/resources/evergreen/trivia_quiz.json
index 6100ca62..8f0a4114 100644
--- a/bot/resources/evergreen/trivia_quiz.json
+++ b/bot/resources/evergreen/trivia_quiz.json
@@ -217,6 +217,36 @@
"question": "What does the acronym GPRS stand for?",
"answer": "General Packet Radio Service",
"info": "General Packet Radio Service (GPRS) is a packet-based mobile data service on the global system for mobile communications (GSM) of 3G and 2G cellular communication systems. It is a non-voice, high-speed and useful packet-switching technology intended for GSM networks."
+ },
+ {
+ "id": 131,
+ "question": "In what country is the Ebro river located?",
+ "answer": "Spain",
+ "info": "The Ebro river is located in Spain. It is 930 kilometers long and it's the second longest river that ends on the Mediterranean Sea."
+ },
+ {
+ "id": 132,
+ "question": "What year was the IBM PC model 5150 introduced into the market?",
+ "answer": "1981",
+ "info": "The IBM PC was introduced into the market in 1981. It used the Intel 8088, with a clock speed of 4.77 MHz, along with the MDA and CGA as a video card."
+ },
+ {
+ "id": 133,
+ "question": "What's the world's largest urban area?",
+ "answer": "Tokyo",
+ "info": "Tokyo is the most populated city in the world, with a population of 37 million people. It is located in Japan."
+ },
+ {
+ "id": 134,
+ "question": "How many planets are there in the Solar system?",
+ "answer": "8",
+ "info": "In the Solar system, there are 8 planets: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune. Pluto isn't considered a planet in the Solar System anymore."
+ },
+ {
+ "id": 135,
+ "question": "What is the capital of Iraq?",
+ "answer": "Baghdad",
+ "info": "Baghdad is the capital of Iraq. It has a population of 7 million people."
}
]
}
diff --git a/bot/utils/decorators.py b/bot/utils/decorators.py
index 519e61a9..9e6ef73d 100644
--- a/bot/utils/decorators.py
+++ b/bot/utils/decorators.py
@@ -285,7 +285,7 @@ def locked() -> t.Union[t.Callable, None]:
embed = Embed()
embed.colour = Colour.red()
- log.debug(f"User tried to invoke a locked command.")
+ log.debug("User tried to invoke a locked command.")
embed.description = (
"You're already using this command. Please wait until "
"it is done before you use it again."
diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py
index 9a7a0382..a4d0cc56 100644
--- a/bot/utils/pagination.py
+++ b/bot/utils/pagination.py
@@ -128,7 +128,7 @@ class LinePaginator(Paginator):
if not lines:
if exception_on_empty_embed:
- log.exception(f"Pagination asked for empty lines iterable")
+ log.exception("Pagination asked for empty lines iterable")
raise EmptyPaginatorEmbed("No lines to paginate")
log.debug("No lines to add to paginator, adding '(nothing to display)' message")
@@ -335,7 +335,7 @@ class ImagePaginator(Paginator):
if not pages:
if exception_on_empty_embed:
- log.exception(f"Pagination asked for empty image list")
+ log.exception("Pagination asked for empty image list")
raise EmptyPaginatorEmbed("No images to paginate")
log.debug("No images to add to paginator, adding '(no images to display)' message")
diff --git a/bot/utils/randomization.py b/bot/utils/randomization.py
new file mode 100644
index 00000000..8f47679a
--- /dev/null
+++ b/bot/utils/randomization.py
@@ -0,0 +1,23 @@
+import itertools
+import random
+import typing as t
+
+
+class RandomCycle:
+ """
+ Cycles through elements from a randomly shuffled iterable, repeating indefinitely.
+
+ The iterable is reshuffled after each full cycle.
+ """
+
+ def __init__(self, iterable: t.Iterable) -> None:
+ self.iterable = list(iterable)
+ self.index = itertools.cycle(range(len(iterable)))
+
+ def __next__(self) -> t.Any:
+ idx = next(self.index)
+
+ if idx == 0:
+ random.shuffle(self.iterable)
+
+ return self.iterable[idx]