Compare commits

..

142 Commits

Author SHA1 Message Date
Dusty.P
946590a3a1
Merge pull request #59 from dustinpianalto/pls-merge-me
Pls merge me
2018-06-21 10:49:08 -08:00
Dusty.P
222760f6a0
Merge branch 'development' into pls-merge-me 2018-06-21 10:48:39 -08:00
nya~
6f7e3b4bbc
Fixed another thingy regarding borked paths. 2018-06-21 19:35:02 +01:00
nya~
ba27d7c390
Removed a ) 2018-06-21 19:27:32 +01:00
nya~
0a9ab007e5
Fixed borked path for tags. 2018-06-21 19:26:58 +01:00
nya~
66d994023e
Removed junk from merge conflict issue. 2018-06-21 19:26:18 +01:00
nya~
08f1903871
Unborked a thing. 2018-06-21 19:25:12 +01:00
nya~
63d852f37b
Fixed syntax error. 2018-06-21 19:22:04 +01:00
davfsa
776b8d009c
Update bot_management.py 2018-06-21 20:19:16 +02:00
nya~
b189f2ca5e
Oops 2018-06-21 19:17:29 +01:00
nya~
1ddf767074
Logs and reports broken cogs on restart. 2018-06-21 19:17:05 +01:00
nya~
f4b0f03a23
Fixed path issue. 2018-06-21 19:10:34 +01:00
nya~
671e08ca5a
Update. (#2)
* Update sebi_machine_launcher.sh

* Update sebi_machine_launcher.sh

* Update requirements.txt

* removed numpy import

* Fixed a stupid typo.

* Fixed broken import someone didn't check.

* Updated bot_management.py

* Updated bot_management.py

* Update bot_management.py

* Update bot_management.py

* Update bot_management.py

* Added ban command

And changed the response because if you were talking without commas you would need to breathe heavily and and and and *huff*

* Update bot_management.py

* Aaa

wrong word lmao

* Update bot_management.py

* Update bot_management.py

* Update bot_management.py

* Update bot_management.py

* Update bot_management.py

* added new responses to agree

And added async2rw to the tutorials

* readded d.js-start

Because i am retarded and removed it

* Changed d.js-start back to d.js

Because im too dumb

* Update bot_management.py

* Update bot_management.py

* Fixed typos

* Update sebi_machine_launcher.sh

* Updated some files
2018-06-21 19:09:49 +01:00
davfsa
fe143678fe Updated some files 2018-06-21 20:04:07 +02:00
davfsa
8f70fcc142
Update sebi_machine_launcher.sh 2018-06-21 19:56:08 +02:00
davfsa
6b8bda9223 Merge branch 'neko404notfound-development' into development 2018-06-21 19:50:13 +02:00
davfsa
0a4baee373 Merge branch 'development' of https://github.com/neko404notfound/Sebi-Machine into neko404notfound-development 2018-06-21 19:49:24 +02:00
nya~
e62845ade8
Git pulls after four consecutive crashes. 2018-06-21 18:35:17 +01:00
davfsa
1bd8873803
Fixed typos 2018-06-21 19:33:34 +02:00
davfsa
3fa91bbcce
Update bot_management.py 2018-06-21 19:06:20 +02:00
davfsa
38b6ff65a4
Update bot_management.py 2018-06-21 18:58:49 +02:00
davfsa
057e1210b7
Merge pull request #58 from AggPro/patch-5
Changed d.js-start back to d.js
2018-06-21 16:03:11 +02:00
Agg
76b5d1df8f
Changed d.js-start back to d.js
Because im too dumb
2018-06-21 15:42:44 +03:00
davfsa
f2fd478dc8
Merge pull request #57 from AggPro/patch-4
readded d.js-start
2018-06-21 13:28:24 +02:00
Agg
b7f90c990e
readded d.js-start
Because i am retarded and removed it
2018-06-21 13:50:11 +03:00
davfsa
edfebad791
Merge pull request #56 from AggPro/patch-3
added new responses to agree
2018-06-21 12:25:51 +02:00
Agg
d5194bba3d
added new responses to agree
And added async2rw to the tutorials
2018-06-21 12:32:31 +03:00
davfsa
2a9c202436
Update bot_management.py 2018-06-21 09:07:41 +00:00
neko404notfound
4f5a1b518a Renamed src package to sebimachine.
- Gave the package a descriptive name.
- Passed over with black once more.
- Created setup.py to install dependencies.
- Updated author to reflect repo ownership to Dusty.
- Changed `git` command to use the __url__ attribute.
- Changed music to use ogg vorbis instead of mp3, purely for
    performance.
- Tried to make sure nothing broke.
- Updated dockerfile. Pretty sure we don't need it though...
2018-06-21 10:06:03 +01:00
davfsa
7f14b015ce
Update bot_management.py 2018-06-21 08:41:29 +00:00
davfsa
2eb5ac124c
Update bot_management.py 2018-06-21 08:40:23 +00:00
davfsa
788da95c11
Update bot_management.py 2018-06-21 08:31:36 +00:00
neko404notfound
001d6aa0ac Removed execution permission. 2018-06-21 09:31:02 +01:00
neko404notfound
6b44995333 Readded dockerfile as heroku uses it apparently..? 2018-06-21 09:28:25 +01:00
neko404notfound
1a4af0c766 Updated .gitignore and removed docker stuff, since we are not using it. 2018-06-21 09:26:41 +01:00
neko404notfound
5a67f1c732 Fixed license in package.json 2018-06-21 09:25:29 +01:00
neko404notfound
ff3b43c2e6 Removed package-lock, gitignored. 2018-06-21 09:24:41 +01:00
neko404notfound
dd3fd2ea68 Added .gitattributes 2018-06-21 09:23:31 +01:00
neko404notfound
8d579f5bb3 Changed CRLF to LF, passed over code with black to normalise formatting. 2018-06-21 09:21:54 +01:00
nya~
f6c4a5a570
Update (#1)
* Update sebi_machine_launcher.sh

* Update sebi_machine_launcher.sh

* Update requirements.txt

* removed numpy import

* Fixed a stupid typo.

* Fixed broken import someone didn't check.

* Updated bot_management.py

* Updated bot_management.py

* Update bot_management.py

* Update bot_management.py

* Update bot_management.py

* Added ban command

And changed the response because if you were talking without commas you would need to breathe heavily and and and and *huff*

* Update bot_management.py

* Aaa

wrong word lmao

* Update bot_management.py
2018-06-21 09:20:10 +01:00
davfsa
40a182cc43
Update bot_management.py 2018-06-21 08:18:16 +00:00
davfsa
113737f2fd
Merge pull request #54 from AggPro/patch-2
Aaa
2018-06-20 20:28:09 +02:00
Agg
9267acf8fb
Aaa
wrong word lmao
2018-06-20 21:27:51 +03:00
davfsa
871a5f73be
Update bot_management.py 2018-06-20 20:20:06 +02:00
davfsa
327a5e74d2
Merge pull request #53 from AggPro/patch-1
Added ban command
2018-06-20 20:19:09 +02:00
Agg
02c3f1e685
Added ban command
And changed the response because if you were talking without commas you would need to breathe heavily and and and and *huff*
2018-06-20 21:17:31 +03:00
davfsa
7321413c90
Update bot_management.py 2018-06-20 20:03:28 +02:00
davfsa
c6efae9635
Update bot_management.py 2018-06-20 20:01:45 +02:00
davfsa
239261eeaf
Update bot_management.py 2018-06-20 19:55:50 +02:00
davfsa
423edf1a3a Merge remote-tracking branch 'origin/development' into development 2018-06-20 19:49:43 +02:00
davfsa
6b1dd09a98 Updated bot_management.py 2018-06-20 19:49:32 +02:00
davfsa
0f80146172 Updated bot_management.py 2018-06-20 19:49:03 +02:00
davfsa
6ae7113f5d
Merge pull request #52 from neko404notfound/patch-2
Fixed broken import someone didn't check.
2018-06-20 19:43:45 +02:00
neko404notfound
cd286222c1
Fixed broken import someone didn't check. 2018-06-20 18:42:48 +01:00
Dusty.P
db0391d8af
Merge pull request #51 from neko404notfound/patch-1
Fixed a stupid typo.
2018-06-20 09:40:18 -08:00
neko404notfound
7f2f4a32ca
Fixed a stupid typo. 2018-06-20 18:39:47 +01:00
Dustin Pianalto
39838d3e79 removed numpy import 2018-06-20 09:39:22 -08:00
davfsa
a76394b568
Update requirements.txt 2018-06-20 19:15:32 +02:00
davfsa
3ebf0e22f8
Merge pull request #50 from neko404notfound/neko404notfound-venv-patch
Update sebi_machine_launcher.sh
2018-06-20 19:04:07 +02:00
neko404notfound
becc2d3ade
Update sebi_machine_launcher.sh 2018-06-20 18:03:24 +01:00
neko404notfound
6b9a9cc968
Update sebi_machine_launcher.sh 2018-06-20 18:00:16 +01:00
davfsa
09384a7efa
Merge pull request #49 from neko404notfound/development
Requirements and traps.
2018-06-20 18:57:01 +02:00
neko404notfound
76f7e0c36e Requirements and traps.
Requirements file was missing five requirements. This might be useful to
have fixed, so I sorted that out.
    - opuslib
    - youtube_dl
    - asyncpg
    - PyNaCl
    - dataclasses

Made the sebi_machine_launcher.sh executable only under the current
owner.

Added a SIGINT trap to sebi_machine_launcher.sh. You are only restarting
while the bot returns a non-truthy exit code (i.e. non zero). SIGINT,
which triggers the Python KeyboardInterrupt builtin exception being
raised will, if unhandled, result in a non-zero exit code. Thus, if you
provide a keyboard interrupt, you will just have the bot instantly
respawn. This is fine until you have a one second lag over SSH. Then my
wrist starts to hurt from spamming CTRL-C so many times until the bot
process dies.
2018-06-20 17:51:25 +01:00
davfsa
1f8856b277 Changed so that people with manage roles can add and delete tags 2018-06-20 17:48:14 +02:00
davfsa
5caf602f79 Changed discord.Member to discord.User in invite 2018-06-19 22:34:50 +02:00
davfsa
0fb83df922 Renamed start to tutorial 2018-06-19 22:15:29 +02:00
davfsa
5ae0bd4b20 Fixed indentation error 2018-06-19 22:08:28 +02:00
davfsa
8247bb5469 Merge remote-tracking branch 'origin/development' into development 2018-06-19 22:07:09 +02:00
davfsa
a8fd644218 Added a new command "finish"
Finished invite
2018-06-19 22:06:08 +02:00
davfsa
0c4b735e41 Added some files to gitignore 2018-06-19 22:05:09 +02:00
davfsa
ce8bd68193 Removed a useless command from moderation.py 2018-06-19 22:04:47 +02:00
Mr-Blob
6c2857e5c8
Update extensions.txt 2018-06-19 10:03:24 +00:00
Mr-Blob
a9cca0cff6
Create music.py 2018-06-19 10:03:04 +00:00
Mr-Blob
ccd6f14efc
Create noblock.py 2018-06-19 10:02:21 +00:00
Mr-Blob
844d3a4869
Create __init__.py 2018-06-19 10:00:36 +00:00
Dustin Pianalto
6c80b5832b pep8 2018-06-18 10:31:32 -08:00
Dustin Pianalto
3d508968d4 Merge remote-tracking branch 'origin/development' into development
# Conflicts:
#	src/cogs/bot_management.py
2018-06-18 10:30:37 -08:00
davfsa
315b48c235
Update bot_management.py 2018-06-18 20:30:13 +02:00
Dustin Pianalto
d906d375f7 pep8 2018-06-18 10:30:07 -08:00
davfsa
5736945447
Update bot_management.py 2018-06-18 08:24:15 +00:00
davfsa
5a01692f6b
Update bot_management.py 2018-06-16 11:32:07 +02:00
davfsa
b95269c234
Update bot_management.py 2018-06-16 11:27:14 +02:00
davfsa
7ce9b3dd52
Update bot_management.py 2018-06-16 11:23:38 +02:00
davfsa
86521e8e50
Update bot_management.py 2018-06-16 11:01:44 +02:00
davfsa
aef00702a7
Update bot_management.py 2018-06-16 11:01:06 +02:00
davfsa
c966615a80
Update bot_management.py 2018-06-16 11:00:03 +02:00
davfsa
0772281ab2
Fixed indentaion errors 2018-06-16 10:56:57 +02:00
davfsa
3f0252909f
Update bot_management.py 2018-06-16 10:55:32 +02:00
davfsa
313e103ef8
Added invite command 2018-06-16 10:50:14 +02:00
Dustin Pianalto
97524ce9bd Remove on_message 2018-06-16 00:12:46 -08:00
Dustin Pianalto
c43efe5801 Ignore self 2018-06-16 00:06:13 -08:00
davfsa
7aefcf5856
Fixed a little error 2018-06-16 10:04:13 +02:00
davfsa
79342626a2
Fixed a typo 2018-06-16 10:01:02 +02:00
davfsa
8e5f0c3fd7
Added a bot-invite "command" (still-testing) 2018-06-16 09:58:21 +02:00
davfsa
82978b1822
Added a bot-invite "command" (still-testing) 2018-06-16 09:50:39 +02:00
davfsa
62ca50b390
Update bot_management.py 2018-06-15 20:10:30 +02:00
davfsa
a90d7264bf
Upated bot_management
Added a option for people with manage_guild perms to change the claimed bots
2018-06-15 19:13:01 +02:00
davfsa
51aabdf9d7
Delete bot_management.py 2018-06-15 19:11:09 +02:00
davfsa
549f94a7c2
Updated bot_management.py
Added a way for people with manage_guild perms to claim and unclaim bots in the name of other users
2018-06-15 19:03:17 +02:00
Dustin Pianalto
f73d0b89d2 Added emojis for Paginator 2018-06-15 01:42:43 -08:00
Dustin Pianalto
3657977f45 Added Paginator and Book 2018-06-15 01:40:53 -08:00
Dustin Pianalto
69db0a402a Fixed typo 2018-06-15 00:54:46 -08:00
Dustin Pianalto
65ba76e42a Changed RuntimeError to Warning 2018-06-15 00:52:17 -08:00
Dustin Pianalto
db9e81ff67 Updated Error Handler 2018-06-15 00:43:58 -08:00
Dustin Pianalto
65d99cdd85 Updated Error Handler 2018-06-15 00:24:39 -08:00
raatty
7f0d31f4a5 i forgot an f ops 2018-06-15 20:14:08 +12:00
raatty
e9c6e57c98 listing other peoples claims 2018-06-15 20:12:37 +12:00
Dustin Pianalto
c054e1ea30 Fixed order of checks 2018-06-15 00:02:44 -08:00
raatty
25f37edce5 guild instead of server maybe? 2018-06-15 18:48:53 +12:00
raatty
4a4a65222e name instead of id 2018-06-15 18:46:01 +12:00
raatty
3c7d4bbc69 maybe fix? 2018-06-15 18:40:57 +12:00
raatty
c8873d5068 whowns command 2018-06-15 18:35:36 +12:00
Dustin Pianalto
1317b7eaf3 Added cooldowns to claim and unclaim 2018-06-14 10:50:41 -08:00
Dustin Pianalto
885c6f9309 Added command to list bots that are claimed by user 2018-06-14 10:50:27 -08:00
Dustin Pianalto
9dd3a349ca changed order of checks 2018-06-14 10:34:16 -08:00
Dustin Pianalto
79486a0af7 Set claim limit to 10 2018-06-14 10:31:21 -08:00
Dustin Pianalto
449c3bfe18 Add restart command and processing 2018-06-14 10:25:26 -08:00
Dustin Pianalto
3a76598766 Add wrapper script for easy restarts. 2018-06-14 10:18:40 -08:00
Dustin Pianalto
169f4d4379 Added bot_management to auto load list 2018-06-14 10:16:30 -08:00
Dustin Pianalto
81c26742da Merge remote-tracking branch 'origin/development' into development 2018-06-14 10:16:04 -08:00
Dustin Pianalto
5980d6c0ed Added Bot claiming/unclaiming 2018-06-14 10:15:42 -08:00
Dustin Pianalto
78939e9482 Add Error Color 2018-06-14 09:41:43 -08:00
Dusty.P
debe981c91
Merge pull request #47 from YashKandalkar/development
Fixed indentation in start command
2018-06-14 00:01:30 -08:00
Dusty.P
573dad92c4
Merge branch 'development' into development 2018-06-14 00:01:10 -08:00
Dusty.P
7cf1129624
Merge pull request #46 from AggPro/patch-2
fixed typo in readme
2018-06-13 23:54:49 -08:00
Dusty.P
b8d4cf292f
Merge pull request #45 from AggPro/patch-1
DMing help command
2018-06-13 23:54:01 -08:00
YashKandalkar
1fbcdcb6a9 fixed the bad indentation in start command 2018-06-12 21:58:39 +05:30
yash
210acde293
Merge pull request #1 from dustinpianalto/development
um updating my forked repo
2018-06-12 21:18:49 +05:30
Dusty.P
a30f2cc4ae Fix key error 2018-06-12 00:11:34 -08:00
Dusty.P
58bbd83f3f Merge remote-tracking branch 'origin/development' into development 2018-06-12 00:05:06 -08:00
Dusty.P
f8b0771520 Create DB connection 2018-06-12 00:04:51 -08:00
Agg
d263e6f096
fixed typo in readme
aaa
2018-06-12 09:39:30 +03:00
Agg
5e5fd19390
updated my pull request
so it says that the person gets dmed
2018-06-12 09:37:19 +03:00
Dustin Pianalto
049130e77d fixed extension name 2018-06-11 13:50:46 -08:00
Agg
5bccc2e569
DMing help command
d
2018-06-09 18:31:57 +03:00
Dustin Pianalto
22c6dd2a7d renamed BasicCommands to basic_commands 2018-06-07 09:23:39 -08:00
Dustin Pianalto
b2f2888b81 Merge remote-tracking branch 'origin/development' into development 2018-06-07 09:20:20 -08:00
Dustin Pianalto
0ae9b50a76 Changed load message 2018-06-07 09:20:04 -08:00
Dusty.P
bc2f573b15
Merge pull request #43 from YashKandalkar/development
Changed channel names to channel IDs in start command
2018-06-07 09:13:08 -08:00
YashKandalkaril.com
d8e8984af8 added my name in __init__.py 2018-06-07 18:39:32 +05:30
YashKandalkaril.com
d2a93aaf2f Changed channel names to channel IDs and added multiline strings 2018-06-07 18:32:01 +05:30
YashKandalkar
a38c24b618 Commiting to development branch, added 'start' command 2018-06-06 21:07:25 +05:30
44 changed files with 2368 additions and 948 deletions

7
.gitattributes vendored Normal file
View File

@ -0,0 +1,7 @@
*.py text eol=lf
*.js text eol=lf
*.sh text eol=lf
*.json text eol=lf
*.png binary
*.jpg binary

7
.gitignore vendored
View File

@ -117,4 +117,9 @@ venv.bak/
docker-compose.yml
.vscode/
node_modules/
node_modules/
*.xml
*.iml
# Lock file, I guess.
package-lock.json

View File

@ -27,13 +27,13 @@ This bot extends the rewrite version of discord.py. A couple of variables have b
Make sure this one is installed. Example:
```py
if self.bot.mainenance:
if self.bot.maintenance:
print('I am in the development branch')
if not self.bot.mainenance:
if not self.bot.maintenance:
print('I am in the master branch')
```
In other words. `self.mainenance` returns `False` in production and `True` in developer modes.
In other words. `self.maintenance` returns `False` in production and `True` in developer modes.
> `self.bot.embed_color`

View File

@ -16,4 +16,4 @@ RUN python3.6 -m pip install --upgrade pip && \
python3.6 -m pip install -r requirements.txt && \
python3.6 -m pip install -U git+https://github.com/Rapptz/discord.py@rewrite#egg=discord.py[voice]
cmd ["python3.6","-m","src"]
cmd ["python3.6","-m","sebimachine"]

59
package-lock.json generated
View File

@ -1,59 +0,0 @@
{
"name": "sebi-machine",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"discord.js": {
"version": "11.3.2",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.3.2.tgz",
"integrity": "sha512-Abw9CTMX3Jb47IeRffqx2VNSnXl/OsTdQzhvbw/JnqCyqc2imAocc7pX2HoRmgKd8CgSqsjBFBneusz/E16e6A==",
"requires": {
"long": "^4.0.0",
"prism-media": "^0.0.2",
"snekfetch": "^3.6.4",
"tweetnacl": "^1.0.0",
"ws": "^4.0.0"
}
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"prism-media": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.2.tgz",
"integrity": "sha512-L6yc8P5NVG35ivzvfI7bcTYzqFV+K8gTfX9YaJbmIFfMXTs71RMnAupvTQPTCteGsiOy9QcNLkQyWjAafY/hCQ=="
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"snekfetch": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz",
"integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw=="
},
"tweetnacl": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz",
"integrity": "sha1-cT2LgY2kIGh0C/aDhtBHnmb8ins="
},
"ws": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz",
"integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==",
"requires": {
"async-limiter": "~1.0.0",
"safe-buffer": "~5.1.0"
}
}
}
}

View File

@ -11,7 +11,7 @@
"url": "git+https://github.com/dustinpianalto/Sebi-Machine.git"
},
"author": "",
"license": "ISC",
"license": "MIT",
"bugs": {
"url": "https://github.com/dustinpianalto/Sebi-Machine/issues"
},

View File

@ -1,10 +1,16 @@
yarl<1.2
numpy==1.14.0
aiofiles
# aiomultiprocess
# aiosqlite
# asyncpg
# dataclasses
# cached_property
uvloop==0.9.1
aiohttp==3.2.1
aiohttp==3.2.1
# Someone forgot to add this in.
asyncpg
# Music cog requirements
opuslib
dataclasses
PyNaCl
youtube_dl
# Duh!
git+https://github.com/rapptz/discord.py@rewrite

42
sebi_machine_launcher.sh Normal file
View File

@ -0,0 +1,42 @@
#!/bin/bash
# 20th June 2018
# Esp: added a trap here, as it otherwise attempts to restart when given
# the interrupt signal. This is really annoying over SSH when I have
# a 1-second lag anyway.
trap "echo 'Received interrupt. Exiting.'; exit 0" SIGINT SIGTERM
# Also loads the venv if it is present.
[ -d .venv/bin ] && source .venv/bin/activate && echo "Entered venv." || echo "No venv detected."
function git-try-pull() {
git pull --all
}
FAIL_COUNTER=0
while true; do
if [ ${FAIL_COUNTER} -eq 4 ]; then
echo -e "\e[0;31mFailed four times in a row. Trying to repull.\e[0m"
git-try-pull
let FAIL_COUNTER=0
fi
# Just respawn repeatedly until sigint.
python3.6 -m sebimachine
EXIT_STATUS=${?}
if [ ${EXIT_STATUS} -ne 0 ]; then
let FAIL_COUNTER=${FAIL_COUNTER}+1
else
let FAIL_COUNTER=0
fi
# Added colouring to ensure the date of shutdown and the exit code stands
# out from the other clutter in the traceback that might have been output.
echo -e "\e[0;31m[$(date --utc)]\e[0m Sebi-Machine shutdown with error \e[0;31m${EXIT_STATUS}\e[0m. Restarting..." >&2
sleep 1
done

15
sebimachine/__init__.py Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
"""
Sebi-Machine.
"""
__author__ = "Dusty.P"
__contributors__ = (__author__, "Neko404NotFound", "Dusty.P", "davfsa", "YashKandalkar")
__license__ = "MIT"
__title__ = "Sebi-Machine"
__version__ = "0.0.1"
__repository__ = f"https://github.com/{__author__}/{__title__}"
__url__ = __repository__

188
sebimachine/__main__.py Normal file
View File

@ -0,0 +1,188 @@
# !/usr/bin/python
# -*- coding: utf8 -*-
"""
App entry point.
Something meaningful here, eventually.
"""
import asyncio
import json
import logging
import os
import random
import sys
import traceback
from typing import Dict
import discord
from discord.ext import commands
from .config.config import LoadConfig
from .shared_libs import database
from .shared_libs.ioutils import in_here
from .shared_libs.loggable import Loggable
# Init logging to output on INFO level to stderr.
logging.basicConfig(level="INFO")
REBOOT_FILE = "sebimachine/config/reboot"
# If uvloop is installed, change to that eventloop policy as it
# is more efficient
try:
# https://stackoverflow.com/a/45700730
if sys.platform == "win32":
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
logging.warning("Detected Windows. Changing event loop to ProactorEventLoop.")
else:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
del uvloop
except BaseException as ex:
logging.warning(
f"Could not load uvloop. {type(ex).__qualname__}: {ex};",
"reverting to default impl.",
)
else:
logging.info(f"Using uvloop for asyncio event loop policy.")
# Bot Class
# Might be worth moving this to it's own file?
class SebiMachine(commands.Bot, LoadConfig, Loggable):
"""This discord is dedicated to http://www.discord.gg/GWdhBSp"""
def __init__(self):
# Initialize and attach config / settings
LoadConfig.__init__(self)
commands.Bot.__init__(self, command_prefix=self.defaultprefix)
with open(in_here("config", "PrivateConfig.json")) as fp:
self.bot_secrets = json.load(fp)
self.db_con = database.DatabaseConnection(**self.bot_secrets["db-con"])
self.failed_cogs_on_startup = {}
self.book_emojis: Dict[str, str] = {
"unlock": "🔓",
"start": "",
"back": "",
"hash": "#\N{COMBINING ENCLOSING KEYCAP}",
"forward": "",
"end": "",
"close": "🇽",
}
# Load plugins
# Add your cog file name in this list
with open(in_here("extensions.txt")) as cog_file:
cogs = {f'sebimachine.cogs.{c.strip()}' for c in cog_file.readlines()}
for cog in cogs:
try:
self.load_extension(cog)
self.logger.info(f"Loaded: {cog}")
except (ModuleNotFoundError, ImportError) as ex:
logging.exception(f'Could not load {cog}', exc_info=(type(ex), ex, ex.__traceback__))
self.failed_cogs_on_startup[cog] = ex
async def on_ready(self):
"""On ready function"""
self.maintenance and self.logger.warning("MAINTENANCE ACTIVE")
if os.path.exists(REBOOT_FILE):
with open(REBOOT_FILE, "r") as f:
reboot = f.readlines()
if int(reboot[0]) == 1:
await self.get_channel(int(reboot[1])).send("Restart Finished.")
for cog, ex in self.failed_cogs_on_startup.items():
tb = ''.join(traceback.format_exception(type(ex), ex, ex.__traceback__))[-1500:]
await ctx.send(
f'FAILED TO LOAD {cog} BECAUSE OF {type(ex).__name__}: {ex}\n'
f'{tb}'
)
with open(REBOOT_FILE, "w") as f:
f.write(f"0")
async def on_command_error(self, ctx, error):
"""
The event triggered when an error is raised while invoking a command.
ctx : Context
error : Exception
"""
jokes = [
"I'm a bit tipsy, I took to many screenshots...",
"I am rushing to the 24/7 store to get myself anti-bug spray...",
"Organizing turtle race...",
"There is no better place then 127.0.0.1...",
"Recycling Hex Decimal...",
"No worry, I get fixed :^)...",
"R.I.P, press F for respect...",
"The bug repellent dit not work...",
"You found a bug in the program. Unfortunately the joke did not fit here, better luck next time...",
]
# CommandErrors triggered by other propagating errors tend to get wrapped. This means
# if we have a cause, we should probably consider unwrapping that so we get a useful
# message.
# If command is not found, return
em = discord.Embed(colour=self.error_color)
if isinstance(error, discord.ext.commands.errors.CommandNotFound):
em.title = "Command Not Found"
em.description = f"{ctx.prefix}{ctx.invoked_with} is not a valid command."
else:
error = error.__cause__ or error
tb = traceback.format_exception(
type(error), error, error.__traceback__, limit=2, chain=False
)
tb = "".join(tb)
joke = random.choice(jokes)
fmt = (
f"**`{self.defaultprefix}{ctx.command}`**\n{joke}\n\n**{type(error).__name__}:**:\n```py\n{tb}\n```"
)
em.title = (
f"**{type(error).__name__}** in command {ctx.prefix}{ctx.command}"
)
em.description = str(error)
await ctx.send(embed=em)
async def on_message(self, message):
# Make sure people can't change the username
if message.guild:
if message.guild.me.display_name != self.display_name:
try:
await message.guild.me.edit(nick=self.display_name)
except:
pass
else:
if (
"exec" in message.content
or "repl" in message.content
or "token" in message.content
) and message.author != self.user:
await self.get_user(351794468870946827).send(
f"{message.author.name} ({message.author.id}) is using me "
f"in DMs\n{message.content}"
)
# If author is a bot, ignore the message
if message.author.bot:
return
# Make sure the command get processed as if it was typed with lowercase
# Split message.content one first space
command = message.content.split(None, 1)
if command:
command[0] = command[0].lower()
message.content = " ".join(command)
message.content = " ".join(command)
# process command
await self.process_commands(message)
client = SebiMachine()
client.run(client.bot_secrets["bot-key"])

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,68 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
import asyncio
class BasicCommands:
def __init__(self, bot):
self.bot = bot
@commands.command()
async def tutorial(self, ctx):
await ctx.send(f"Hello, {ctx.author.display_name}. Welcome to Sebi's Bot Tutorials. \nFirst off, would you like a quick walkthrough on the server channels?")
channel_list = {'channel-1' : self.bot.get_channel(333149949883842561).mention,
'd.py-rewrite-start' : self.bot.get_channel(386419285439938560).mention,
'js-klasa-start' : self.bot.get_channel(341816240186064897).mention,
'async2rewrite-start' : self.bot.get_channel(392223495389577217).mention,
'd.js' : self.bot.get_channel(436771798303113217).mention}
bots_channels = (self.bot.get_channel(339112602867204097).mention,
self.bot.get_channel(411586546551095296).mention)
help_channels = (self.bot.get_channel(425315253153300488).mention,
self.bot.get_channel(392215236612194305).mention,
self.bot.get_channel(351034776985141250).mention)
def check(m):
return True if m.author.id == ctx.author.id and m.channel.id == ctx.channel.id else False
msg = await self.bot.wait_for('message', check = check, timeout = 15)
agree = ("yes", "yep", "non't", "ya", "ye", "yup", "ok", "why not")
if msg is None:
await ctx.send("Sorry, {ctx.author.mention}, you didn't reply on time. You can run the command again when you're free :)")
else:
if msg.content.lower() in agree:
async with ctx.typing():
await ctx.send("Alrighty-Roo... Check your DMs!")
await ctx.author.send("Alrighty-Roo...")
await ctx.author.send(f"To start making your bot from scratch, you first need to head over to {channel_list['channel-1']}"
" (Regardless of the language you're gonna use).")
await asyncio.sleep(0.5)
await ctx.author.send(f"After you have a bot account, you can either continue with {channel_list['d.py-rewrite-start']} "
f"if you want to make a bot in discord.py rewrite __or__ go to {channel_list['js-klasa-start']} or "
f"{channel_list['d.js']} for making a bot in JavaScript."
f"If you already have old Discord.py async code and you want to use it with the new Rewrite versions, head to {channel_list['async2rewrite-start']}")
await ctx.author.send("...Read all the tutorials and still need help? You have two ways to get help.")
await asyncio.sleep(1.5)
await ctx.author.send("**Method-1**\nThis is the best method of getting help. You help yourself.\n"
f"To do so, head over to a bots dedicated channel (either {bots_channels[0]} or {bots_channels[1]})"
" and type `?rtfm rewrite thing_you_want_help_with`.\nThis will trigger the bot R.Danny Bot and will "
"give you links on your query on the official discord.py rewrite docs. *PS: Let the page completely load*")
await asyncio.sleep(5)
await ctx.author.send("**Method-2**\nIf you haven't found anything useful with Method-1, feel free to ask your question "
f"in any of the related help channels. ({', '.join(help_channels)})\nMay the force be with you!!")
else:
return await ctx.send("Session terminated. You can run this command again whenever you want.")
def setup(bot):
bot.add_cog(BasicCommands(bot))

View File

@ -0,0 +1,189 @@
import discord
from discord.ext import commands
class BotManager:
def __init__(self, bot):
self.bot = bot
async def on_member_join(self, member):
# If the member is not a bot
if member.bot is False:
return
else:
# The member is a bot
bot_owner = member.guild.get_member(await self.bot.db_con.fetchval('select owner from bots where id = $1', member.id))
await bot_owner.add_roles(discord.utils.get(member.guild.roles, name='Bot Developers'))
await member.add_roles(discord.utils.get(member.guild.roles, name='Bots'))
try:
await member.edit(nick='[' + await self.bot.db_con.fetchval('select prefix from bots where id = $1', member.id)
+ '] ' + member.name)
except:
pass
async def on_member_remove(self, member):
# If the member is not a bot
if member.bot is False:
return
else:
# The member is a bot
await self.bot.db_con.execute('DELETE FROM bots WHERE id = $1', member.id)
async def on_member_ban(self, guild, user):
if member.bot is True:
return
else:
# I need to finish this
pass
@commands.command()
async def invite(self, ctx, bot=None, prefix=None):
bot = await ctx.bot.get_user_info(bot)
if not bot:
raise Warning('You must include the id of the bot you are trying to invite... Be exact.')
if not bot.bot:
raise Warning('You can only invite bots.')
if not prefix:
raise Warning('Please provide a prefix')
# Make sure that the bot has not been invited already and it is not being tested
if await self.bot.db_con.fetch('select count(*) from bots where id = $1', bot.id) == 1:
raise Warning('The bot has already been invited or is being tested')
await self.bot.db_con.execute('insert into bots (id, owner, prefix) values ($1, $2, $3)',
bot.id, ctx.author.id, prefix)
em = discord.Embed(colour=self.bot.embed_color)
em.title = "Hello {},".format(ctx.author.name)
em.description = "Thanks for inviting your bot! It will be tested and invited shortly. " \
"Please open your DMs if they are not already so the bot can contact " \
"you to inform you about the progress of the bot!"
await ctx.send(embed=em)
em = discord.Embed(title="Bot invite", colour=discord.Color(0x363941))
em.description = discord.utils.oauth_url(client_id, permissions=None, guild=ctx.guild)
em.set_thumbnail(url=bot.avatar_url)
em.add_field(name="Bot name", value=bot.name)
em.add_field(name="Bot id", value="`" + str(bot.id) + "`")
em.add_field(name="Bot owner", value=ctx.author.mention)
em.add_field(name="Bot prefix", value="`" + prefix + "`")
await ctx.bot.get_channel(459280759945953300).send(embed=em)
@commands.command(name='claim', aliases=['makemine', 'gimme'])
@commands.cooldown(1, 5, commands.BucketType.user)
async def _claim_bot(self, ctx, bot: discord.Member = None, prefix: str = None, owner: discord.Member = None):
if not bot:
raise Warning('You must include the name of the bot you are trying to claim... Be exact.')
if not bot.bot:
raise Warning('You can only claim bots.')
if not prefix:
if bot.display_name.startswith('['):
prefix = bot.display_name.split(']')[0].strip('[')
else:
raise Warning('Prefix not provided and can\'t be found in bot nick.')
if owner is not None and ctx.author.guild_permissions.manage_roles:
author_id = owner.id
else:
author_id = ctx.author.id
em = discord.Embed()
if await self.bot.db_con.fetchval('select count(*) from bots where owner = $1', author_id) >= 10:
em.colour = self.bot.error_color
em.title = 'Too Many Bots Claimed'
em.description = 'Each person is limited to claiming 10 bots as that is how ' \
'many bots are allowed by the Discord API per user.'
return await ctx.send(embed=em)
existing = await self.bot.db_con.fetchrow('select * from bots where id = $1', bot.id)
if not existing:
await self.bot.db_con.execute('insert into bots (id, owner, prefix) values ($1, $2, $3)',
bot.id, author_id, prefix)
em.colour = self.bot.embed_color
em.title = 'Bot Claimed'
em.description = f'You have claimed {bot.display_name} with a prefix of {prefix}\n' \
f'If there is an error please run command again to correct the prefix,\n' \
f'or {ctx.prefix}unclaim {bot.mention} to unclaim the bot.'
elif existing['owner'] and existing['owner'] != author_id:
em.colour = self.bot.error_color
em.title = 'Bot Already Claimed'
em.description = 'This bot has already been claimed by someone else.\n' \
'If this is actually your bot please let the guild Administrators know.'
elif existing['owner'] and existing['owner'] == author_id:
em.colour = self.bot.embed_color
em.title = 'Bot Already Claimed'
em.description = 'You have already claimed this bot.\n' \
'If the prefix you provided is different from what is already in the database' \
' it will be updated for you.'
if existing['prefix'] != prefix:
await self.bot.db_con.execute("update bots set prefix = $1 where id = $2", prefix, bot.id)
elif not existing['owner']:
await self.bot.db_con.execute('update bots set owner = $1, prefix = $2 where id = $3',
author_id, prefix, bot.id)
em.colour = self.bot.embed_color
em.title = 'Bot Claimed'
em.description = f'You have claimed {bot.display_name} with a prefix of {prefix}\n' \
f'If there is an error please run command again to correct the prefix,\n' \
f'or {ctx.prefix}unclaim {bot.mention} to unclaim the bot.'
else:
em.colour = self.bot.error_color
em.title = 'Something Went Wrong...'
await ctx.send(embed=em)
@commands.command(name='unclaim')
@commands.cooldown(1, 5, commands.BucketType.user)
async def _unclaim_bot(self, ctx, bot: discord.Member = None):
if not bot:
raise Warning('You must include the name of the bot you are trying to claim... Be exact.')
if not bot.bot:
raise Warning('You can only unclaim bots.')
em = discord.Embed()
existing = await self.bot.db_con.fetchrow('select * from bots where id = $1', bot.id)
if not existing or not existing['owner']:
em.colour = self.bot.error_color
em.title = 'Bot Not Found'
em.description = 'That bot is not claimed'
elif existing['owner'] != ctx.author.id and not ctx.author.guild_permissions.manage_roles:
em.colour = self.bot.error_color
em.title = 'Not Claimed By You'
em.description = 'That bot is claimed by someone else.\n' \
'You can\'t unclaim someone else\'s bot'
else:
await self.bot.db_con.execute('update bots set owner = null where id = $1', bot.id)
em.colour = self.bot.embed_color
em.title = 'Bot Unclaimed'
em.description = f'You have unclaimed {bot.display_name}\n' \
f'If this is an error please reclaim using\n' \
f'{ctx.prefix}claim {bot.mention} {existing["prefix"]}'
await ctx.send(embed=em)
@commands.command(name='listclaims', aliases=['claimed', 'mybots'])
@commands.cooldown(1, 5, commands.BucketType.user)
async def _claimed_bots(self, ctx, usr: discord.Member = None):
if usr is None:
usr = ctx.author
bots = await self.bot.db_con.fetch('select * from bots where owner = $1', usr.id)
if bots:
em = discord.Embed(title=f'{usr.display_name} has claimed the following bots:',
colour=self.bot.embed_color)
for bot in bots:
member = ctx.guild.get_member(int(bot['id']))
em.add_field(name=member.display_name, value=f'Stored Prefix: {bot["prefix"]}', inline=False)
else:
em = discord.Embed(title='You have not claimed any bots.',
colour=self.bot.embed_color)
await ctx.send(embed=em)
@commands.command(name='whowns')
async def _whowns(self, ctx, bot: discord.Member):
if not bot.bot:
await ctx.send('this commands only for bots')
else:
owner = await self.bot.db_con.fetchrow('select * from bots where id = $1', bot.id)
await ctx.send(ctx.guild.get_member(owner['owner']).display_name)
def setup(bot):
bot.add_cog(BotManager(bot))

248
sebimachine/cogs/code.py Normal file
View File

@ -0,0 +1,248 @@
from discord.ext import commands
import traceback
import discord
import inspect
import textwrap
from contextlib import redirect_stdout
import io
class REPL:
"""Python in Discords"""
def __init__(self, bot):
self.bot = bot
self._last_result = None
self.sessions = set()
def cleanup_code(self, content):
"""
Automatically removes code blocks from the code.
"""
# remove ```py\n```
if content.startswith("```") and content.endswith("```"):
return "\n".join(content.split("\n")[1:-1])
# remove `foo`
return content.strip("` \n")
def get_syntax_error(self, e):
if e.text is None:
return "{0.__class__.__name__}: {0}".format(e)
return "{0.text}{1:>{0.offset}}\n{2}: {0}".format(e, "^", type(e).__name__)
@commands.command(name="exec")
async def _eval(self, ctx, *, body: str = None):
"""
Execute python code in discord chat.
Only the owner of this bot can use this command.
Alias:
- exec
Usage:
- exec < python code >
Example:
- exec print(546132)
"""
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
if body is None:
return await ctx.send(
"Please, use\n"
f'`{self.bot.config["prefix"]}exec`\n\n'
"\n`\\`\\`\\`py\n[python code]\n\\`\\`\\`\n"
"to get the most out of the command"
)
env = {
"bot": self.bot,
"ctx": ctx,
"channel": ctx.message.channel,
"author": ctx.message.author,
"server": ctx.message.guild,
"message": ctx.message,
"_": self._last_result,
}
env.update(globals())
body = self.cleanup_code(body)
stdout = io.StringIO()
to_compile = "async def func():\n%s" % textwrap.indent(body, " ")
try:
exec(to_compile, env)
except SyntaxError as e:
try:
await ctx.send(f"```py\n{self.get_syntax_error(e)}\n```")
except Exception as e:
error = [
self.get_syntax_error(e)[i : i + 2000]
for i in range(0, len(self.get_syntax_error(e)), 2000)
]
for i in error:
await ctx.send(f"```py\n{i}\n```")
func = env["func"]
try:
with redirect_stdout(stdout):
ret = await func()
except Exception as e:
value = stdout.getvalue()
try:
await ctx.send(f"```py\n{value}{traceback.format_exc()}\n```")
except Exception as e:
error = [value[i : i + 2000] for i in range(0, len(value), 2000)]
for i in error:
await ctx.send(f"```py\n{i}\n```")
tracebackerror = [
traceback.format_exc()[i : i + 2000]
for i in range(0, len(traceback.format_exc()), 2000)
]
for i in tracebackerror:
await ctx.send(f"```py\n{i}\n```")
else:
value = stdout.getvalue()
if ret is None:
if value:
try:
await ctx.send(f"```py\n{value}\n```")
except Exception as e:
code = [value[i : i + 1980] for i in range(0, len(value), 1980)]
for i in code:
await ctx.send(f"```py\n{i}\n```")
else:
self._last_result = ret
try:
code = [value[i : i + 1980] for i in range(0, len(value), 1980)]
for i in code:
await ctx.send(f"```py\n{i}\n```")
except Exception as e:
code = [value[i : i + 1980] for i in range(0, len(value), 1980)]
for i in code:
await ctx.send(f"```py\n{i}\n```")
modifyd_ret = [ret[i : i + 1980] for i in range(0, len(ret), 1980)]
for i in modifyd_ret:
await ctx.send(f"```py\n{i}\n```")
@commands.command(hidden=True)
async def repl(self, ctx):
"""
Start a interactive python shell in chat.
Only the owner of this bot can use this command.
Usage:
- repl < python code >
Example:
- repl print(205554)
"""
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
msg = ctx.message
variables = {
"ctx": ctx,
"bot": self.bot,
"message": msg,
"server": msg.guild,
"channel": msg.channel,
"author": msg.author,
"_": None,
}
if msg.channel.id in self.sessions:
msg = await ctx.send(
"Already running a REPL session in this channel. Exit it with `quit`."
)
self.sessions.add(msg.channel.id)
await ctx.send("Enter code to execute or evaluate. `exit()` or `quit` to exit.")
while True:
response = await self.bot.wait_for(
"message",
check=lambda m: m.content.startswith("`")
and m.author == ctx.author
and m.channel == ctx.channel,
)
cleaned = self.cleanup_code(response.content)
if cleaned in ("quit", "exit", "exit()"):
msg = await ctx.send("Exiting.")
self.sessions.remove(msg.channel.id)
return
executor = exec
if cleaned.count("\n") == 0:
# single statement, potentially 'eval'
try:
code = compile(cleaned, "<repl session>", "eval")
except SyntaxError:
pass
else:
executor = eval
if executor is exec:
try:
code = compile(cleaned, "<repl session>", "exec")
except SyntaxError as e:
try:
await ctx.send(f"```Python\n{self.get_syntax_error(e)}\n```")
except Exception as e:
error = [
self.get_syntax_error(e)[i : i + 2000]
for i in range(0, len(self.get_syntax_error(e)), 2000)
]
for i in error:
await ctx.send(f"```Python\n{i}\n```")
variables["message"] = response
fmt = None
stdout = io.StringIO()
try:
with redirect_stdout(stdout):
result = executor(code, variables)
if inspect.isawaitable(result):
result = await result
except Exception as e:
value = stdout.getvalue()
await ctx.send(f"```Python\n{value}{traceback.format_exc()}\n```")
continue
else:
value = stdout.getvalue()
if result is not None:
fmt = "{}{}".format(value, result)
variables["_"] = result
elif value:
fmt = value
try:
if fmt is not None:
if len(fmt) > 1980:
code = [fmt[i : i + 1980] for i in range(0, len(fmt), 1980)]
for i in code:
await ctx.send(f"```py\n{i}\n```")
else:
await ctx.send(fmt)
except discord.Forbidden:
pass
except discord.HTTPException as e:
await ctx.send(f"Unexpected error: `{e}`")
def setup(bot):
bot.add_cog(REPL(bot))

View File

@ -1,119 +1,146 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
import traceback
import aiofiles
class Upload:
"""
CogName should be the name of the cog
"""
def __init__(self, bot):
self.bot = bot
print('upload loaded')
@commands.command()
async def reload(self, ctx, *, extension: str):
"""Reload an extension."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
extension = extension.lower()
try:
self.bot.unload_extension("src.cogs.{}".format(extension))
self.bot.load_extension("src.cogs.{}".format(extension))
except Exception as e:
traceback.print_exc()
await ctx.send(f'Could not reload `{extension}` -> `{e}`')
else:
await ctx.send(f'Reloaded `{extension}`.')
@commands.command()
async def reloadall(self, ctx):
"""Reload all extensions."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
try:
for extension in self.bot.extensions:
self.bot.unload_extension(extension)
self.bot.load_extension(extension)
await ctx.send(f"Reload success! :thumbsup:\n")
except Exception as e:
await ctx.send(f"Could not reload `{extension}` -> `{e}`.\n")
@commands.command()
async def unload(self, ctx, *, extension: str):
"""Unload an extension."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
extension = extension.lower()
try:
self.bot.unload_extension("src.cogs.{}".format(extension))
except Exception as e:
traceback.print_exc()
if ctx.message.author.id not in self.bot.owner_list:
await ctx.send(f'Could not unload `{extension}` -> `{e}`')
else:
await ctx.send(f'Unloaded `{extension}`.')
@commands.command()
async def load(self, ctx, *, extension: str):
"""Load an extension."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
extension = extension.lower()
try:
self.bot.load_extension("src.cogs.{}".format(extension))
except Exception as e:
traceback.print_exc()
await ctx.send(f'Could not unload `{extension}` -> `{e}`')
else:
await ctx.send(f'Loaded `{extension}`.')
@commands.command()
async def permunload(self, ctx, extension=None):
"""Disables permanently a cog."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
if cog is None:
return await ctx.send("Please provide a extension. Do `help permunload` for more info")
extension = extension.lower()
async with aiofiles.open("extension.txt") as fp:
lines=fp.readlines()
removed = False
async with aiofiles.open("extension.txt", "w") as fp:
for i in lines:
if i.replace("\n", "") != extension:
fp.write(i)
else:
removed = True
break
if removed is True:
try:
self.bot.unload_extension(extension)
except:
pass
return await ctx.send("Extension removed successfully")
await ctx.send("Extension not found")
def setup(bot):
bot.add_cog(Upload(bot))
#!/usr/bin/python
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
import traceback
import aiofiles
import os
class Upload:
"""
CogName should be the name of the cog
"""
def __init__(self, bot):
self.bot = bot
print("upload loaded")
@commands.command()
async def reload(self, ctx, *, extension: str):
"""Reload an extension."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
extension = extension.lower()
try:
self.bot.unload_extension("src.cogs.{}".format(extension))
self.bot.load_extension("src.cogs.{}".format(extension))
except Exception as e:
traceback.print_exc()
await ctx.send(f"Could not reload `{extension}` -> `{e}`")
else:
await ctx.send(f"Reloaded `{extension}`.")
@commands.command()
async def reloadall(self, ctx):
"""Reload all extensions."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
try:
for extension in self.bot.extensions:
self.bot.unload_extension(extension)
self.bot.load_extension(extension)
await ctx.send(f"Reload success! :thumbsup:\n")
except Exception as e:
await ctx.send(f"Could not reload `{extension}` -> `{e}`.\n")
@commands.command()
async def unload(self, ctx, *, extension: str):
"""Unload an extension."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
extension = extension.lower()
try:
self.bot.unload_extension("src.cogs.{}".format(extension))
except Exception as e:
traceback.print_exc()
if ctx.message.author.id not in self.bot.owner_list:
await ctx.send(f"Could not unload `{extension}` -> `{e}`")
else:
await ctx.send(f"Unloaded `{extension}`.")
@commands.command()
async def load(self, ctx, *, extension: str):
"""Load an extension."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
extension = extension.lower()
try:
self.bot.load_extension("src.cogs.{}".format(extension))
except Exception as e:
traceback.print_exc()
await ctx.send(f"Could not load `{extension}` -> `{e}`")
else:
await ctx.send(f"Loaded `{extension}`.")
@commands.command()
async def permunload(self, ctx, extension=None):
"""Disables permanently a cog."""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
if cog is None:
return await ctx.send(
"Please provide a extension. Do `help permunload` for more info"
)
extension = extension.lower()
async with aiofiles.open("extension.txt") as fp:
lines = fp.readlines()
removed = False
async with aiofiles.open("extension.txt", "w") as fp:
for i in lines:
if i.replace("\n", "") != extension:
fp.write(i)
else:
removed = True
break
if removed is True:
try:
self.bot.unload_extension(extension)
except:
pass
return await ctx.send("Extension removed successfully")
await ctx.send("Extension not found")
@commands.command(hidden=True)
async def reboot(self, ctx):
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
await ctx.send("Sebi-Machine is restarting.")
with open(f"src/config/reboot", "w") as f:
f.write(f"1\n{ctx.channel.id}")
# noinspection PyProtectedMember
os._exit(1)
def setup(bot):
bot.add_cog(Upload(bot))

View File

@ -1,24 +1,26 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
class CogName:
"""
CogName should be the name of the cog
"""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def ping(self, ctx):
"""Say pong"""
now = ctx.message.created_at
msg = await ctx.send('Pong')
sub = msg.created_at - now
await msg.edit(content=f'🏓Pong, **{sub.total_seconds() * 1000}ms**')
def setup(bot):
bot.add_cog(CogName(bot))
#!/usr/bin/python
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
class CogName:
"""
CogName should be the name of the cog
"""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def ping(self, ctx):
"""Say pong"""
now = ctx.message.created_at
msg = await ctx.send("Pong")
sub = msg.created_at - now
await msg.edit(content=f"🏓Pong, **{sub.total_seconds() * 1000}ms**")
def setup(bot):
bot.add_cog(CogName(bot))

View File

@ -1,44 +1,47 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
import random
import aiohttp
class Fun:
"""
CogName should be the name of the cog
"""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def sebisauce(self, ctx):
"""
Get a image related to Sebi.
Sebi is a random guy with perfect code related jokes.
Usage:
- sebisauce
"""
await ctx.trigger_typing()
url = 'http://ikbengeslaagd.com/API/sebisauce.json'
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
source = await response.json(encoding='utf8')
total_sebi = 0
for key in dict.keys(source):
total_sebi += 1
im = random.randint(0, int(total_sebi) - 1)
await ctx.send(embed=discord.Embed(
title='\t',
description='\t',
color=self.bot.embed_color).set_image(
url=source[str(im)]))
def setup(bot):
bot.add_cog(Fun(bot))
#!/usr/bin/python
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
import random
import aiohttp
class Fun:
"""
CogName should be the name of the cog
"""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def sebisauce(self, ctx):
"""
Get a image related to Sebi.
Sebi is a random guy with perfect code related jokes.
Usage:
- sebisauce
"""
await ctx.trigger_typing()
url = "http://ikbengeslaagd.com/API/sebisauce.json"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
source = await response.json(encoding="utf8")
total_sebi = 0
for key in dict.keys(source):
total_sebi += 1
im = random.randint(0, int(total_sebi) - 1)
await ctx.send(
embed=discord.Embed(
title="\t", description="\t", color=self.bot.embed_color
).set_image(url=source[str(im)])
)
def setup(bot):
bot.add_cog(Fun(bot))

View File

@ -29,8 +29,10 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import discord
from discord.ext import commands
from src.shared_libs.utils import paginate, run_command
from src.shared_libs.loggable import Loggable
from sebimachine.shared_libs.utils import paginate, run_command
from sebimachine.shared_libs.loggable import Loggable
from sebimachine import __url__
import asyncio
@ -41,53 +43,73 @@ class Git(Loggable):
@commands.group(case_insensitive=True, invoke_without_command=True)
async def git(self, ctx):
"""Run help git for more info"""
await ctx.send('https://github.com/dustinpianalto/Sebi-Machine/')
@commands.command(case_insensitive=True, brief='Gets the Trello link.')
# await ctx.send("https://github.com/dustinpianalto/Sebi-Machine/")
await ctx.send(__url__ or "No URL specified in __init__.py")
@commands.command(case_insensitive=True, brief="Gets the Trello link.")
async def trello(self, ctx):
await ctx.send('<https://trello.com/b/x02goBbW/sebis-bot-tutorial-roadmap>')
await ctx.send("<https://trello.com/b/x02goBbW/sebis-bot-tutorial-roadmap>")
@git.command()
async def pull(self, ctx):
self.logger.warning('Invoking git-pull')
self.logger.warning("Invoking git-pull")
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
em = discord.Embed(style='rich',
title=f'Git Pull',
color=self.bot.embed_color)
em.set_thumbnail(url=f'{ctx.guild.me.avatar_url}')
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
em = discord.Embed(style="rich", title=f"Git Pull", color=self.bot.embed_color)
em.set_thumbnail(url=f"{ctx.guild.me.avatar_url}")
# Pretty sure you can just do await run_command() if that is async,
# or run in a TPE otherwise.
result = await asyncio.wait_for(self.bot.loop.create_task(
run_command('git fetch --all')), 120) + '\n'
result += await asyncio.wait_for(self.bot.loop.create_task(
run_command('git reset --hard origin/$(git rev-parse '
'--symbolic-full-name --abbrev-ref HEAD)')),
120) + '\n\n'
result += await asyncio.wait_for(self.bot.loop.create_task(
run_command('git show --stat | sed "s/.*@.*[.].*/ /g"')), 10)
result = (
await asyncio.wait_for(
self.bot.loop.create_task(run_command("git fetch --all")), 120
)
+ "\n"
)
result += (
await asyncio.wait_for(
self.bot.loop.create_task(
run_command(
"git reset --hard origin/$(git rev-parse "
"--symbolic-full-name --abbrev-ref HEAD)"
)
),
120,
)
+ "\n\n"
)
result += await asyncio.wait_for(
self.bot.loop.create_task(
run_command('git show --stat | sed "s/.*@.*[.].*/ /g"')
),
10,
)
results = paginate(result, maxlen=1014)
for page in results[:5]:
em.add_field(name='\uFFF0', value=f'{page}')
em.add_field(name="\uFFF0", value=f"{page}")
await ctx.send(embed=em)
@git.command()
async def status(self, ctx):
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
em = discord.Embed(style='rich',
title=f'Git Status',
color=self.bot.embed_color)
em.set_thumbnail(url=f'{ctx.guild.me.avatar_url}')
result = await asyncio.wait_for(self.bot.loop.create_task(
run_command('git status')), 10)
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
em = discord.Embed(
style="rich", title=f"Git Status", color=self.bot.embed_color
)
em.set_thumbnail(url=f"{ctx.guild.me.avatar_url}")
result = await asyncio.wait_for(
self.bot.loop.create_task(run_command("git status")), 10
)
results = paginate(result, maxlen=1014)
for page in results[:5]:
em.add_field(name='\uFFF0', value=f'{page}')
em.add_field(name="\uFFF0", value=f"{page}")
await ctx.send(embed=em)

View File

@ -0,0 +1,78 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
class Moderation:
"""
Moderation Commands
"""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def kick(self, ctx, member: discord.Member = None):
"""
Kick a discord member from your server.
Only contributors can use this command.
Usage:
- kick <discord.member>
"""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
if member is None:
await ctx.send("Are you sure you are capable of this command?")
try:
await member.kick()
await ctx.send(
f"You kicked **`{member.name}`** from **`{ctx.guild.name}`**"
)
except Exception as e:
await ctx.send(
"You may not use this command, as you do not have permission to do so:\n\n**`{ctx.guild.name}`**"
f"\n\n```py\n{e}\n```"
)
@commands.command()
async def ban(self, ctx, member: discord.Member = None):
"""
Ban a discord member from your server.
Only contributors can use this command.
Usage:
- ban <discord.member>
"""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send(
"Only my contributors can use me like this :blush:", delete_after=10
)
if member is None:
await ctx.send("Are you sure you are capable of this command?")
try:
await member.ban()
await ctx.send(
f"You banned **`{member.name}`** from **`{ctx.guild.name}`**"
)
except Exception as e:
await ctx.send(
"You may not use this command, as you do not have permission to do so:\n\n**`{ctx.guild.name}`**"
f"\n\n```py\n{e}\n```"
)
def setup(bot):
bot.add_cog(Moderation(bot))

329
sebimachine/cogs/music.py Normal file
View File

@ -0,0 +1,329 @@
import asyncio
import inspect
import traceback
import weakref
from typing import Dict
import async_timeout
import dataclasses
import discord
from discord.ext import commands
import youtube_dl
# noinspection PyUnresolvedReferences,PyUnresolvedReferences,PyPackageRequirements
from .utils import noblock
YT_DL_OPTS = {
"format": "ogg[abr>0]/bestaudio/best",
"ignoreerrors": True,
"default_search": "auto",
"source_address": "0.0.0.0",
"quiet": True,
}
# Let it be waiting on an empty queue for about 30 minutes
# before closing the connection from being idle.
IDLE_FOR = 60 * 30
@dataclasses.dataclass(repr=True)
class Request:
"""Track request."""
who: discord.Member
what: str # Referral
title: str # Video title
actual_url: str # Actual URL to play
def __str__(self):
return self.title
def __hash__(self):
return hash(str(self.who.id) + self.what)
# noinspection PyBroadException
class Session:
"""
Each player being run is a session; (E.g. if you open a player in one server and I did in another).
Sessions will have a queue, an event that can fire to stop the current track and move on, and a voice
channel to bind to. This is defined as the voice channel the owner of the session was in when they made the channel.
To create a session, call ``Session.new_session``. Do not call the constructor directly.
Attributes:
ctx: discord.ext.commands.Context
The context of the original command invocation we are creating a session for.
loop: asyncio.AbstractEventLoop
The event loop to run this in.
voice_client: discord.VoiceClient
Voice client we are streaming audio through.
queue: asyncio.Queue
Track queue.
"""
@classmethod
async def new_session(cls, ctx: commands.Context):
"""
Helper to make a new session. Invoke constructor using this, as it handles any errors. It also ensures
we connect immediately.
"""
try:
s = cls(ctx)
await s.connect()
except Exception as ex:
traceback.print_exc()
await ctx.send(
f"I couldn't connect! Reason: {str(ex) or type(ex).__qualname__}"
)
return None
else:
return s
def __init__(self, ctx: commands.Context) -> None:
"""Create a new session."""
if ctx.author.voice is None:
raise RuntimeError("Please enter a voice channel I have access to first.")
# Holds the tasks currently running associated with this.
self.voice_channel = ctx.author.voice.channel
self.ctx: commands.Context = ctx
self.voice_client: discord.VoiceClient = None
self.loop: asyncio.AbstractEventLoop = weakref.proxy(self.ctx.bot.loop)
self.queue = asyncio.Queue()
# Lock-based event to allow firing a handler to advance to the next track.
self._start_next_track_event = asyncio.Event()
self._on_stop_event = asyncio.Event()
self._player: asyncio.Task = None
self._track: asyncio.Task = None
@property
def is_connected(self) -> bool:
return self.voice_client and self.voice_client.is_connected()
async def connect(self) -> None:
"""Connects to the VC."""
if not self.is_connected and not self._player:
# noinspection PyUnresolvedReferences
self.voice_client = await self.voice_channel.connect()
self._start_next_track_event.clear()
self._player = self.__spawn_player()
else:
raise RuntimeError("I already have a voice client/player running.")
async def disconnect(self) -> None:
"""Disconnects from the VC."""
await self.voice_client.disconnect()
self.voice_client = None
def __spawn_player(self) -> asyncio.Task:
"""Starts a new player."""
async def player():
try:
while True:
# Wait on an empty queue for a finite period of time.
with async_timeout.timeout(IDLE_FOR):
request = await self.queue.get()
await self.ctx.send(
f"Playing `{request}` requested by {request.who}"
)
# Clear the skip event if it is set.
self._start_next_track_event.clear()
# Start the player if it was a valid request, else continue to the next track.
if not self.__play(request.actual_url):
await self.ctx.send(
f"{request.referral} was a bad request and was skipped."
)
continue
await self._start_next_track_event.wait()
if self.voice_client.is_playing():
self.voice_client.stop()
except asyncio.CancelledError:
# Hit when someone kills the player using stop().
print("Requested to stop player", repr(self))
except asyncio.TimeoutError:
await self.ctx.send("Was idle for too long...")
print("Player queue was empty for too long and was stopped", repr(self))
except Exception:
traceback.print_exc()
finally:
if self.voice_client.is_playing():
await self.voice_client.stop()
if self.is_connected:
await self.disconnect()
return self.loop.create_task(player())
def __play(self, url):
"""Tries to play the given URL. If it fails, we return False, else we return True."""
try:
ffmpeg_player = discord.FFmpegPCMAudio(url)
# Play the stream. After we finish, either from being cancelled or otherwise, fire the
# skip track event to start the next track.
self.voice_client.play(
ffmpeg_player, after=lambda error: self._start_next_track_event.set()
)
except Exception:
traceback.print_exc()
return False
else:
return True
def skip(self):
"""Request to skip track."""
self._start_next_track_event.set()
def stop(self):
"""Request to stop playing."""
if self._player:
self._player.cancel()
self._on_stop_event.set()
self._on_stop_event.clear()
def on_exit(self, func):
"""Decorates a function to invoke it on exit."""
async def callback():
await self._on_stop_event.wait()
inspect.iscoroutinefunction(func) and await func() or func()
self.loop.create_task(callback())
return func
# noinspection PyBroadException
class PlayerCog:
def __init__(self):
self.sessions: Dict[discord.Guild, Session] = {}
# noinspection PyMethodMayBeStatic
async def __local_check(self, ctx):
return ctx.guild
@commands.command()
async def join(self, ctx):
if ctx.guild not in self.sessions:
p = await Session.new_session(ctx)
if p:
self.sessions[ctx.guild] = p
@p.on_exit
def when_terminated():
try:
self.sessions.pop(ctx.guild)
finally:
return
await ctx.send("*hacker voice*\n**I'm in.**", delete_after=15)
else:
await ctx.send(
f"I am already playing in {self.sessions[ctx.guild].voice_channel.mention}"
)
# noinspection PyNestedDecorators
@staticmethod
@noblock.no_block
def _get_video_meta(referral):
downloader = youtube_dl.YoutubeDL(YT_DL_OPTS)
info = downloader.extract_info(referral, download=False)
return info
@commands.command()
async def queue(self, ctx):
if ctx.guild not in self.sessions:
return await ctx.send("Please join me into a voice channel first.")
sesh = self.sessions[ctx.guild]
if sesh.queue.empty():
return await ctx.send(
"There is nothing in the queue at the moment!\n\n"
"Add something by running `<>play https://url` or `<>play search term`!"
)
# We cannot faff around with the actual queue so make a shallow copy of the internal
# non-async dequeue.
# noinspection PyProtectedMember
agenda = sesh.queue._queue.copy()
message = ["**Queue**"]
for i, item in enumerate(list(agenda)[:15]):
message.append(f"`{i+1: >2}: {item.title} ({item.who})`")
if len(agenda) >= 15:
message.append("")
message.append(f"There are {len(agenda)} items in the queue currently.")
await ctx.send("\n".join(message)[:2000])
@commands.command()
async def play(self, ctx, *, referral):
if ctx.guild not in self.sessions:
return await ctx.send("Please join me into a voice channel first.")
try:
try:
info = await self._get_video_meta(referral)
# If it was interpreted as a search, it appears this happens?
# The documentation is so nice.
if info.get("_type") == "playlist":
info = info["entries"][0]
# ...wait... did I say nice? I meant "non existent."
url = info["url"]
title = info.get("title") or referral
except IndexError:
return await ctx.send("No results...", delete_after=15)
except Exception as ex:
return await ctx.send(
f"Couldn't add this to the queue... reason: {ex!s}"
)
await self.sessions[ctx.guild].queue.put(
Request(ctx.author, referral, title, url)
)
await ctx.send(f"Okay. Queued `{title or referral}`.")
except KeyError:
await ctx.send("I am not playing in this server.")
@commands.command()
async def stop(self, ctx):
try:
await self.sessions[ctx.guild].stop()
except KeyError:
await ctx.send("I am not playing in this server.")
except TypeError:
await ctx.send("I wasn't playing anything, but okay.", delete_after=15)
@commands.command()
async def skip(self, ctx):
try:
self.sessions[ctx.guild].skip()
try:
await ctx.message.add_reaction("\N{OK HAND SIGN}")
except discord.Forbidden:
await ctx.send("\N{OK HAND SIGN}")
except KeyError:
await ctx.send("I am not playing in this server.")
@commands.command()
async def disconnect(self, ctx):
await self.sessions[ctx.guild].stop()
await self.disconnect()
def setup(bot):
bot.add_cog(PlayerCog())

106
sebimachine/cogs/tag.py Normal file
View File

@ -0,0 +1,106 @@
import discord
from discord.ext import commands
import json
import aiofiles
import asyncio
class Tag:
def __init__(self, bot):
self.bot = bot
with open("sebimachine/shared_libs/tags.json", "r") as fp:
json_data = fp.read()
global tags
tags = json.loads(json_data)
@commands.group(case_insensitive=True, invoke_without_command=True)
async def tag(self, ctx, tag=None):
"""Gets a tag"""
await ctx.trigger_typing()
if tag is None:
return await ctx.send(
"Please provide a argument. Do `help tag` for more info"
)
found = tags.get(tag, None)
if found is None:
return await ctx.send("Tag not found")
await ctx.send(found)
@tag.command(case_insensitive=True)
async def list(self, ctx):
"""Lists available tags"""
await ctx.trigger_typing()
desc = ""
for i in tags:
desc = desc + i + "\n"
if desc == "":
desc = "None"
em = discord.Embed(
title="Available tags:", description=desc, colour=discord.Colour(0x00FFFF)
)
await ctx.send(embed=em)
@tag.command(case_insensitive=True)
async def add(self, ctx, tag_name=None, *, tag_info=None):
"""Adds a new tag"""
await ctx.trigger_typing()
if not ctx.author.guild_permissions.manage_roles:
return await ctx.send("You are not allowed to do this")
if tag_name is None or tag_info is None:
return await ctx.send(
"Please provide a tag name and the tag info. Do `help tag` for more info"
)
exists = False
for i in tags:
if i == tag_name:
exists = True
if not exists:
tags.update({tag_name: tag_info})
async with aiofiles.open("src/shared_libs/tags.json", "w") as fp:
json_data = json.dumps(tags)
await fp.write(json_data)
return await ctx.send("The tag has been added")
await ctx.send("The tag already exists")
@tag.command(case_insensitive=True)
async def remove(self, ctx, tag=None):
"""Remove a existing tag"""
await ctx.trigger_typing()
if not ctx.author.guild_permissions.manage_roles:
return await ctx.send("You are not allowed to do this")
if tag is None:
return await ctx.send(
"Please provide a tag name and the tag info. Do `help tag` for more info"
)
found = None
for i in tags:
if i == tag:
found = i
if found is not None:
del tags[found]
async with aiofiles.open("src/shared_libs/tags.json", "w") as fp:
json_data = json.dumps(tags)
await fp.write(json_data)
return await ctx.send("The tag has been removed")
await ctx.send("The tag has not been found")
def setup(bot):
bot.add_cog(Tag(bot))

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,15 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
import asyncio
import functools
def no_block(func):
"""Turns a blocking function into a non-blocking coroutine function."""
@functools.wraps(func)
async def no_blocking_handler(*args, **kwargs):
partial = functools.partial(func, *args, **kwargs)
return await asyncio.get_event_loop().run_in_executor(None, partial)
return no_blocking_handler

View File

@ -5,13 +5,15 @@ import json
import discord
import os
class LoadConfig:
"""
All config is collected here
"""
def __init__(self):
# Read our config file
with open('src/config/Config.json') as fp:
with open("sebimachine/config/Config.json") as fp:
self.config = json.load(fp)
# Initialize config
@ -21,8 +23,9 @@ class LoadConfig:
self.version = self.config["version"]
self.display_name = self.config["display_name"]
self.maintenance = self.config["maintenance"]
self.embed_color = discord.Color(0x00FFFF)
if self.maintenance == 'False':
self.embed_color = discord.Colour(0x00FFFF)
self.error_color = discord.Colour(0xFF0000)
if self.maintenance == "False":
self.maintenance = False
else:
self.maintenance = True

View File

@ -5,3 +5,6 @@ code
git
fun
moderation
basic_commands
bot_management
music

View File

@ -1,6 +1,6 @@
{
"sar": "sar",
"selfrole": "sar",
"selfroles": "sar"
{
"sar": "sar",
"selfrole": "sar",
"selfroles": "sar"
}

View File

@ -0,0 +1,37 @@
import asyncio
import asyncpg
class DatabaseConnection:
def __init__(
self,
host: str = "localhost",
port: int = 5432,
database: str = "",
user: str = "",
password: str = "",
):
if user == "" or password == "" or database == "":
raise RuntimeError("Username or Password are blank")
self.kwargs = {
"host": host,
"port": port,
"database": database,
"user": user,
"password": password,
}
self._conn = None
asyncio.get_event_loop().run_until_complete(self.acquire())
self.fetchval = self._conn.fetchval
self.execute = self._conn.execute
self.fetch = self._conn.fetch
self.fetchrow = self._conn.fetchrow
async def acquire(self):
if not self._conn:
self._conn = await asyncpg.create_pool(**self.kwargs)
async def close(self):
await self._conn.close()
self._conn = None

View File

@ -8,10 +8,10 @@ import inspect
import os
__all__ = ('in_here',)
__all__ = ("in_here",)
def in_here(first_path_bit: str, *path_bits: str, stack_depth: int=0) -> str:
def in_here(first_path_bit: str, *path_bits: str, stack_depth: int = 0) -> str:
"""
A somewhat voodooish and weird piece of code. This enables us to
directly refer to a file in the same directory as the code that
@ -36,16 +36,17 @@ def in_here(first_path_bit: str, *path_bits: str, stack_depth: int=0) -> str:
we expect this to be called in. Affects the relative directory
that is used.
:returns: the absolute path to the given relative path provided.
"""
"""
try:
frame = inspect.stack()[1 + stack_depth]
except IndexError:
raise RuntimeError('Could not find a stack record. Interpreter has '
'been shot.')
raise RuntimeError(
"Could not find a stack record. Interpreter has " "been shot."
)
else:
module = inspect.getmodule(frame[0])
assert hasattr(module, '__file__'), 'No `__file__\' attr, welp.'
module = inspect.getmodule(frame[0])
assert hasattr(module, "__file__"), "No `__file__' attr, welp."
# https://docs.python.org/3/library/inspect.html#the-interpreter-stack
# If Python caches strings rather than copying when we move them
# around or modify them, then this may cause a referential cycle which

View File

@ -14,11 +14,11 @@ boast faster lookups.
import logging
__all__ = ('Loggable',)
__all__ = ("Loggable",)
class Loggable:
__slots__ = ('logger',)
__slots__ = ("logger",)
def __init_subclass__(cls, **_):
cls.logger = logging.getLogger(cls.__qualname__)

View File

@ -0,0 +1,454 @@
"""
Utility for creating Paginated responses
#####################################################################################
# #
# MIT License #
# #
# Copyright (c) 2018 Dusty.P https://github.com/dustinpianalto #
# #
# 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 typing
import discord
class Paginator:
def __init__(
self,
bot: discord.ext.commands.Bot,
*,
max_chars: int = 1970,
max_lines: int = 20,
prefix: str = "```md",
suffix: str = "```",
page_break: str = "\uFFF8",
field_break: str = "\uFFF7",
field_name_char: str = "\uFFF6",
inline_char: str = "\uFFF5",
max_line_length: int = 100,
embed=False,
):
_max_len = 6000 if embed else 1980
assert 0 < max_lines <= max_chars
assert 0 < max_line_length < 120
self._parts = list()
self._prefix = prefix
self._suffix = suffix
self._max_chars = (
max_chars
if max_chars + len(prefix) + len(suffix) + 2 <= _max_len
else _max_len - len(prefix) - len(suffix) - 2
)
self._max_lines = max_lines - (prefix + suffix).count("\n") + 1
self._page_break = page_break
self._max_line_length = max_line_length
self._pages = list()
self._max_field_chars = 1014
self._max_field_name = 256
self._max_description = 2048
self._embed = embed
self._field_break = field_break
self._field_name_char = field_name_char
self._inline_char = inline_char
self._embed_title = ""
self._embed_description = ""
self._embed_color = None
self._embed_thumbnail = None
self._embed_url = None
self._bot = bot
def set_embed_meta(
self,
title: str = None,
description: str = None,
color: discord.Colour = None,
thumbnail: str = None,
url: str = None,
):
if title and len(title) > self._max_field_name:
raise RuntimeError("Provided Title is too long")
else:
self._embed_title = title
if description and len(description) > self._max_description:
raise RuntimeError("Provided Description is too long")
else:
self._embed_description = description
self._embed_color = color
self._embed_thumbnail = thumbnail
self._embed_url = url
def pages(self) -> typing.List[str]:
_pages = list()
_fields = list()
_page = ""
_lines = 0
_field_name = ""
_field_value = ""
_inline = False
def open_page():
nonlocal _page, _lines, _fields
if not self._embed:
_page = self._prefix
_lines = 0
else:
_fields = list()
def close_page():
nonlocal _page, _lines, _fields
if not self._embed:
_page += self._suffix
_pages.append(_page)
else:
if _fields:
_pages.append(_fields)
open_page()
open_page()
if not self._embed:
for part in [str(p) for p in self._parts]:
if part == self._page_break:
close_page()
new_chars = len(_page) + len(part)
if new_chars > self._max_chars:
close_page()
elif (_lines + (part.count("\n") + 1 or 1)) > self._max_lines:
close_page()
_lines += part.count("\n") + 1 or 1
_page += "\n" + part
else:
def open_field(name: str):
nonlocal _field_value, _field_name
_field_name = name
_field_value = self._prefix
def close_field(next_name: str = None):
nonlocal _field_name, _field_value, _fields
_field_value += self._suffix
if _field_value != self._prefix + self._suffix:
_fields.append(
{"name": _field_name, "value": _field_value, "inline": _inline}
)
if next_name:
open_field(next_name)
open_field("\uFFF0")
for part in [str(p) for p in self._parts]:
if part == self._page_break:
close_page()
continue
elif part == self._field_break:
if len(_fields) + 1 < 25:
close_field(next_name="\uFFF0")
else:
close_field()
close_page()
continue
if part.startswith(self._field_name_char):
part = part.replace(self._field_name_char, "")
if part.startswith(self._inline_char):
_inline = True
part = part.replace(self._inline_char, "")
else:
_inline = False
if _field_value and _field_value != self._prefix:
close_field(part)
else:
_field_name = part
continue
_field_value += "\n" + part
close_field()
close_page()
self._pages = _pages
return _pages
def process_pages(self) -> typing.List[str]:
_pages = self._pages or self.pages()
_len_pages = len(_pages)
_len_page_str = len(f"{_len_pages}/{_len_pages}")
if not self._embed:
for i, page in enumerate(_pages):
if len(page) + _len_page_str <= 2000:
_pages[i] = f"{i + 1}/{_len_pages}\n{page}"
else:
for i, page in enumerate(_pages):
em = discord.Embed(
title=self._embed_title,
description=self._embed_description,
color=self._bot.embed_color,
)
if self._embed_thumbnail:
em.set_thumbnail(url=self._embed_thumbnail)
if self._embed_url:
em.url = self._embed_url
if self._embed_color:
em.colour = self._embed_color
em.set_footer(text=f"{i + 1}/{_len_pages}")
for field in page:
em.add_field(
name=field["name"], value=field["value"], inline=field["inline"]
)
_pages[i] = em
return _pages
def __len__(self):
return sum(len(p) for p in self._parts)
def __eq__(self, other):
# noinspection PyProtectedMember
return self.__class__ == other.__class__ and self._parts == other._parts
def add_page_break(self, *, to_beginning: bool = False) -> None:
self.add(self._page_break, to_beginning=to_beginning)
def add(
self,
item: typing.Any,
*,
to_beginning: bool = False,
keep_intact: bool = False,
truncate=False,
) -> None:
item = str(item)
i = 0
if not keep_intact and not item == self._page_break:
item_parts = item.strip("\n").split("\n")
for part in item_parts:
if len(part) > self._max_line_length:
if not truncate:
length = 0
out_str = ""
def close_line(line):
nonlocal i, out_str, length
self._parts.insert(
i, out_str
) if to_beginning else self._parts.append(out_str)
i += 1
out_str = line + " "
length = len(out_str)
bits = part.split(" ")
for bit in bits:
next_len = length + len(bit) + 1
if next_len <= self._max_line_length:
out_str += bit + " "
length = next_len
elif len(bit) > self._max_line_length:
if out_str:
close_line(line="")
for out_str in [
bit[i : i + self._max_line_length]
for i in range(0, len(bit), self._max_line_length)
]:
close_line("")
else:
close_line(bit)
close_line("")
else:
line = f"{part:.{self._max_line_length-3}}..."
self._parts.insert(
i, line
) if to_beginning else self._parts.append(line)
else:
self._parts.insert(i, part) if to_beginning else self._parts.append(
part
)
i += 1
elif keep_intact and not item == self._page_break:
if len(item) >= self._max_chars or item.count("\n") > self._max_lines:
raise RuntimeError(
"{item} is too long to keep on a single page and is marked to keep intact."
)
if to_beginning:
self._parts.insert(0, item)
else:
self._parts.append(item)
else:
if to_beginning:
self._parts.insert(0, item)
else:
self._parts.append(item)
class Book:
def __init__(
self,
pag: Paginator,
ctx: typing.Tuple[
typing.Optional[discord.Message],
discord.TextChannel,
discord.ext.commands.Bot,
discord.Message,
],
) -> None:
self._pages = pag.process_pages()
self._len_pages = len(self._pages)
self._current_page = 0
self._message, self._channel, self._bot, self._calling_message = ctx
self._locked = True
if pag == Paginator(self._bot):
raise RuntimeError("Cannot create a book out of an empty Paginator.")
def advance_page(self) -> None:
self._current_page += 1
if self._current_page >= self._len_pages:
self._current_page = 0
def reverse_page(self) -> None:
self._current_page += -1
if self._current_page < 0:
self._current_page = self._len_pages - 1
async def display_page(self) -> None:
if isinstance(self._pages[self._current_page], discord.Embed):
if self._message:
await self._message.edit(
content=None, embed=self._pages[self._current_page]
)
else:
self._message = await self._channel.send(
embed=self._pages[self._current_page]
)
else:
if self._message:
await self._message.edit(
content=self._pages[self._current_page], embed=None
)
else:
self._message = await self._channel.send(
self._pages[self._current_page]
)
async def create_book(self) -> None:
# noinspection PyUnresolvedReferences
async def reaction_checker():
# noinspection PyShadowingNames
def check(reaction, user):
if self._locked:
return (
str(reaction.emoji) in self._bot.book_emojis.values()
and user == self._calling_message.author
and reaction.message.id == self._message.id
)
else:
return (
str(reaction.emoji) in self._bot.book_emojis.values()
and reaction.message.id == self._message.id
)
await self.display_page()
if len(self._pages) > 1:
for emoji in self._bot.book_emojis.values():
try:
await self._message.add_reaction(emoji)
except (discord.Forbidden, KeyError):
pass
else:
try:
await self._message.add_reaction(self._bot.book_emojis["unlock"])
await self._message.add_reaction(self._bot.book_emojis["close"])
except (discord.Forbidden, KeyError):
pass
while True:
try:
reaction, user = await self._bot.wait_for(
"reaction_add", timeout=60, check=check
)
except asyncio.TimeoutError:
try:
await self._message.clear_reactions()
except discord.Forbidden:
pass
raise asyncio.CancelledError
else:
await self._message.remove_reaction(reaction, user)
if str(reaction.emoji) == self._bot.book_emojis["close"]:
await self._calling_message.delete()
await self._message.delete()
raise asyncio.CancelledError
elif str(reaction.emoji) == self._bot.book_emojis["forward"]:
self.advance_page()
elif str(reaction.emoji) == self._bot.book_emojis["back"]:
self.reverse_page()
elif str(reaction.emoji) == self._bot.book_emojis["end"]:
self._current_page = self._len_pages - 1
elif str(reaction.emoji) == self._bot.book_emojis["start"]:
self._current_page = 0
elif str(reaction.emoji) == self._bot.book_emojis["hash"]:
m = await self._channel.send(
f"Please enter a number in range 1 to {self._len_pages}"
)
def num_check(message):
if self._locked:
return (
message.content.isdigit()
and 0 < int(message.content) <= self._len_pages
and message.author == self._calling_message.author
)
else:
return (
message.content.isdigit()
and 0 < int(message.content) <= self._len_pages
)
try:
msg = await self._bot.wait_for(
"message", timeout=30, check=num_check
)
except asyncio.TimeoutError:
await m.edit(content="Message Timed out.")
else:
self._current_page = int(msg.content) - 1
try:
await m.delete()
await msg.delete()
except discord.Forbidden:
pass
elif str(reaction.emoji) == self._bot.book_emojis["unlock"]:
self._locked = False
await self._message.remove_reaction(
reaction, self._channel.guild.me
)
continue
await self.display_page()
self._bot.loop.create_task(reaction_checker())

View File

@ -0,0 +1,227 @@
<<<<<<< HEAD:src/shared_libs/utils.py
"""
===
MIT License
Copyright (c) 2018 Dusty.P https://github.com/dustinpianalto
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 io import StringIO
import sys
import asyncio
import discord
from discord.ext.commands.formatter import Paginator
class Capturing(list):
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
return self
def __exit__(self, *args):
self.extend(self._stringio.getvalue().splitlines())
del self._stringio # free up some memory
sys.stdout = self._stdout
def to_list_of_str(items, out: list=list(), level=1, recurse=0):
def rec_loop(item, key, out, level):
quote = '"'
if type(item) == list:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}[')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}]')
elif type(item) == dict:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{{')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}}}')
else:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{repr(item)},')
if type(items) == list:
if not recurse:
out = list()
out.append('[')
for item in items:
rec_loop(item, None, out, level)
if not recurse:
out.append(']')
elif type(items) == dict:
if not recurse:
out = list()
out.append('{')
for key in items:
rec_loop(items[key], key, out, level)
if not recurse:
out.append('}')
return out
def paginate(text, maxlen=1990):
paginator = Paginator(prefix='```py', max_size=maxlen+10)
if type(text) == list:
data = to_list_of_str(text)
elif type(text) == dict:
data = to_list_of_str(text)
else:
data = str(text).split('\n')
for line in data:
if len(line) > maxlen:
n = maxlen
for l in [line[i:i+n] for i in range(0, len(line), n)]:
paginator.add_line(l)
else:
paginator.add_line(line)
return paginator.pages
async def run_command(args):
# Create subprocess
process = await asyncio.create_subprocess_shell(
args,
# stdout must a pipe to be accessible as process.stdout
stdout=asyncio.subprocess.PIPE)
# Wait for the subprocess to finish
stdout, stderr = await process.communicate()
# Return stdout
return stdout.decode().strip()
=======
"""
===
MIT License
Copyright (c) 2018 Dusty.P https://github.com/dustinpianalto
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 io import StringIO
import sys
import asyncio
import discord
from discord.ext.commands.formatter import Paginator
class Capturing(list):
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
return self
def __exit__(self, *args):
self.extend(self._stringio.getvalue().splitlines())
del self._stringio # free up some memory
sys.stdout = self._stdout
def to_list_of_str(items, out: list=list(), level=1, recurse=0):
def rec_loop(item, key, out, level):
quote = '"'
if type(item) == list:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}[')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}]')
elif type(item) == dict:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{{')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}}}')
else:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{repr(item)},')
if type(items) == list:
if not recurse:
out = list()
out.append('[')
for item in items:
rec_loop(item, None, out, level)
if not recurse:
out.append(']')
elif type(items) == dict:
if not recurse:
out = list()
out.append('{')
for key in items:
rec_loop(items[key], key, out, level)
if not recurse:
out.append('}')
return out
def paginate(text, maxlen=1990):
paginator = Paginator(prefix='```py', max_size=maxlen+10)
if type(text) == list:
data = to_list_of_str(text)
elif type(text) == dict:
data = to_list_of_str(text)
else:
data = str(text).split('\n')
for line in data:
if len(line) > maxlen:
n = maxlen
for l in [line[i:i+n] for i in range(0, len(line), n)]:
paginator.add_line(l)
else:
paginator.add_line(line)
return paginator.pages
async def run_command(args):
# Create subprocess
process = await asyncio.create_subprocess_shell(
args,
# stdout must a pipe to be accessible as process.stdout
stdout=asyncio.subprocess.PIPE)
# Wait for the subprocess to finish
stdout, stderr = await process.communicate()
# Return stdout
return stdout.decode().strip()
>>>>>>> e62845ade82bc5e3ade059021693f99b8efcf6a9:sebimachine/shared_libs/utils.py

47
setup.py Normal file
View File

@ -0,0 +1,47 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
"""
Setup.
"""
import re
from setuptools import setup
import traceback
package_name = 'sebimachine'
req_line_test = lambda l: l and l[0] != '#'
with open('README.md') as fp:
readme = fp.read()
with open('requirements.txt') as fp:
requirements = {*filter(req_line_test, map(str.lstrip, fp.read().split('\n')))}
with open(f'{package_name}/__init__.py') as fp:
attrs = {}
print('Attributes:')
for k, v in re.findall(r'^__(\w+)__\s?=\s?"([^"]*)"', fp.read(), re.M):
k = 'name' if k == 'title' else k
attrs[k] = v
print(k, v)
# Use pip on invoke to install requirements. Ensures we can essentially just run this
# script without setuptools arguments. TODO: fix.
try:
import pip
pip.main(['install', *install_requires])
except (ModuleNotFoundError, ImportError):
print('Failed to import pip. Install git dependencies manually.')
traceback.print_exc()
setup(
long_description=readme,
**attrs
)

View File

@ -1,15 +0,0 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
"""
Sebi-Machine.
"""
__author__ = 'Annihilator708'
# TODO: add yourselves here. I can't remember everyones handles.
__contributors__ = (__author__, 'Neko404NotFound', 'Dusty.P', 'davfsa')
__license__ = 'MIT'
__title__ = 'Sebi-Machine'
__version__ = 'tbd'
__repository__ = f'https://github.com/{__author__}/{__title__}'
__url__ = __repository__

View File

@ -1,144 +0,0 @@
# !/usr/bin/python
# -*- coding: utf8 -*-
"""
App entry point.
Something meaningful here, eventually.
"""
import asyncio
import json
import logging
import random
import traceback
import os
import sys
import discord
from discord.ext import commands
from src.config.config import LoadConfig
from src.shared_libs.loggable import Loggable
from src.shared_libs.ioutils import in_here
# Init logging to output on INFO level to stderr.
logging.basicConfig(level='INFO')
# If uvloop is installed, change to that eventloop policy as it
# is more efficient
try:
# https://stackoverflow.com/a/45700730
if sys.platform == 'win32':
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
logging.warning('Detected Windows. Changing event loop to ProactorEventLoop.')
else:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
del uvloop
except BaseException as ex:
logging.warning(f'Could not load uvloop. {type(ex).__qualname__}: {ex};',
'reverting to default impl.')
else:
logging.info(f'Using uvloop for asyncio event loop policy.')
# Bot Class
# Might be worth moving this to it's own file?
class SebiMachine(commands.Bot, LoadConfig, Loggable):
"""This discord is dedicated to http://www.discord.gg/GWdhBSp"""
def __init__(self):
# Initialize and attach config / settings
LoadConfig.__init__(self)
commands.Bot.__init__(self, command_prefix=self.defaultprefix)
# Load plugins
# Add your cog file name in this list
with open(in_here('extensions.txt')) as cog_file:
cogs = cog_file.readlines()
for cog in cogs:
# Could this just be replaced with `strip()`?
cog = cog.replace('\n', '')
self.load_extension(f'src.cogs.{cog}')
self.logger.info(f'Loaded: {cog}')
async def on_ready(self):
"""On ready function"""
self.maintenance and self.logger.warning('MAINTENANCE ACTIVE')
async def on_command_error(self, ctx, error):
"""
The event triggered when an error is raised while invoking a command.
ctx : Context
error : Exception
"""
jokes = ["I\'m a bit tipsy, I took to many screenshots...",
"I am rushing to the 24/7 store to get myself anti-bug spray...",
"Organizing turtle race...",
"There is no better place then 127.0.0.1...",
"Recycling Hex Decimal...",
"No worry, I get fixed :^)...",
"R.I.P, press F for respect...",
"The bug repellent dit not work...",
"You found a bug in the program. Unfortunately the joke did not fit here, better luck next time..."]
# CommandErrors triggered by other propagating errors tend to get wrapped. This means
# if we have a cause, we should probably consider unwrapping that so we get a useful
# message.
# If command is not found, return
if isinstance(error, discord.ext.commands.errors.CommandNotFound):
return
error = error.__cause__ or error
tb = traceback.format_exception(type(error), error, error.__traceback__, limit=2, chain=False)
tb = ''.join(tb)
joke = random.choice(jokes)
fmt = f'**`{self.defaultprefix}{ctx.command}`**\n{joke}\n\n**{type(error).__name__}:**:\n```py\n{tb}\n```'
# Stops the error handler erroring.
try:
await ctx.send(fmt)
except:
traceback.print_exc()
async def on_message(self, message):
# Make sure people can't change the username
if message.guild:
if message.guild.me.display_name != self.display_name:
try:
await message.guild.me.edit(nick=self.display_name)
except:
pass
else:
if ('exec' in message.content or 'repl' in message.content or 'token' in message.content) \
and message.author != self.user:
await self.get_user(351794468870946827).send(f'{message.author.name} ({message.author.id}) is using me '
f'in DMs\n{message.content}')
# If author is a bot, ignore the message
if message.author.bot: return
# Make sure the command get processed as if it was typed with lowercase
# Split message.content one first space
command = message.content.split(None, 1)
if command:
command[0] = command[0].lower()
message.content = ' '.join(command)
message.content = ' '.join(command)
# process command
await self.process_commands(message)
client = SebiMachine()
# Make sure the key stays private.
# I am 99% certain this is valid!
with open(in_here('config', 'PrivateConfig.json')) as fp:
PrivateConfig = json.load(fp)
if PrivateConfig["bot-key"] == '':
PrivateConfig["bot-key"] = os.getenv('botkey')
client.run(PrivateConfig["bot-key"])

View File

@ -1,231 +0,0 @@
from discord.ext import commands
import traceback
import discord
import inspect
import textwrap
from contextlib import redirect_stdout
import io
class REPL:
"""Python in Discords"""
def __init__(self, bot):
self.bot = bot
self._last_result = None
self.sessions = set()
def cleanup_code(self, content):
"""
Automatically removes code blocks from the code.
"""
# remove ```py\n```
if content.startswith('```') and content.endswith('```'):
return '\n'.join(content.split('\n')[1:-1])
# remove `foo`
return content.strip('` \n')
def get_syntax_error(self, e):
if e.text is None:
return '{0.__class__.__name__}: {0}'.format(e)
return '{0.text}{1:>{0.offset}}\n{2}: {0}'.format(e, '^', type(e).__name__)
@commands.command(name='exec')
async def _eval(self, ctx, *, body: str = None):
"""
Execute python code in discord chat.
Only the owner of this bot can use this command.
Alias:
- exec
Usage:
- exec < python code >
Example:
- exec print(546132)
"""
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
if body is None:
return await ctx.send(
'Please, use\n'
f'`{self.bot.config["prefix"]}exec`\n\n'
'\n`\\`\\`\\`py\n[python code]\n\\`\\`\\`\n'
'to get the most out of the command')
env = {
'bot': self.bot,
'ctx': ctx,
'channel': ctx.message.channel,
'author': ctx.message.author,
'server': ctx.message.guild,
'message': ctx.message,
'_': self._last_result
}
env.update(globals())
body = self.cleanup_code(body)
stdout = io.StringIO()
to_compile = 'async def func():\n%s' % textwrap.indent(body, ' ')
try:
exec(to_compile, env)
except SyntaxError as e:
try:
await ctx.send(f'```py\n{self.get_syntax_error(e)}\n```')
except Exception as e:
error = [self.get_syntax_error(e)[i:i + 2000] for i in
range(0, len(self.get_syntax_error(e)), 2000)]
for i in error:
await ctx.send(f'```py\n{i}\n```')
func = env['func']
try:
with redirect_stdout(stdout):
ret = await func()
except Exception as e:
value = stdout.getvalue()
try:
await ctx.send(f'```py\n{value}{traceback.format_exc()}\n```')
except Exception as e:
error = [value[i:i + 2000] for i in range(0, len(value), 2000)]
for i in error:
await ctx.send(f'```py\n{i}\n```')
tracebackerror = [traceback.format_exc()[i:i + 2000] for i in
range(0, len(traceback.format_exc()), 2000)]
for i in tracebackerror:
await ctx.send(f'```py\n{i}\n```')
else:
value = stdout.getvalue()
if ret is None:
if value:
try:
await ctx.send(f'```py\n{value}\n```')
except Exception as e:
code = [value[i:i + 1980] for i in range(0, len(value), 1980)]
for i in code:
await ctx.send(f'```py\n{i}\n```')
else:
self._last_result = ret
try:
code = [value[i:i + 1980] for i in range(0, len(value), 1980)]
for i in code:
await ctx.send(f'```py\n{i}\n```')
except Exception as e:
code = [value[i:i + 1980] for i in range(0, len(value), 1980)]
for i in code:
await ctx.send(f'```py\n{i}\n```')
modifyd_ret = [ret[i:i + 1980] for i in range(0, len(ret), 1980)]
for i in modifyd_ret:
await ctx.send(f'```py\n{i}\n```')
@commands.command(hidden=True)
async def repl(self, ctx):
"""
Start a interactive python shell in chat.
Only the owner of this bot can use this command.
Usage:
- repl < python code >
Example:
- repl print(205554)
"""
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
msg = ctx.message
variables = {
'ctx': ctx,
'bot': self.bot,
'message': msg,
'server': msg.guild,
'channel': msg.channel,
'author': msg.author,
'_': None,
}
if msg.channel.id in self.sessions:
msg = await ctx.send('Already running a REPL session in this channel. Exit it with `quit`.')
self.sessions.add(msg.channel.id)
await ctx.send('Enter code to execute or evaluate. `exit()` or `quit` to exit.')
while True:
response = await self.bot.wait_for('message', check=lambda m: m.content.startswith(
'`') and m.author == ctx.author and m.channel == ctx.channel)
cleaned = self.cleanup_code(response.content)
if cleaned in ('quit', 'exit', 'exit()'):
msg = await ctx.send('Exiting.')
self.sessions.remove(msg.channel.id)
return
executor = exec
if cleaned.count('\n') == 0:
# single statement, potentially 'eval'
try:
code = compile(cleaned, '<repl session>', 'eval')
except SyntaxError:
pass
else:
executor = eval
if executor is exec:
try:
code = compile(cleaned, '<repl session>', 'exec')
except SyntaxError as e:
try:
await ctx.send(f'```Python\n{self.get_syntax_error(e)}\n```')
except Exception as e:
error = [self.get_syntax_error(e)[i:i + 2000] for i in
range(0, len(self.get_syntax_error(e)), 2000)]
for i in error:
await ctx.send(f'```Python\n{i}\n```')
variables['message'] = response
fmt = None
stdout = io.StringIO()
try:
with redirect_stdout(stdout):
result = executor(code, variables)
if inspect.isawaitable(result):
result = await result
except Exception as e:
value = stdout.getvalue()
await ctx.send(f'```Python\n{value}{traceback.format_exc()}\n```')
continue
else:
value = stdout.getvalue()
if result is not None:
fmt = '{}{}'.format(value, result)
variables['_'] = result
elif value:
fmt = value
try:
if fmt is not None:
if len(fmt) > 1980:
code = [fmt[i:i + 1980] for i in range(0, len(fmt), 1980)]
for i in code:
await ctx.send(f'```py\n{i}\n```')
else:
await ctx.send(fmt)
except discord.Forbidden:
pass
except discord.HTTPException as e:
await ctx.send(f'Unexpected error: `{e}`')
def setup(bot):
bot.add_cog(REPL(bot))

View File

@ -1,44 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from discord.ext import commands
import discord
class Moderation:
"""
Moderation Commands
"""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def sar(self, ctx):
"""Assign or remove self assigned roles."""
pass
@commands.command()
async def kick(self, ctx, member: discord.Member = None):
"""
Kick a discord member from your server.
Only contributors can use this command.
Usage:
- kick <discord.member>
"""
await ctx.trigger_typing()
if ctx.author.id not in self.bot.ownerlist:
return await ctx.send('Only my contributors can use me like this :blush:', delete_after=10)
if member is None:
await ctx.send('Are you sure you are capable of this command?')
try:
await member.kick()
await ctx.send(f'You kicked **`{member.name}`** from **`{ctx.guild.name}`**')
except Exception as e:
await ctx.send('You may not use this command you do not have permission in server:\n\n**`{ctx.guild.name}`**'
f'\n\n```py\n{e}\n```')
def setup(bot):
bot.add_cog(Moderation(bot))

View File

@ -1,97 +0,0 @@
import discord
from discord.ext import commands
import json
import aiofiles
import asyncio
class Tag:
def __init__(self, bot):
self.bot = bot
with open("src/shared_libs/tags.json", "r") as fp:
json_data = fp.read()
global tags
tags = json.loads(json_data)
@commands.group(case_insensitive=True, invoke_without_command=True)
async def tag(self, ctx, tag=None):
"""Gets a tag"""
await ctx.trigger_typing()
if tag is None:
return await ctx.send('Please provide a argument. Do `help tag` for more info')
found = tags.get(tag, None)
if found is None:
return await ctx.send('Tag not found')
await ctx.send(found)
@tag.command(case_insensitive=True)
async def list(self, ctx):
"""Lists available tags"""
await ctx.trigger_typing()
desc = ""
for i in tags:
desc = desc + i + "\n"
if desc == "":
desc = "None"
em = discord.Embed(title='Available tags:', description=desc ,colour=discord.Colour(0x00FFFF))
await ctx.send(embed=em)
@tag.command(case_insensitive=True)
async def add(self, ctx, tag_name=None, *, tag_info=None):
"""Adds a new tag"""
await ctx.trigger_typing()
if not ctx.author.guild_permissions.manage_guild:
return await ctx.send("You are not allowed to do this")
if tag_name is None or tag_info is None:
return await ctx.send("Please provide a tag name and the tag info. Do `help tag` for more info")
exists = False
for i in tags:
if i == tag_name:
exists = True
if not exists:
tags.update({tag_name : tag_info})
async with aiofiles.open("src/shared_libs/tags.json", "w") as fp:
json_data = json.dumps(tags)
await fp.write(json_data)
return await ctx.send("The tag has been added")
await ctx.send("The tag already exists")
@tag.command(case_insensitive=True)
async def remove(self, ctx, tag=None):
"""Remove a existing tag"""
await ctx.trigger_typing()
if not ctx.author.guild_permissions.manage_guild:
return await ctx.send("You are not allowed to do this")
if tag is None:
return await ctx.send("Please provide a tag name and the tag info. Do `help tag` for more info")
found = None
for i in tags:
if i == tag:
found = i
if found is not None:
del tags[found]
async with aiofiles.open("src/shared_libs/tags.json", "w") as fp:
json_data = json.dumps(tags)
await fp.write(json_data)
return await ctx.send("The tag has been removed")
await ctx.send("The tag has not been found")
def setup(bot):
bot.add_cog(Tag(bot))

View File

@ -1,113 +0,0 @@
"""
===
MIT License
Copyright (c) 2018 Dusty.P https://github.com/dustinpianalto
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 io import StringIO
import sys
import asyncio
import discord
from discord.ext.commands.formatter import Paginator
import numpy as np
class Capturing(list):
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
return self
def __exit__(self, *args):
self.extend(self._stringio.getvalue().splitlines())
del self._stringio # free up some memory
sys.stdout = self._stdout
def to_list_of_str(items, out: list=list(), level=1, recurse=0):
def rec_loop(item, key, out, level):
quote = '"'
if type(item) == list:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}[')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}]')
elif type(item) == dict:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{{')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}}}')
else:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{repr(item)},')
if type(items) == list:
if not recurse:
out = list()
out.append('[')
for item in items:
rec_loop(item, None, out, level)
if not recurse:
out.append(']')
elif type(items) == dict:
if not recurse:
out = list()
out.append('{')
for key in items:
rec_loop(items[key], key, out, level)
if not recurse:
out.append('}')
return out
def paginate(text, maxlen=1990):
paginator = Paginator(prefix='```py', max_size=maxlen+10)
if type(text) == list:
data = to_list_of_str(text)
elif type(text) == dict:
data = to_list_of_str(text)
else:
data = str(text).split('\n')
for line in data:
if len(line) > maxlen:
n = maxlen
for l in [line[i:i+n] for i in range(0, len(line), n)]:
paginator.add_line(l)
else:
paginator.add_line(line)
return paginator.pages
async def run_command(args):
# Create subprocess
process = await asyncio.create_subprocess_shell(
args,
# stdout must a pipe to be accessible as process.stdout
stdout=asyncio.subprocess.PIPE)
# Wait for the subprocess to finish
stdout, stderr = await process.communicate()
# Return stdout
return stdout.decode().strip()