Compare commits

...
Author SHA1 Message Date
Dustin Pianalto 897c77fee6 moved song 2018-06-18 15:04:00 -08:00
Dustin Pianalto 9c340116eb remove detailed error messages 2018-06-11 19:18:26 -08:00
Dustin Pianalto 52174175ef Add error color to bot instance 2018-06-11 19:11:48 -08:00
Dustin Pianalto 0858fbc7d5 Shortened help strings 2018-06-11 18:50:03 -08:00
Dustin Pianalto 3e75b20c54 Added help strings 2018-06-11 18:31:08 -08:00
Dustin Pianalto 3dc75dabe6 Added help strings 2018-06-11 18:28:00 -08:00
Dustin Pianalto db525d0048 Added help strings 2018-06-11 18:17:04 -08:00
Dustin Pianalto 35ab455a7c added help docstring
adjusted help line length
2018-06-11 17:32:59 -08:00
Dustin Pianalto 66cbaffc7a Merge remote-tracking branch 'origin/release-1.0.0' into release-1.0.0 2018-06-11 10:47:40 -08:00
Dustin Pianalto 71686b4cc2 fixed layout in ping 2018-06-11 10:47:16 -08:00
Dustin Pianalto 7ac58a435c v1.0.0b2 2018-06-11 02:34:24 -08:00
Dustin Pianalto d596db870b fixed bug in admin close 2018-06-11 02:26:41 -08:00
Dustin Pianalto 69109dc3a9 fixed bug in admin new 2018-06-11 02:22:18 -08:00
Dustin Pianalto c4a0398a60 fixed bug in admin new 2018-06-11 02:17:15 -08:00
Dustin Pianalto 90ca2d0fac fixed bug in admin new 2018-06-11 02:15:34 -08:00
Dustin Pianalto 4bf8649950 removed print from patreon 2018-06-11 02:11:08 -08:00
Dustin Pianalto 70545be638 fixed bug in patreon 2018-06-11 02:10:20 -08:00
Dustin Pianalto d56fb072bb test patreon 2018-06-11 02:07:50 -08:00
Dustin Pianalto 49894b2dd7 test patreon 2018-06-11 02:06:29 -08:00
Dustin Pianalto b4a5f83689 test patreon 2018-06-11 02:04:46 -08:00
Dustin Pianalto 63b87bf1c7 fix git status paginator 2018-06-11 02:00:35 -08:00
Dustin Pianalto dd96a9cf90 update git status to new book 2018-06-11 01:59:14 -08:00
Dustin Pianalto 9c281c6d7f hide print_infections 2018-06-11 01:55:14 -08:00
Dustin Pianalto 0c4fd467ff add todo 2018-06-11 01:53:18 -08:00
Dustin Pianalto f11f53ac81 hide view_code 2018-06-11 01:51:02 -08:00
Dustin Pianalto dc51b4e6b6 fixed bug in view_code 2018-06-11 01:50:36 -08:00
Dustin Pianalto 3531459b03 fixed bug in _allowed_channels 2018-06-11 00:37:17 -08:00
Dustin Pianalto 31a245a7db fixed bug in _allowed_channels 2018-06-11 00:20:28 -08:00
Dustin Pianalto 8c7f33c166 fixed bug in _allowed_channels 2018-06-11 00:11:06 -08:00
Dustin Pianalto 512588a34c fixed bug in _allowed_channels 2018-06-10 22:09:03 -08:00
Dustin Pianalto 7c3e9726f9 fixed bug in _allowed_channels 2018-06-10 22:07:18 -08:00
Dustin Pianalto 95c7bcb928 fixed bug in _allowed_channels 2018-06-10 22:04:21 -08:00
Dustin Pianalto 44c8ca1eb5 fixed bug in _allowed_channels 2018-06-10 22:02:42 -08:00
Dustin Pianalto 9d894a4438 fixed bug in _allowed_channels 2018-06-10 22:01:16 -08:00
Dustin Pianalto 2bdbdb9749 fixed bug in _allowed_channels 2018-06-10 21:56:40 -08:00
Dustin Pianalto 9b47aff4b0 fixed bug in _allowed_channels 2018-06-10 21:55:14 -08:00
Dustin Pianalto 54d5be24e8 rewrote _allowed_channels 2018-06-10 21:44:56 -08:00
Dustin Pianalto d616a94709 testing _allowed_channels 2018-06-09 15:18:30 -08:00
Dustin Pianalto f8fe207721 testing _allowed_channels 2018-06-09 15:14:25 -08:00
Dustin Pianalto 2259900dcd fixed bug in _allowed_channels 2018-06-09 15:07:36 -08:00
Dustin Pianalto ec6cd95c9e test allowed_channels 2018-06-09 15:05:26 -08:00
Dustin Pianalto d14de7a964 Fixed bug in _allowed_channels 2018-06-09 14:58:39 -08:00
Dustin Pianalto a77f976562 Fixed bug in _allowed_channels 2018-06-09 14:58:04 -08:00
Dustin Pianalto 687c62dce7 Fixed bug in _allowed_channels 2018-06-09 14:57:15 -08:00
Dustin Pianalto 778e9890ba Fixed bug in _allowed_channels 2018-06-09 14:51:39 -08:00
Dustin Pianalto b46d8dd366 Fixed bug in _allowed_channels 2018-06-09 14:26:28 -08:00
Dustin Pianalto 46c9075397 changed help line length 2018-06-09 14:17:01 -08:00
Dustin Pianalto 677fc9a60e changed help line length 2018-06-09 14:14:15 -08:00
Dustin Pianalto 4c70c67f31 changed help line length 2018-06-09 14:11:19 -08:00
Dustin Pianalto 240d606f2b bug fix in purge 2018-06-08 15:35:38 -08:00
Dustin Pianalto 47815c7119 try bug fix in purge 2018-06-08 15:33:14 -08:00
Dustin Pianalto b672768ef4 try bug fix in purge 2018-06-08 15:31:58 -08:00
Dustin Pianalto 576f070cf2 try bug fix in purge 2018-06-08 15:29:26 -08:00
Dustin Pianalto ca25f6e07a try bug fix in purge 2018-06-08 15:28:21 -08:00
Dustin Pianalto bf89d6f1ba try bug fix in purge 2018-06-08 15:27:20 -08:00
Dustin Pianalto f9b2fe0a25 try bug fix in purge 2018-06-08 15:23:11 -08:00
Dustin Pianalto 5c60a9ec7a try bug fix in purge 2018-06-08 15:21:19 -08:00
Dustin Pianalto 916e605a6e Beta of release 1.0.0 2018-06-08 15:04:35 -08:00
Dustin Pianalto b9689b502d Fixed bug in help 2018-06-08 14:55:34 -08:00
Dustin Pianalto ee258bc62f Added space between signature and help 2018-06-08 14:53:06 -08:00
Dustin Pianalto eaa8511455 Added space between signature and help 2018-06-08 14:51:44 -08:00
Dustin Pianalto 2537b8331a version 0.6.1a 2018-06-08 14:50:22 -08:00
Dustin Pianalto 3791d205de Added signature to detailed help 2018-06-08 14:49:16 -08:00
Dustin Pianalto 011daf3063 Added signature to detailed help 2018-06-08 14:48:28 -08:00
Dustin Pianalto 4963d773e2 version 0.6.0a 2018-06-08 14:36:01 -08:00
Dustin Pianalto 87566fffcd Removed paging emojis if only one page 2018-06-08 14:35:13 -08:00
Dustin Pianalto 97d7dc7e25 Add detailed help for commands 2018-06-08 14:29:58 -08:00
Dustin Pianalto b5c0e002ab sort group commands 2018-06-08 13:07:23 -08:00
Dustin Pianalto d989591431 sort cog_commands 2018-06-08 13:05:36 -08:00
Dustin Pianalto eab32f3b84 sort cog_commands 2018-06-08 13:03:06 -08:00
Dustin Pianalto 1dca5d41bb changed line length on help 2018-06-08 13:01:03 -08:00
Dustin Pianalto 8b5af80bad Reformatted description 2018-06-08 12:57:51 -08:00
Dustin Pianalto 340ab4b140 Reformatted description 2018-06-08 12:57:26 -08:00
Dustin Pianalto d9f4858485 Reformatted help message 2018-06-08 12:55:41 -08:00
Dustin Pianalto 499adbfced fixed help 2018-06-08 12:42:19 -08:00
Dustin Pianalto fc1a6c63d7 updated version info 2018-06-08 12:40:40 -08:00
Dustin Pianalto 641cdb7150 Added help info 2018-06-08 12:39:29 -08:00
Dustin Pianalto 643a7776df modify description 2018-06-08 12:14:43 -08:00
Dustin Pianalto 7797e8c771 Added embed metadata 2018-06-08 12:06:12 -08:00
Dustin Pianalto d5c5801e3a made message type optional 2018-06-08 12:05:38 -08:00
Dustin Pianalto 7f37194574 Moved description into Geeksbot Class 2018-06-08 12:02:30 -08:00
Dustin Pianalto 98c84c8442 sort cogs set 2018-06-08 11:57:14 -08:00
Dustin Pianalto 2773de1503 fix book 2018-06-08 11:53:15 -08:00
Dustin Pianalto 34183adcfb format help strings 2018-06-08 11:51:44 -08:00
Dustin Pianalto 73e5a227a5 Make message optional in book creation 2018-06-08 11:42:15 -08:00
Dustin Pianalto c2e6495be3 make command arg optional to help command 2018-06-08 11:32:10 -08:00
Dustin Pianalto 5447601cec remove built-in help 2018-06-08 11:30:57 -08:00
Dustin Pianalto 19155577fa Trial help 2018-06-08 11:30:31 -08:00
Dustin Pianalto 3f33154606 Merge remote-tracking branch 'origin/development' into development 2018-06-08 11:17:25 -08:00
Dustin Pianalto bbee079d2c added truncate parameter 2018-06-08 11:17:08 -08:00
Dustin Pianalto 4cd62e1e05 Fixed db_concon 2018-06-08 00:56:23 -08:00
Dustin Pianalto f7fab22d81 TODO done 2018-06-08 00:45:08 -08:00
Dustin Pianalto 51ac2ba474 Don't append empty page 2018-06-08 00:24:07 -08:00
Dustin Pianalto 7d901f55f9 Move link to next line in result title 2018-06-08 00:23:14 -08:00
Dustin Pianalto 1c022ad214 Don't append empty field 2018-06-08 00:21:24 -08:00
Dustin Pianalto f174b0cbfa Converted Google search to paginated embed 2018-06-08 00:17:36 -08:00
Dustin Pianalto 71cf7a44a7 Converted Google search to paginated embed 2018-06-08 00:13:33 -08:00
Dustin Pianalto 103f715969 Converted Google search to paginated embed 2018-06-08 00:06:28 -08:00
Dustin Pianalto b1574a96d7 Embed will only react to reactions on its own message 2018-06-07 23:56:13 -08:00
Dustin Pianalto feefff0a2b Embed will only react to reactions on its own message 2018-06-07 23:52:27 -08:00
Dustin Pianalto 3d8c3b0d88 adjusted line length for embed 2018-06-07 23:48:04 -08:00
Dustin Pianalto fc655767f7 Modified git pull to use paginated embeds 2018-06-07 23:39:12 -08:00
Dustin Pianalto 222159f059 Added required argument to Paginator instances 2018-06-07 23:38:44 -08:00
Dustin Pianalto cb7290be00 Fixed number checker 2018-06-07 23:06:25 -08:00
Dustin Pianalto 88d0af40c5 fixed pages 2018-06-07 19:37:52 -08:00
Dustin Pianalto 681936b211 fixed fields 2018-06-07 19:36:35 -08:00
Dustin Pianalto e83f283e60 test _pages 2018-06-07 19:32:07 -08:00
Dustin Pianalto 377201f5ca test _pages 2018-06-07 19:31:16 -08:00
Dustin Pianalto 83787333e5 Fix fields 2018-06-07 19:29:07 -08:00
Dustin Pianalto 34174cb148 Fix fields 2018-06-07 19:28:19 -08:00
Dustin Pianalto 35d383420f Add inline support 2018-06-07 19:25:32 -08:00
Dustin Pianalto c52d708af9 Fix close_page 2018-06-07 19:21:32 -08:00
Dustin Pianalto d6b53bb07c test fields 2018-06-07 19:19:26 -08:00
Dustin Pianalto a24749dd63 Open Field first 2018-06-07 19:16:33 -08:00
Dustin Pianalto 63006cd200 test page 2018-06-07 19:11:27 -08:00
Dustin Pianalto 910686d80b Fixed open_page and close_page 2018-06-07 19:08:37 -08:00
Dustin Pianalto 6312d21b00 Fixed paginator in book 2018-06-07 19:04:04 -08:00
Dustin Pianalto 4c52d9eafc Fixed paginator in book 2018-06-07 19:02:51 -08:00
Dustin Pianalto 12db8a520d Fixed paginator in repl 2018-06-07 19:01:36 -08:00
Dustin Pianalto 95fce3f636 Fixed paginator in error handling so I can see what I am breaking ;) 2018-06-07 18:59:57 -08:00
Dustin Pianalto 8746cd0bcf Modify Paginator to do embeds 2018-06-07 18:53:11 -08:00
Dustin Pianalto 59a7bb37c6 only strip \n in paginator 2018-06-07 16:26:15 -08:00
Dusty.P ab14f9da3c delete messages 2018-06-06 23:39:17 -08:00
Dusty.P ee2ebe4274 Zero indexed... 2018-06-06 23:37:31 -08:00
Dusty.P 714d5cd229 Forgot await... 2018-06-06 23:35:44 -08:00
Dusty.P 05536052e9 Added option to select page number 2018-06-06 23:34:37 -08:00
Dusty.P ab83f968b9 Added option to select page number 2018-06-06 23:32:58 -08:00
Dusty.P 08d8f424be fixed book being slow 2018-06-06 23:19:36 -08:00
Dusty.P 82cf5d9856 fixed book... again. 2018-06-06 23:14:54 -08:00
Dusty.P dc98e5eea3 fixed book... again. 2018-06-06 23:12:07 -08:00
Dusty.P 4e8bee6785 fixed book... again. 2018-06-06 23:08:20 -08:00
Dusty.P 61e774bc5a fixed book... maybe. 2018-06-06 23:01:50 -08:00
Dusty.P 7344187eb1 fixed reboot command 2018-06-06 23:00:23 -08:00
Dusty.P a6273e6856 Added create_book 2018-06-06 22:59:18 -08:00
Dusty.P 9d6216e344 Added create_book 2018-06-06 22:56:03 -08:00
Dusty.P b031adce62 Maybe fixing something? 2018-06-06 22:13:50 -08:00
Dusty.P 38469d38b2 Maybe fixing something? 2018-06-06 22:13:00 -08:00
Dusty.P 077a9e66b3 Maybe fixing something? 2018-06-06 22:11:31 -08:00
Dusty.P 5edc0c98ef Breaking more stuff 2018-06-06 22:09:32 -08:00
Dusty.P 9799265c0a Breaking more stuff 2018-06-06 22:07:50 -08:00
Dusty.P 994e257026 Hopefully this doesn't break something 2018-06-06 22:01:24 -08:00
Dusty.P 4801daeafe Hopefully this doesn't break something 2018-06-06 21:57:36 -08:00
Dusty.P 8104302e43 Change emojis for book 2018-06-06 21:49:22 -08:00
Dusty.P 0600b883fe Process pages for book 2018-06-06 21:21:35 -08:00
Dustin Pianalto 3700ab2344 adjust Book 2018-06-06 17:18:38 -08:00
Dustin Pianalto 762984592b adjust paginator 2018-06-06 17:13:22 -08:00
Dustin Pianalto 1c778e4a49 Adding reactions to book 2018-06-06 17:10:10 -08:00
Dusty.P 08c3d8a79c Start on Book 2018-06-06 01:49:36 -08:00
Dusty.P 061c39a8eb Add Emojis 2018-06-05 23:54:12 -08:00
Dusty.P 0d45b8b827 Modify exec 2018-06-05 23:37:42 -08:00
Dusty.P 8a68823f82 Fixed in_time if not given 2018-06-05 23:07:20 -08:00
Dusty.P 914ee90f70 Added correct prefix and suffix for py code 2018-06-05 23:05:04 -08:00
Dusty.P 39bf9c71fb Removed test print 2018-06-05 23:02:51 -08:00
Dusty.P b782bf681a Fig bug where last line is not added. 2018-06-05 22:59:09 -08:00
Dusty.P 789eee5b2e test paginator 2018-06-05 22:56:29 -08:00
Dusty.P 42388f4b63 test paginator 2018-06-05 22:49:30 -08:00
Dusty.P 6c4de0fb58 test paginator 2018-06-05 22:34:31 -08:00
Dusty.P 54d631df67 test paginator 2018-06-05 22:14:51 -08:00
Dusty.P 1102b54972 test paginator 2018-06-05 22:04:28 -08:00
Dusty.P 80a6c0c924 Convert to new Paginator 2018-06-05 22:03:01 -08:00
Dusty.P 96ca3208e8 test case 2018-06-05 21:48:17 -08:00
Dusty.P bfa9d7c1ef Modify paginator 2018-06-05 21:44:13 -08:00
Dusty.P 4945ac630b Changed paginate to use new paginator 2018-06-05 21:24:58 -08:00
Dustin Pianalto 5ed3152dc9 Hotfix to use DannyPag for now 2018-06-05 17:10:44 -08:00
Dusty.P b56e57d939 Fixed newline bug 2018-06-05 00:42:09 -08:00
Dusty.P 2b41489bd6 Error if keep intact has more lines than max_lines 2018-06-05 00:36:23 -08:00
Dusty.P 1434ac36c4 Fixed line count for keep intact strings 2018-06-05 00:33:36 -08:00
Dusty.P dd1e0d8812 Fixed line count for keep intact strings 2018-06-05 00:30:54 -08:00
Dusty.P 679a49d9e4 Fixed line count for keep intact strings 2018-06-05 00:25:41 -08:00
Dusty.P c7532b6ed7 Remove leading and trailing whitespace 2018-06-05 00:13:54 -08:00
Dusty.P 84a7734f39 Make changes to lines counter 2018-06-05 00:07:53 -08:00
Dusty.P cedf030345 Test lines 2018-06-05 00:04:01 -08:00
Dusty.P cd79629340 Change Paginator import to DannyPag 2018-06-04 23:50:17 -08:00
Dusty.P 999ebae413 Completed? Paginator 2018-06-04 23:43:28 -08:00
Dustin Pianalto 127bd483b2 Working on Paginator 2018-06-04 16:50:49 -08:00
Dustin Pianalto bb46a28a1a Changed spacing on git url 2018-06-04 13:27:27 -08:00
Dustin Pianalto 2fdac0b854 Merge remote-tracking branch 'origin/development' into development 2018-06-04 13:25:31 -08:00
Dustin Pianalto 73ce5de59f Added GitHub link 2018-06-04 13:24:57 -08:00
Dusty.P 0fc8d6644d Start working on Paginator 2018-06-04 00:58:56 -08:00
Dusty.P d5e4a5dbd7 Modified exec output 2018-06-03 23:56:26 -08:00
Dusty.P 55fb9ae3e7 Adjust gti 2018-06-03 23:50:16 -08:00
Dusty.P 70dbc350c2 Adjust gti 2018-06-03 23:33:39 -08:00
Dusty.P cf729b094b Adjust gti 2018-06-03 22:52:37 -08:00
Dusty.P 43477595b2 Adjust gti 2018-06-03 00:07:33 -08:00
Dusty.P f188a145eb Adjust gti 2018-06-03 00:03:30 -08:00
Dusty.P ca5b862852 Adjust gti 2018-06-02 23:52:27 -08:00
Dusty.P e2252f7140 Adjust gti 2018-06-02 23:48:52 -08:00
Dusty.P ea177a3d71 Adjust gti 2018-06-02 23:45:49 -08:00
Dusty.P 73503c9199 Adjust gti 2018-06-02 23:39:43 -08:00
Dusty.P 02f189781f Adjust gti 2018-06-02 23:30:52 -08:00
Dusty.P 352470e364 Adjust gti 2018-06-02 23:25:03 -08:00
Dusty.P cc9a934fe6 Adjust gti 2018-06-02 23:11:21 -08:00
Dusty.P 70535d0399 Adjust gti 2018-06-02 22:11:10 -08:00
Dusty.P ec5d1bd1da Adjust gti 2018-06-02 22:09:49 -08:00
Dusty.P 62a58bc1ec Adjust gti 2018-06-02 22:01:27 -08:00
Dusty.P 6e5ad99253 Added command to get time in a different timezone 2018-06-02 21:56:58 -08:00
Dusty.P fecff7925c Get trout by id instead of name 2018-05-23 22:06:10 -08:00
Dusty.P bb7240689b Fixed trout 2018-05-23 21:48:54 -08:00
Dusty.P c924412e67 Adjust speed on Explode 2018-05-21 22:14:37 -08:00
Dusty.P c0cb5568d7 Fixing errors 2018-05-21 21:56:10 -08:00
Dusty.P 9dbcff12b4 Fixing errors 2018-05-21 21:50:55 -08:00
Dusty.P efc73a0119 Moved misc to scratchpad 2018-05-21 21:25:41 -08:00
Dusty.P a4dca2de21 Update .gitignore 2018-05-21 21:13:18 -08:00
Dustin Pianalto 51d3f32055 Switch to Asyncpg for db con 2018-05-21 19:55:36 -08:00
Dustin Pianalto 7949707a3c Switch to Asyncpg for db con 2018-05-21 19:28:01 -08:00
Dustin Pianalto 5e5f50bcc0 Merge remote-tracking branch 'origin/development' into development
# Conflicts:
#	shared_libs/database.py
2018-05-21 19:21:14 -08:00
Dustin Pianalto 996d7a3ea3 Switch to Asyncpg for db con 2018-05-21 19:20:59 -08:00
Dustin Pianalto 7b3b953fea Switch to Asyncpg for db con 2018-05-21 19:20:03 -08:00
Dustin Pianalto 8c702a854d Switch to Asyncpg for db con 2018-05-21 19:18:56 -08:00
Dustin Pianalto 47ca65da91 Switch to Asyncpg for db con 2018-05-21 19:13:40 -08:00
Dustin Pianalto 249e5d94eb Switch to Asyncpg for db con 2018-05-21 18:31:06 -08:00
Dusty.P f992d85d26 test errors 2018-05-20 01:01:03 -08:00
Dusty.P 469cd5a4b1 test errors 2018-05-20 01:00:17 -08:00
Dusty.P 960fbfbcd2 test errors 2018-05-20 00:59:05 -08:00
Dusty.P fac236ed04 test errors 2018-05-20 00:53:22 -08:00
Dusty.P b42d0ef35a test errors 2018-05-20 00:51:55 -08:00
Dusty.P 09f3aa538d test errors 2018-05-20 00:48:35 -08:00
Dusty.P d8e28af901 test errors 2018-05-20 00:47:31 -08:00
Dusty.P 69a0475edd Switch to asyncpg 2018-05-20 00:43:31 -08:00
Dusty.P b6f3125d30 Switch to asyncpg 2018-05-20 00:42:16 -08:00
Dusty.P 336c8a6906 test errors 2018-05-20 00:39:18 -08:00
Dusty.P 5146c01ba9 test errors 2018-05-20 00:37:26 -08:00
Dusty.P 5b6c94b16f test errors 2018-05-20 00:36:31 -08:00
Dusty.P fa1c789b40 test errors 2018-05-20 00:35:13 -08:00
Dusty.P 1c04e9fe1a test errors 2018-05-20 00:34:02 -08:00
Dusty.P e9874bd060 test errors 2018-05-20 00:32:46 -08:00
Dusty.P 05b4bd184f test errors 2018-05-20 00:31:42 -08:00
Dusty.P b4fd2e5d54 test errors 2018-05-20 00:30:03 -08:00
Dusty.P 5df671a4b1 test errors 2018-05-20 00:26:36 -08:00
Dusty.P c53fd11789 Merge branch 'development' of github.com:dustinpianalto/Geeksbot into development 2018-05-20 00:22:55 -08:00
Dusty.P 9a82390682 Switching to asyncpg 2018-05-20 00:22:40 -08:00
Dustin Pianalto 3e1f2290cb Re-enable Fact 2018-05-17 13:29:59 -08:00
Dustin Pianalto d413f52a5f Adjust splode 2018-05-07 10:23:14 -08:00
Dustin Pianalto f4962cef77 Paginate view_code 2018-05-04 11:29:30 -08:00
Dustin Pianalto 48cdea96ee Add map location 2018-05-04 10:48:07 -08:00
Dustin Pianalto bd34de573c Add map location 2018-05-04 10:40:49 -08:00
Dustin Pianalto c7e0fe017c Add map location 2018-05-04 10:39:15 -08:00
Dustin Pianalto d55b889a3a Add map location 2018-05-04 10:36:17 -08:00
Dustin Pianalto c0834b3ab4 Add map location 2018-05-04 10:31:18 -08:00
Dustin Pianalto f7c4a9f04b Add map location 2018-05-04 10:29:45 -08:00
Dustin Pianalto 09b87e8278 explode 2018-05-03 15:23:41 -08:00
Dustin Pianalto d37fb816bd explode 2018-05-03 15:22:57 -08:00
Dustin Pianalto 5924e0f39d explode 2018-05-03 15:14:11 -08:00
Dustin Pianalto 3da8b579a4 explode 2018-05-03 15:03:15 -08:00
Dustin Pianalto b73a2a5016 explode 2018-05-03 15:00:39 -08:00
Dustin Pianalto 2d345e6891 explode 2018-05-03 14:59:14 -08:00
Dustin Pianalto 5a2fb878f3 explode 2018-05-03 14:57:23 -08:00
Dustin Pianalto 7b3a130c91 Moved sending iss image into tpe 2018-05-03 13:00:53 -08:00
Dustin Pianalto e435b0cbb9 Moved sending iss image into tpe 2018-05-03 12:59:45 -08:00
Dustin Pianalto dcc61f6546 Add notifications to ISS image creation 2018-05-03 12:51:24 -08:00
Dustin Pianalto 5916f3470c Add notifications to ISS image creation 2018-05-03 12:50:54 -08:00
Dustin Pianalto ab760af838 Add notifications to ISS image creation 2018-05-03 12:48:36 -08:00
Dustin Pianalto 3d7e32b2d1 Resize iss image 2018-05-03 12:44:11 -08:00
Dustin Pianalto 7fa35bf478 Moved generating iss image to tpe 2018-05-03 12:38:44 -08:00
Dustin Pianalto c052248cfe Moved generating iss image to tpe 2018-05-03 12:36:27 -08:00
Dustin Pianalto 081b62d267 Moved generating iss image to tpe 2018-05-03 12:34:38 -08:00
Dustin Pianalto bfe7f5835d Added display typing while generating image for iss 2018-05-03 12:08:45 -08:00
Dustin Pianalto e62e2c252f Added ISS Location command 2018-05-03 12:06:39 -08:00
Dustin Pianalto 3dde1eabd4 Added ISS Location command 2018-05-03 12:04:54 -08:00
Dusty.P 450783b785 Switching to asyncpg 2018-04-20 23:17:31 -08:00
Dusty.P 618c3acf84 Switched to Connection Pool 2018-04-20 22:58:00 -08:00
Dusty.P baaa252c7e Fixed Git Pull 2018-04-20 22:40:13 -08:00
Dusty.P 774f95a70e Switching to asyncpg 2018-04-20 22:38:47 -08:00
Dusty.P c4148f2cc0 Switching to asynpg 2018-04-20 22:29:30 -08:00
dustinpianalto d797203a7b Update .gitignore 2018-04-20 22:22:05 -08:00
dustinpianalto c93043bf91 Delete vcs.xml 2018-04-20 22:21:27 -08:00
dustinpianalto 0c48f345b7 Delete modules.xml 2018-04-20 22:21:19 -08:00
dustinpianalto 9bde33b3c3 Delete misc.xml 2018-04-20 22:21:12 -08:00
dustinpianalto 3bff3e8276 Delete Geeksbot.iml 2018-04-20 22:21:04 -08:00
dustinpianalto d27d85df19 Delete Dustin_Pianalto.xml 2018-04-20 22:20:54 -08:00
Dusty.P 2ba7f09065 Merge branch 'development' of https://github.com/dustinpianalto/Geeksbot into development 2018-04-20 22:14:14 -08:00
Dusty.P 9f0997c492 Switching to asynpg 2018-04-20 22:10:10 -08:00
Dusty.P f6864ddc0a Merge branch 'development' of https://github.com/dustinpianalto/Geeksbot into development 2018-04-20 21:10:20 -08:00
Dusty.P 9b9a4e8227 Switching to asynpg 2018-04-20 21:10:05 -08:00
Dusty.P c4fee255dc Merge branch 'development' of https://github.com/dustinpianalto/Geeksbot into development 2018-04-20 21:08:50 -08:00
Dusty.P a004c9c70c Deleted *.sh.swp 2018-04-20 21:08:30 -08:00
Dusty.P f939df6bc0 Switching to asyncpg 2018-04-20 21:07:14 -08:00
Dusty.P 54eb8abfd6 Merge branch 'development' of https://github.com/dustinpianalto/Geeksbot into development 2018-04-20 21:06:15 -08:00
Dusty.P 5495aa213e Switching to asyncpg 2018-04-20 21:06:01 -08:00
Dusty.P dd5dab317d Merge branch 'development' of https://github.com/dustinpianalto/Geeksbot into development 2018-04-20 21:04:57 -08:00
Dusty.P fc4529bcba Ignore *.sh.swp 2018-04-20 21:04:10 -08:00
Dusty.P 9cc1004f38 Switching to asyncpg 2018-04-20 21:02:28 -08:00
Dusty.P 11ac36d5fd Merge branch 'development' of https://github.com/dustinpianalto/Geeksbot into development 2018-04-20 20:58:45 -08:00
Dusty.P 13d4dc564c Switching to asyncpg 2018-04-20 20:58:24 -08:00
Dusty.P 4958e6ea02 Merge branch 'development' of https://github.com/dustinpianalto/Geeksbot into development 2018-04-20 20:55:53 -08:00
Dusty.P 6244f8cb77 Switching to asyncpg 2018-04-20 20:55:28 -08:00
Dusty.P 34ed2717d4 Merge branch 'development' of https://github.com/dustinpianalto/Geeksbot into development 2018-04-20 20:39:06 -08:00
Dusty.P ab49f3a469 Merge remote-tracking branch 'origin/development' into development
# Conflicts:
#	geeksbot.py
2018-04-20 20:38:46 -08:00
Dusty.P da615a54ea Fixed db_con 2018-04-20 20:38:15 -08:00
Dusty.P a38cdfb6e1 Merge branch 'development' of https://github.com/dustinpianalto/Geeksbot into development 2018-04-20 20:37:17 -08:00
Dusty.P 9f552d76eb Fixed db_con 2018-04-20 20:36:07 -08:00
Dusty.P 9f849bdf29 Merge branch 'development' of https://github.com/dustinpianalto/Geeksbot into development 2018-04-20 20:23:16 -08:00
Dusty.P 005f4ca25d Fixed Git Pull 2018-04-20 20:23:02 -08:00
Dusty.P 6d8f3531a7 Switching to fetchval 2018-04-20 20:11:55 -08:00
Dusty.P 9ef06824a7 Switching to fetchval 2018-04-20 19:49:01 -08:00
Dusty.P 0654bd51eb Merge remote-tracking branch 'origin/development' into development 2018-04-20 19:40:29 -08:00
Dusty.P 3a43e18c61 Switching to fetchval 2018-04-20 19:40:04 -08:00
Dusty.P 6d49bcea36 Switching to fetchval 2018-04-20 19:33:20 -08:00
Dusty.P 61f632a359 Added TODOs 2018-04-20 00:21:29 -08:00
Dusty.P 76574f94a7 Began conversion to asyncpg for DB 2018-04-16 01:37:53 -08:00
35 changed files with 1356 additions and 618 deletions
Binary file not shown.
+90 -1
View File
@@ -1,4 +1,93 @@
bot_secrets.json
google_client_secret.json
__pycache__/
logs/*
*.sh.swp
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# mypy
.mypy_cache/
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/dictionaries
.idea/**/shelf
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
cmake-build-release/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
+1 -4
View File
@@ -4,11 +4,8 @@
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="R User Library" level="project" />
<orderEntry type="library" name="R Skeletons" level="application" />
</component>
<component name="TestRunnerService">
<option name="projectConfiguration" value="py.test" />
<option name="PROJECT_TEST_RUNNER" value="py.test" />
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>
+5
View File
@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="Dustin.Pianalto">
<words>
<w>rcon</w>
</words>
</dictionary>
</component>
+1 -1
View File
@@ -3,5 +3,5 @@
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (Geeksbot)" project-jdk-type="Python SDK" />
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
-54
View File
@@ -1,54 +0,0 @@
import discord
from discord.ext import commands
import logging
from .imports.utils import paginate, run_command
import asyncio
owner_id = 351794468870946827
embed_color = discord.Colour.from_rgb(49, 107, 111)
git_log = logging.getLogger('git')
class Git:
def __init__(self, bot):
self.bot = bot
@commands.group(case_insensitive=True)
async def git(self, ctx):
"""Run help git for more info"""
pass
@git.command()
@commands.is_owner()
async def pull(self, ctx):
em = discord.Embed(style='rich',
title=f'Git Pull',
color=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 fetch --all')), 120) + '\n'
result += await asyncio.wait_for(self.bot.loop.create_task(run_command('git reset --hard '
'origin/master')), 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='', value=f'{page}')
await ctx.send(embed=em)
@git.command()
@commands.is_owner()
async def status(self, ctx):
em = discord.Embed(style='rich',
title=f'Git Pull',
color=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='', value=f'{page}')
await ctx.send(embed=em)
def setup(bot):
bot.add_cog(Git(bot))
-93
View File
@@ -1,93 +0,0 @@
from io import StringIO
import sys
import asyncio
import discord
from discord.ext.commands.formatter import Paginator
from . import checks
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
async def mute(bot, ctx, admin=0, member_id=None):
mute_role = bot.con.one(f'select muted_role from guild_config where guild_id = {ctx.guild.id}')
if mute_role:
if admin or checks.is_admin(bot, ctx):
if ctx.guild.me.guild_permissions.manage_roles:
if member_id:
ctx.guild.get_member(member_id).edit(roles=[discord.utils.get(ctx.guild.roles, id=mute_role)])
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()
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
until python /home/dusty/bin/geeksbot/geeksbot.py; do
until python -m src; do
echo "Geeksbot shutdown with error: $?. Restarting..." >&2
sleep 1
done
-35
View File
@@ -1,35 +0,0 @@
@checks.no_bots()
@commands.cooldown(1,5,commands.BucketType.user)
@commands.command()
async def captcha(self, ctx, type, *, text):
type = type.lower()
if type not in "checked unchecked loading".split():
raise commands.BadArgument(f"Invalid type {type!r}. Available "
"types: `unchecked`, `loading`, `checked`")
font = ImageFont.truetype("Roboto-Regular.ttf", 14)
async with ctx.typing():
img = Image.open(f"blank-captcha-{type}.png")
img.load()
d = ImageDraw.Draw(img)
fnc = functools.partial(d.text, (53,30), text, fill=(0,0,0,255),
font=font)
await self.bot.loop.run_in_executor(None, fnc)
img.save("captcha.png")
await ctx.send(file=discord.File("captcha.png"))
os.system("rm captcha.png")
img.close()
import functools, youtube_dl
#bot.voice_chan = await ctx.author.voice.channel.connect()
bot.voice_chan.stop()
opts = {"format": 'webm[abr>0]/bestaudio/best',"ignoreerrors": True,"default_search": "auto","source_address": "0.0.0.0",'quiet': True}
ydl = youtube_dl.YoutubeDL(opts)
url = 'https://www.youtube.com/watch?v=hjbPszSt5Pc'
func = functools.partial(ydl.extract_info, url, download=False)
info = func()
#bot.voice_chan.play(discord.FFmpegPCMAudio('dead_puppies.mp3'))
bot.voice_chan.play(discord.FFmpegPCMAudio(info['url']))
#async while bot.voice_chan.is_playing():
# pass
#await bot.voice_chan.disconnect()
+51 -28
View File
@@ -1,13 +1,15 @@
from typing import Dict
import discord
from discord.ext import commands
import logging
from datetime import datetime
import json
import aiohttp
from postgres import Postgres
from collections import deque
from googleapiclient.discovery import build
from concurrent import futures
from src.shared_libs import database
log_format = '{asctime}.{msecs:03.0f}|{levelname:<8}|{name}::{message}'
date_format = '%Y.%m.%d %H.%M.%S'
@@ -23,7 +25,7 @@ formatter = logging.Formatter(log_format, style='{', datefmt=date_format)
console_handler.setFormatter(formatter)
logging.getLogger('').addHandler(console_handler)
config_dir = 'config/'
config_dir = 'src/config/'
admin_id_file = 'admin_ids'
extension_dir = 'exts'
owner_id = 351794468870946827
@@ -35,48 +37,70 @@ emojis: Dict[str, str] = {
'x': '',
'y': '',
'poop': '💩',
'boom': '💥',
}
description = 'I am Geeksbot v0.1! Fear me!'
class Geeksbot(commands.Bot):
def __init__(self, **kwargs):
kwargs["command_prefix"] = self.get_custom_prefix
self.description = 'I am Geeksbot! Fear me!'
kwargs['description'] = self.description
super().__init__(**kwargs)
self.aio_session = aiohttp.ClientSession(loop=self.loop)
with open(f'{config_dir}{bot_config_file}') as file:
self.bot_config = json.load(file)
with open(f'{config_dir}{secrets_file}') as file:
self.bot_secrets = json.load(file)
# with open(f'{config_dir}{profane_words_file}') as file:
# self.profane_words = file.readlines()
self.guild_config = {}
self.infected = {}
self.TOKEN = self.bot_secrets['token']
self.embed_color = discord.Colour.from_rgb(49, 107, 111)
self.error_color = discord.Colour.from_rgb(142, 29, 31)
del self.bot_secrets['token']
self.con = Postgres(f" host={self.bot_secrets['db_con']['host']}\
port={self.bot_secrets['db_con']['port']}\
dbname={self.bot_secrets['db_con']['db_name']}\
connect_timeout=10 user={self.bot_secrets['db_con']['user']}\
password={self.bot_secrets['db_con']['password']}")
del self.bot_secrets['db_con']
self.db_con = database.DatabaseConnection(**self.bot_secrets['db_con'])
self.default_prefix = 'g$'
self.voice_chans = {}
self.spam_list = {}
self.owner_id = 351794468870946827
self.__version__ = 'v1.0.0'
self.gcs_service = build('customsearch', 'v1', developerKey=self.bot_secrets['google_search_key'])
self.tpe = futures.ThreadPoolExecutor()
self.geo_api = '2d4e419c2be04c8abe91cb5dd1548c72'
self.unicode_emojis: Dict[str, str] = {
'x': '',
'y': '',
'poop': '💩',
'boom': '💥',
'left_fist': '🤛',
'lock': '🔒',
}
self.book_emojis: Dict[str, str] = {
'unlock': '🔓',
'start': '',
'back': '',
'hash': '#\N{COMBINING ENCLOSING KEYCAP}',
'forward': '',
'end': '',
'close': '🇽',
}
async def get_custom_prefix(self, bot_inst, message):
return self.con.one('select prefix from guild_config where guild_id = %(id)s', {'id': message.guild.id})\
or self.default_prefix
async def logout(self):
await self.db_con.close()
super().logout()
@staticmethod
async def get_custom_prefix(bot_inst, message):
return await bot_inst.db_con.fetchval('select prefix from guild_config where guild_id = $1',
message.guild.id) or bot_inst.default_prefix
async def load_ext(self, ctx, mod=None):
self.load_extension('{0}.{1}'.format(extension_dir, mod))
self.load_extension('src.{0}.{1}'.format(extension_dir, mod))
if ctx is not None:
await ctx.send('{0} loaded.'.format(mod))
async def unload_ext(self, ctx, mod=None):
self.unload_extension('{0}.{1}'.format(extension_dir, mod))
self.unload_extension('src.{0}.{1}'.format(extension_dir, mod))
if ctx is not None:
await ctx.send('{0} unloaded.'.format(mod))
@@ -85,7 +109,7 @@ class Geeksbot(commands.Bot):
self.aio_session.close() # aiohttp is drunk and can't decide if it's a coro or not
bot = Geeksbot(description=description, case_insensitive=True)
bot = Geeksbot(case_insensitive=True)
@bot.command(hidden=True)
@@ -120,14 +144,14 @@ async def unload(ctx, mod):
async def on_message(ctx):
if not ctx.author.bot:
if ctx.guild:
if int(bot.con.one(f"select channel_lockdown from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id})):
if ctx.channel.id in json.loads(bot.con.one(f"select allowed_channels from guild_config "
f"where guild_id = %(id)s",
{'id': ctx.guild.id})):
if int(await bot.db_con.fetchval("select channel_lockdown from guild_config where guild_id = $1",
ctx.guild.id)):
if ctx.channel.id in json.loads(await bot.db_con.fetchval("select allowed_channels from guild_config "
"where guild_id = $1",
ctx.guild.id)):
await bot.process_commands(ctx)
elif ctx.channel.id == 418452585683484680:
prefix = bot.con.one('select prefix from guild_config where guild_id = %(id)s', {'id': ctx.guild.id})
prefix = await bot.db_con.fetchval('select prefix from guild_config where guild_id = $1', ctx.guild.id)
prefix = prefix[0] if prefix else bot.default_prefix
ctx.content = f'{prefix}{ctx.content}'
await bot.process_commands(ctx)
@@ -139,20 +163,19 @@ async def on_message(ctx):
@bot.event
async def on_ready():
bot.remove_command('help')
bot.recent_msgs = {}
for guild in bot.guilds:
bot.recent_msgs[guild.id] = deque(maxlen=50)
logging.info('Logged in as {0.name}|{0.id}'.format(bot.user))
load_list = bot.bot_config['load_list']
for load_item in load_list:
await bot.load_ext(None, f'{load_item}')
logging.info('Extension Loaded: {0}'.format(load_item))
logging.info('Done loading, Geeksbot is active.')
with open(f'{config_dir}reboot', 'r') as f:
reboot = f.readlines()
if int(reboot[0]) == 1:
await bot.get_channel(int(reboot[1])).send('Restart Finished.')
with open(f'{config_dir}reboot', 'w') as f:
f.write(f'0')
logging.info('Done loading, Geeksbot is active.')
bot.run(bot.TOKEN)
View File
+103 -78
View File
@@ -4,11 +4,10 @@ import json
import logging
import inspect
import os
from .imports import checks
from src.imports import checks, utils
config_dir = 'config/'
config_dir = 'src/config/'
admin_id_file = 'admin_ids'
extension_dir = 'extensions'
owner_id = 351794468870946827
embed_color = discord.Colour.from_rgb(49, 107, 111)
bot_config_file = 'bot_config.json'
@@ -52,6 +51,7 @@ class Admin:
await ctx.send('Geeksbot is restarting.')
with open(f'{config_dir}reboot', 'w') as f:
f.write(f'1\n{ctx.channel.id}')
# noinspection PyProtectedMember
os._exit(1)
@commands.command(hidden=True)
@@ -72,20 +72,19 @@ class Admin:
emoji_code = f'<a:{emoji.name}:{emoji.id}>'
else:
emoji_code = f'<:{emoji.name}:{emoji.id}>'
if self.bot.con.all('select id from geeksbot_emojis where id = %(id)s', {'id': emoji.id}):
self.bot.con.run("update geeksbot_emojis set id = %(id)s, name = %(name)s, code = %(emoji_code)s "
"where name = %(name)s",
{'name': emoji.name, 'id': emoji.id, 'emoji_code': emoji_code})
if await self.bot.db_con.fetch('select id from geeksbot_emojis where id = $1', emoji.id):
await self.bot.db_con.execute("update geeksbot_emojis set id = $2, name = $1, code = $3 "
"where name = $1", emoji.name, emoji.id, emoji_code)
else:
self.bot.con.run("insert into geeksbot_emojis(id,name,code) values (%(id)s,%(name)s,%(emoji_code)s)",
{'name': emoji.name, 'id': emoji.id, 'emoji_code': emoji_code})
await self.bot.db_con.execute("insert into geeksbot_emojis(id,name,code) values ($2,$1,$3)",
emoji.name, emoji.id, emoji_code)
await ctx.message.add_reaction('')
await ctx.send(f'Emojis have been updated in the database.')
@commands.command(hidden=True)
@commands.check(checks.is_guild_owner)
async def get_guild_config(self, ctx):
config = self.bot.con.one('select * from guild_config where guild_id = %(id)s', {'id': ctx.guild.id})
config = await self.bot.db_con.fetchrow('select * from guild_config where guild_id = $1', ctx.guild.id)
configs = [str(config)[i:i+1990] for i in range(0, len(config), 1990)]
await ctx.message.author.send(f'The current config for the {ctx.guild.name} guild is:\n')
admin_log.info(configs)
@@ -95,45 +94,49 @@ class Admin:
@commands.group(case_insensitive=True)
async def set(self, ctx):
"""Run help set for more info"""
"""Group for setting configuration options"""
pass
@commands.group(case_insensitive=True)
async def add(self, ctx):
"""Run help set for more info"""
"""Group for adding items to guild config"""
pass
@commands.group(case_insensitive=True)
async def remove(self, ctx):
"""Run help set for more info"""
"""Group for removing items from guild config"""
pass
@set.command(name='admin_chan', aliases=['ac', 'admin_chat', 'admin chat'])
async def _admin_channel(self, ctx, channel: discord.TextChannel=None):
"""Sets the admin notification channel"""
if ctx.guild:
if checks.is_admin(self.bot, ctx):
if await checks.is_admin(self.bot, ctx):
if channel is not None:
self.bot.con.run('update guild_config set admin_chat = %(chan)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'chan': channel.id})
await self.bot.db_con.execute('update guild_config set admin_chat = $2 where guild_id = $1',
ctx.guild.id, channel.id)
await ctx.send(f'{channel.name} is now set as the Admin Chat channel for this guild.')
@set.command(name='channel_lockdown', aliases=['lockdown', 'restrict_access', 'cl'])
async def _channel_lockdown(self, ctx, config='true'):
"""Toggles the channel lockdown restriction
When this is active Geeksbot can only respond in channels defined in allowed_channels
If you run this before configuring allowed_channels it will tell you to run that command first."""
if ctx.guild:
if checks.is_admin(self.bot, ctx):
if await checks.is_admin(self.bot, ctx):
if str(config).lower() == 'true':
if self.bot.con.one('select allowed_channels from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}) is []:
if await self.bot.db_con.fetchval('select allowed_channels from guild_config '
'where guild_id = $1', ctx.guild.id) is []:
await ctx.send('Please set at least one allowed channel before running this command.')
else:
self.bot.con.run('update guild_config set channel_lockdown = True where guild_id = %(id)s',
{'id': ctx.guild.id})
await self.bot.db_con.execute('update guild_config set channel_lockdown = True '
'where guild_id = $1', ctx.guild.id)
await ctx.send('Channel Lockdown is now active.')
elif str(config).lower() == 'false':
if self.bot.con.one('select channel_lockdown from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}):
self.bot.con.run('update guild_config set channel_lockdown = False where guild_id = %(id)s',
{'id': ctx.guild.id})
if await self.bot.db_con.fetchval('select channel_lockdown from guild_config where guild_id = $1',
ctx.guild.id):
await self.bot.db_con.execute('update guild_config set channel_lockdown = False '
'where guild_id = $1', ctx.guild.id)
await ctx.send('Channel Lockdown has been deactivated.')
else:
await ctx.send('Channel Lockdown is already deactivated.')
@@ -144,59 +147,74 @@ class Admin:
@add.command(name='allowed_channels', aliases=['channel', 'ac'])
async def _allowed_channels(self, ctx, *, channels):
"""Defines channels Geeksbot can respond in
This only takes effect if channel_lockdown is enabled.
If one of the channels passed is not found then it is ignored."""
if ctx.guild:
if checks.is_admin(self.bot, ctx):
if await checks.is_admin(self.bot, ctx):
channels = channels.lower().replace(' ', '').split(',')
added = ''
for channel in channels:
chnl = discord.utils.get(ctx.guild.channels, name=channel)
if chnl is None:
await ctx.send(f'{channel} is not a valid text channel in this guild.')
else:
admin_log.info('Chan found')
if self.bot.con.one('select allowed_channels from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}):
if chnl.id in json.loads(self.bot.con.one('select allowed_channels from guild_config '
'where guild_id = %(id)s',
{'id': ctx.guild.id})):
admin_log.info('Chan found in config')
await ctx.send(f'{channel} is already in the list of allowed channels. Skipping...')
else:
admin_log.info('Chan not found in config')
allowed_channels = json.loads(self.bot.con.one('select allowed_channels from '
'guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})).append(chnl.id)
self.bot.con.run('update guild_config set allowed_channels = %(channels)s '
'where guild_id = %(id)s',
{'id': ctx.guild.id, 'channels': allowed_channels})
added = f'{added}\n{channel}'
else:
admin_log.info('Chan not found in config')
allowed_channels = [chnl.id]
self.bot.con.run('update guild_config set allowed_channels = %(channels)s '
'where guild_id = %(id)s',
{'id': ctx.guild.id, 'channels': allowed_channels})
added = f'{added}\n{channel}'
if added != '':
await ctx.send(f'The following channels have been added to the allowed channel list: {added}')
existing_channels = list()
channels_add = list()
admin_log.info(channels)
allowed_channels = await self.bot.db_con.fetchval('select allowed_channels from guild_config '
'where guild_id = $1', ctx.guild.id)
if allowed_channels == 'null':
allowed_channels = None
channels = [discord.utils.get(ctx.guild.channels, name=channel)
for channel in channels if channel is not None]
if allowed_channels and channels:
allowed_channels = [int(channel) for channel in json.loads(allowed_channels)]
existing_channels = [channel for channel in channels if channel.id in allowed_channels]
channels_add = [channel for channel in channels if channel.id not in allowed_channels]
allowed_channels += [channel.id for channel in channels if channel.id not in allowed_channels]
await self.bot.db_con.execute('update guild_config set allowed_channels = $2 where guild_id = $1',
ctx.guild.id, json.dumps(allowed_channels))
elif channels:
admin_log.info('Config is empty')
allowed_channels = [channel.id for channel in channels]
await self.bot.db_con.execute('update guild_config set allowed_channels = $2 '
'where guild_id = $1', ctx.guild.id,
json.dumps(allowed_channels))
else:
await ctx.send('None of those are valid text channels for this guild.')
return
if existing_channels:
channel_str = '\n'.join([str(channel.name) for channel in existing_channels])
await ctx.send(f'The following channels were skipped because they are already in the config:\n'
f'{channel_str}\n')
if channels_add:
channel_str = '\n'.join([str(channel.name) for channel in channels_add])
await ctx.send('The following channels have been added to the allowed channel list:\n'
f'{channel_str}\n')
await ctx.message.add_reaction('')
else:
await ctx.send(f'You are not authorized to run this command.')
else:
await ctx.send('This command must be run from inside a guild.')
@commands.command()
# TODO Fix view_code
@commands.command(hidden=True)
@commands.is_owner()
async def view_code(self, ctx, code_name):
await ctx.send(f"```py\n{inspect.getsource(self.bot.get_command(code_name).callback)}\n```")
pag = utils.Paginator(self.bot, prefix='```py', suffix='```')
pag.add(inspect.getsource(self.bot.all_commands[code_name].callback))
for page in pag.pages():
await ctx.send(page)
@add.command(aliases=['prefix', 'p'])
@commands.cooldown(1, 5, type=commands.BucketType.guild)
async def add_prefix(self, ctx, *, prefix=None):
"""Adds a custom prefix for the current guild
Note: This overwrites the default of g$. If you would
like to keep using g$ you will need to add it to the
Guild config as well."""
if ctx.guild:
if checks.is_admin(self.bot, ctx):
prefixes = self.bot.con.one('select prefix from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if await checks.is_admin(self.bot, ctx):
prefixes = await self.bot.db_con.fetchval('select prefix from guild_config where guild_id = $1',
ctx.guild.id)
if prefix is None:
await ctx.send(prefixes)
return
@@ -208,8 +226,8 @@ class Admin:
if len(prefixes) > 10:
await ctx.send(f'Only 10 prefixes are allowed per guild.\nPlease remove some before adding more.')
prefixes = prefixes[:10]
self.bot.con.run('update guild_config set prefix = %(prefixes)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'prefixes': prefixes})
await self.bot.db_con.execute('update guild_config set prefix = $2 where guild_id = $1',
ctx.guild.id, prefixes)
await ctx.guild.me.edit(nick=f'[{prefixes[0]}] Geeksbot')
await ctx.send(f"Updated. You currently have {len(prefixes)} "
f"{'prefix' if len(prefixes) == 1 else 'prefixes'} "
@@ -222,10 +240,13 @@ class Admin:
@remove.command(aliases=['prefix', 'p'])
@commands.cooldown(1, 5, type=commands.BucketType.guild)
async def remove_prefix(self, ctx, *, prefix=None):
"""Removes custom prefix from the current guild
If the last prefix is removed then Geeksbot will default
Back to g$"""
if ctx.guild:
if checks.is_admin(self.bot, ctx):
prefixes = self.bot.con.one('select prefix from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if await checks.is_admin(self.bot, ctx):
prefixes = await self.bot.db_con.fetchval('select prefix from guild_config where guild_id = $1',
ctx.guild.id)
found = 0
if prefix is None:
await ctx.send(prefixes)
@@ -242,8 +263,8 @@ class Admin:
else:
await ctx.send(f'The prefix {p} is not in the config for this guild.')
if found:
self.bot.con.run('update guild_config set prefix = %(prefixes)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'prefixes': prefixes})
await self.bot.db_con.execute('update guild_config set prefix = $2 where guild_id = $1',
ctx.guild.id, prefixes)
await ctx.guild.me.edit(nick=f'[{prefixes[0] if len(prefixes) != 0 else self.bot.default_prefix}] '
f'Geeksbot')
await ctx.send(f"Updated. You currently have {len(prefixes)} "
@@ -258,16 +279,19 @@ class Admin:
@commands.cooldown(1, 5, type=commands.BucketType.guild)
@commands.check(checks.is_guild_owner)
async def _add_admin_role(self, ctx, role=None):
"""Adds role to the admin list for current guild
Allowing members of that role to run admin commands
on the current guild."""
role = discord.utils.get(ctx.guild.roles, name=role)
if role is not None:
roles = json.loads(self.bot.con.one('select admin_roles from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}))
roles = json.loads(await self.bot.db_con.fetchval('select admin_roles from guild_config '
'where guild_id = $1', ctx.guild.id))
if role.name in roles:
await ctx.send(f'{role.name} is already registered as an admin role in this guild.')
else:
roles[role.name] = role.id
self.bot.con.run('update guild_config set admin_roles = %(roles)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'roles': json.dumps(roles)})
await self.bot.db_con.execute('update guild_config set admin_roles = $2 where guild_id = $1',
ctx.guild.id, json.dumps(roles))
await ctx.send(f'{role.name} has been added to the list of admin roles for this guild.')
else:
await ctx.send('You must include a role with this command.')
@@ -276,14 +300,15 @@ class Admin:
@commands.cooldown(1, 5, type=commands.BucketType.guild)
@commands.check(checks.is_guild_owner)
async def _remove_admin_role(self, ctx, role=None):
"""Removes role from admin list in current guild"""
role = discord.utils.get(ctx.guild.roles, name=role)
if role is not None:
roles = json.loads(self.bot.con.one('select admin_roles from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}))
roles = json.loads(await self.bot.db_con.fetchval('select admin_roles from guild_config '
'where guild_id = $1', ctx.guild.id))
if role.name in roles:
del roles[role.name]
self.bot.con.run('update guild_config set admin_roles = %(roles)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'roles': roles})
await self.bot.db_con.execute('update guild_config set admin_roles = $2 where guild_id = $1',
ctx.guild.id, json.dumps(roles))
await ctx.send(f'{role.name} has been removed from the list of admin roles for this guild.')
else:
await ctx.send(f'{role.name} is not registered as an admin role in this guild.')
+90 -107
View File
@@ -1,9 +1,10 @@
import discord
from discord.ext import commands
import logging
from datetime import datetime
import json
import re
from .imports import utils
from src.imports import utils
config_dir = 'config/'
admin_id_file = 'admin_ids'
@@ -50,17 +51,20 @@ class BotEvents:
config_str = f'{config_str}\n{" "*4}{config}: {guild_config[config]}'
return config_str
# noinspection PyUnusedLocal
async def on_raw_message_delete(self, msg_id, chan_id):
self.bot.con.run('update messages set deleted_at = %(time)s where id = %(id)s',
{'time': datetime.utcnow(), 'id': msg_id})
await self.bot.db_con.execute('update messages set deleted_at = $1 where id = $2',
datetime.utcnow(), msg_id)
# noinspection PyUnusedLocal
async def on_raw_bulk_message_delete(self, msg_ids, chan_id):
sql = ''
del_time = datetime.utcnow()
for msg_id in msg_ids:
sql += f';update messages set deleted_at = %(time)s where id = {msg_id}'
self.bot.con.run(sql, {'time': datetime.utcnow()})
await self.bot.db_con.execute('update messages set deleted_at = $1 where id = $2',
del_time, msg_id)
async def on_message(self, ctx):
# noinspection PyBroadException
try:
if ctx.author in self.bot.infected:
if datetime.now().timestamp() > self.bot.infected[ctx.author][1] + 300:
@@ -73,36 +77,22 @@ class BotEvents:
sql = 'insert into messages (id, tts, type, content, embeds, channel, mention_everyone, mentions,\
channel_mentions, role_mentions, webhook, attachments, pinned, reactions, guild, created_at,\
system_content, author) \
values (%(id)s, %(tts)s, %(type)s, %(content)s, %(embeds)s, %(channel)s, %(mention_everyone)s, %(mentions)s,\
%(channel_mentions)s, %(role_mentions)s, %(webhook)s, %(attachments)s, %(pinned)s, %(reactions)s, %(guild)s,\
%(created_at)s, %(system_content)s, %(author)s)'
msg_data = dict()
msg_data['id'] = ctx.id
msg_data['tts'] = ctx.tts
msg_data['type'] = str(ctx.type)
msg_data['content'] = ctx.content
msg_data['embeds'] = [json.dumps(e.to_dict()) for e in ctx.embeds]
msg_data['channel'] = ctx.channel.id
msg_data['mention_everyone'] = ctx.mention_everyone
msg_data['mentions'] = [user.id for user in ctx.mentions]
msg_data['channel_mentions'] = [channel.id for channel in ctx.channel_mentions]
msg_data['role_mentions'] = [role.id for role in ctx.role_mentions]
msg_data['webhook'] = ctx.webhook_id
msg_data['attachments'] = [json.dumps({'id': a.id, 'size': a.size, 'height': a.height, 'width': a.width,
'filename': a.filename, 'url': a.url}) for a in ctx.attachments]
msg_data['pinned'] = ctx.pinned
msg_data['guild'] = ctx.guild.id
msg_data['created_at'] = ctx.created_at
msg_data['system_content'] = ctx.system_content
msg_data['author'] = ctx.author.id
msg_data['reactions'] = [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions]
self.bot.con.run(sql, msg_data)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)'
msg_data = [ctx.id, ctx.tts, str(ctx.type), ctx.content, [json.dumps(e.to_dict()) for e in ctx.embeds],
ctx.channel.id, ctx.mention_everyone, [user.id for user in ctx.mentions],
[channel.id for channel in ctx.channel_mentions], [role.id for role in ctx.role_mentions],
ctx.webhook_id, [json.dumps({'id': a.id, 'size': a.size, 'height': a.height, 'width': a.width,
'filename': a.filename, 'url': a.url}) for a in ctx.attachments],
ctx.pinned, [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions],
ctx.guild.id if ctx.guild else ctx.author.id, ctx.created_at, ctx.system_content, ctx.author.id]
await self.bot.db_con.execute(sql, *msg_data)
if ctx.guild:
if ctx.author != ctx.guild.me:
if self.bot.con.one(f"select pg_filter from guild_config where guild_id = {ctx.guild.id}"):
if await self.bot.db_con.fetchval("select pg_filter from guild_config where guild_id = $1",
ctx.guild.id):
profane = 0
for word in self.bot.con.one('select profane_words from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}):
for word in await self.bot.db_con.fetchval('select profane_words from guild_config '
'where guild_id = $1', ctx.guild.id):
word = word.strip()
if word in ctx.content.lower():
events_log.info(f'Found non PG word {word}')
@@ -129,57 +119,44 @@ class BotEvents:
await react.message.channel.send(f"You can't Poop on me {user.mention} :P")
reactions = react.message.reactions
reacts = [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in reactions]
self.bot.con.run('update messages set reactions = %(reacts)s where id = %(id)s',
{'id': react.message.id, 'reacts': reacts})
await self.bot.db_con.execute('update messages set reactions = $2 where id = $1',
react.message.id, reacts)
async def on_message_edit(self, before, ctx):
previous_content = self.bot.con.one('select previous_content from messages where id = %(id)s', {'id': ctx.id})
previous_content = await self.bot.db_con.fetchval('select previous_content from messages where id = $1', ctx.id)
if previous_content:
previous_content.append(before.content)
else:
previous_content = [before.content]
previous_embeds = self.bot.con.one('select previous_embeds from messages where id = %(id)s', {'id': ctx.id})
previous_embeds = await self.bot.db_con.fetchval('select previous_embeds from messages where id = $1', ctx.id)
if previous_embeds:
previous_embeds.append([json.dumps(e.to_dict()) for e in before.embeds])
else:
previous_embeds = [[json.dumps(e.to_dict()) for e in before.embeds]]
sql = 'update messages set (edited_at, previous_content, previous_embeds, tts, type, content,\
embeds, channel, mention_everyone, mentions, channel_mentions, role_mentions, webhook,\
attachments, pinned, reactions, guild, created_at, system_content, author) \
= (%(edited_at)s, %(previous_content)s, %(previous_embeds)s, %(tts)s, %(type)s, %(content)s,\
%(embeds)s, %(channel)s, %(mention_everyone)s, %(mentions)s, %(channel_mentions)s, %(role_mentions)s,\
%(webhook)s, %(attachments)s, %(pinned)s, %(reactions)s, %(guild)s, %(created_at)s, %(system_content)s,\
%(author)s) where id = %(id)s'
msg_data = dict()
msg_data['id'] = ctx.id
msg_data['tts'] = ctx.tts
msg_data['type'] = str(ctx.type)
msg_data['content'] = ctx.content
msg_data['embeds'] = [json.dumps(e.to_dict()) for e in ctx.embeds]
msg_data['channel'] = ctx.channel.id
msg_data['mention_everyone'] = ctx.mention_everyone
msg_data['mentions'] = [user.id for user in ctx.mentions]
msg_data['channel_mentions'] = [channel.id for channel in ctx.channel_mentions]
msg_data['role_mentions'] = [role.id for role in ctx.role_mentions]
msg_data['webhook'] = ctx.webhook_id
msg_data['attachments'] = ctx.attachments
msg_data['pinned'] = ctx.pinned
msg_data['guild'] = ctx.guild.id
msg_data['created_at'] = ctx.created_at
msg_data['system_content'] = ctx.system_content
msg_data['author'] = ctx.author.id
msg_data['reactions'] = [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions]
msg_data['previous_content'] = previous_content
msg_data['previous_embeds'] = previous_embeds
msg_data['edited_at'] = datetime.utcnow()
self.bot.con.run(sql, msg_data)
sql = 'update messages set (edited_at, previous_content, previous_embeds, tts, type, content, embeds, ' \
'channel, mention_everyone, mentions, channel_mentions, role_mentions, webhook, attachments, pinned, ' \
'reactions, guild, created_at, system_content, author) = ' \
'($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)' \
'where id = $21'
msg_data = [datetime.utcnow(), previous_content, previous_embeds, ctx.tts, str(ctx.type), ctx.content,
[json.dumps(e.to_dict()) for e in ctx.embeds], ctx.channel.id, ctx.mention_everyone,
[user.id for user in ctx.mentions], [channel.id for channel in ctx.channel_mentions],
[role.id for role in ctx.role_mentions], ctx.webhook_id,
[json.dumps({'id': a.id, 'size': a.size, 'height': a.height, 'width': a.width,
'filename': a.filename, 'url': a.url}) for a in ctx.attachments], ctx.pinned,
[json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions], ctx.guild.id,
ctx.created_at, ctx.system_content, ctx.author.id, ctx.id]
await self.bot.db_con.execute(sql, *msg_data)
@staticmethod
async def on_command_error(ctx, error):
if ctx.channel.id == 418452585683484680 and type(error) == discord.ext.commands.errors.CommandNotFound:
return
for page in utils.paginate(error):
await ctx.send(page)
# noinspection PyMethodMayBeStatic
async def on_command_error(self, ctx, error):
pag = utils.Paginator(ctx.bot, embed=True, max_line_length=48)
pag.set_embed_meta(title=f'Command Error',
color=self.bot.error_color,
thumbnail=f'{ctx.guild.me.avatar_url}')
pag.add(error)
book = utils.Book(pag, (None, ctx.channel, self.bot, ctx.message))
await book.create_book()
async def on_guild_join(self, guild):
with open(f"{config_dir}{default_guild_config_file}", 'r') as file:
@@ -189,31 +166,25 @@ class BotEvents:
default_config['name'] = guild.name.replace("'", "\\'")
default_config['guild_id'] = guild.id
events_log.info(default_config)
self.bot.con.run("insert into guild_config(guild_id, guild_name, admin_roles, rcon_enabled, channel_lockdown,\
raid_status, pg_filter, patreon_enabled, referral_enabled)\
values (%(guild_id)s, %(name)s, %(admin_roles)s, %(rcon_enabled)s, %(channel_lockdown)s,\
%(raid_status)s, %(pg_filter)s, %(patreon_enabled)s, %(referral_enabled)s)",
{'guild_id': default_config['guild_id'],
'name': default_config['name'],
'admin_roles': json.dumps(default_config['admin_roles']),
'rcon_enabled': default_config['rcon_enabled'],
'channel_lockdown': default_config['channel_lockdown'],
'raid_status': default_config['raid_status'],
'pg_filter': default_config['pg_filter'],
'patreon_enabled': default_config['patreon_enabled'],
'referral_enabled': default_config['referral_enabled']
})
await self.bot.db_con.execute("insert into guild_config(guild_id, guild_name, admin_roles, rcon_enabled, "
"channel_lockdown, raid_status, pg_filter, patreon_enabled, referral_enabled) "
"values ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
default_config['guild_id'], default_config['name'],
json.dumps(default_config['admin_roles']), default_config['rcon_enabled'],
default_config['channel_lockdown'], default_config['raid_status'],
default_config['pg_filter'], default_config['patreon_enabled'],
default_config['referral_enabled'])
events_log.info(f'Entry Created for {guild.name}')
await guild.me.edit(nick='[g$] Geeksbot')
async def on_guild_remove(self, guild):
self.bot.con.run(f'delete from guild_config where guild_id = %(id)s', {'id': guild.id})
await self.bot.db_con.execute(f'delete from guild_config where guild_id = $1', guild.id)
events_log.info(f'Left the {guild.name} guild.')
async def on_member_join(self, member):
events_log.info(f'Member joined: {member.name} {member.id} Guild: {member.guild.name} {member.guild.id}')
join_chan = self.bot.con.one('select join_leave_chat from guild_config where guild_id = %(id)s',
{'id': member.guild.id})
join_chan = await self.bot.db_con.fetchval('select join_leave_chat from guild_config where guild_id = $1',
member.guild.id)
if join_chan:
em = discord.Embed(style='rich',
color=embed_color
@@ -226,31 +197,30 @@ class BotEvents:
em.set_footer(text=f"{member.guild.name} | {member.joined_at.strftime('%Y-%m-%d at %H:%M:%S GMT')}",
icon_url=member.guild.icon_url)
await discord.utils.get(member.guild.channels, id=join_chan).send(embed=em)
mem_data = {'id': member.id,
'name': member.name,
'discriminator': member.discriminator,
'bot': member.bot
}
mem = self.bot.con.one('select guilds,nicks from user_data where id = %(id)s', {'id': member.id})
mem_data = [member.id,
member.name,
member.discriminator,
member.bot
]
mem = await self.bot.db_con.fetchval('select guilds,nicks from user_data where id = $1', member.id)
if mem:
mem[1].append(json.dumps({member.guild.id: member.display_name}))
mem[0].append(member.guild.id)
mem_data['nicks'] = mem[1]
mem_data['guilds'] = mem[0]
self.bot.con.run('update user_data set (name, discriminator, bot, nicks, guilds) =\
(%(name)s, %(discriminator)s, %(bot)s, %(nicks)s, %(guilds)s) where\
id = %(id)s', mem_data)
mem_data.append(mem[1])
mem_data.append(mem[0])
self.bot.con.run('update user_data set (name, discriminator, bot, nicks, guilds) = '
'($2, $3, $4, $5, $6) where id = $1', *mem_data)
else:
mem_data['nicks'] = [json.dumps({member.guild.id: member.display_name})]
mem_data['guilds'] = [member.guild.id]
self.bot.con.run('insert into user_data (id, name, discriminator, bot, nicks, guilds) values\
(%(id)s, %(name)s, %(discriminator)s, %(bot)s, %(nicks)s, %(guilds)s)', mem_data)
mem_data.append([json.dumps({member.guild.id: member.display_name})])
mem_data.append([member.guild.id])
self.bot.con.run('insert into user_data (id, name, discriminator, bot, nicks, guilds) '
'values ($1, $2, $3, $4, $5, $6)', *mem_data)
async def on_member_remove(self, member):
leave_time = datetime.utcnow()
events_log.info(f'Member left: {member.name} {member.id} Guild: {member.guild.name} {member.guild.id}')
join_chan = self.bot.con.one('select join_leave_chat from guild_config where guild_id = %(id)s',
{'id': member.guild.id})
join_chan = await self.bot.db_con.fetchval('select join_leave_chat from guild_config where guild_id = $1',
member.guild.id)
if join_chan:
em = discord.Embed(style='rich',
color=red_color
@@ -273,6 +243,19 @@ class BotEvents:
em.set_footer(text=f"{member.guild.name} | {datetime.utcnow().strftime('%Y-%m-%d at %H:%M:%S GMT')}",
icon_url=member.guild.icon_url)
await discord.utils.get(member.guild.channels, id=join_chan).send(embed=em)
mem_data = [member.id,
member.name,
member.discriminator,
member.bot
]
mem = await self.bot.db_con.fetchrow('select guilds,nicks from user_data where id = $1', member.id)
if mem:
mem[0].remove(member.guild.id)
mem[1].remove(json.dumps({member.guild.id: member.display_name}))
mem_data.append(mem[1])
mem_data.append(mem[0])
self.bot.con.run('update user_data set (name, discriminator, bot, nicks, guilds) = '
'($2, $3, $4, $5, $6) where id = $1', *mem_data)
def setup(bot):
+43 -19
View File
@@ -33,6 +33,9 @@ class Fun:
@commands.command()
@commands.cooldown(1, 30, type=commands.BucketType.user)
async def infect(self, ctx, member: discord.Member, emoji):
"""Infects a user with the given emoji
Every time the user sends a message that I am also in I will react to that message with said emoji."""
if member.id == self.bot.user.id and ctx.author.id != owner_id:
await ctx.send(f'You rolled a Critical Fail...\nInfection bounces off and rebounds on the attacker.')
member = ctx.author
@@ -48,6 +51,7 @@ class Fun:
@commands.command()
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def heal(self, ctx, member: discord.Member):
"""Removes infection from user."""
if ctx.author == member and ctx.author.id != owner_id:
await ctx.send('You can\'t heal yourself silly...')
else:
@@ -57,7 +61,7 @@ class Fun:
else:
await ctx.send(f'{member.display_name} is not infected...')
@commands.command()
@commands.command(hidden=True)
@commands.is_owner()
async def print_infections(self, ctx):
await ctx.author.send(f'```{self.bot.infected}```')
@@ -65,13 +69,15 @@ class Fun:
@commands.command()
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def slap(self, ctx, member: discord.Member):
"""IRC Style Trout Slap"""
trout = await self.bot.db_con.fetchval("select code from geeksbot_emojis where id = 449083238766477312")
if member.id == self.bot.user.id and ctx.author.id != owner_id:
await ctx.send(f'You rolled a Critical Fail...\nThe trout bounces off and rebounds on the attacker.')
await ctx.send(f'{ctx.author.mention} '
f'You slap yourself in the face with a large trout <:trout:408543365085397013>')
f'You slap yourself in the face with a large trout {trout}')
else:
await ctx.send(f'{ctx.author.display_name} slaps '
f'{member.mention} around a bit with a large trout <:trout:408543365085397013>')
f'{member.mention} around a bit with a large trout {trout}')
@staticmethod
def get_factorial(number):
@@ -80,22 +86,23 @@ class Fun:
a = a * (i + 1)
return a
# @commands.command()
# @commands.cooldown(1, 5, type=commands.BucketType.user)
# async def fact(self, ctx, number:int):
# if number < 20001 and number > 0:
# n = 1990
# with ctx.channel.typing():
# a = await self.bot.loop.run_in_executor(None, self.get_factorial, number)
# if len(str(a)) > 6000:
# for b in [str(a)[i:i+n] for i in range(0, len(str(a)), n)]:
# await ctx.author.send(f'```py\n{b}```')
# await ctx.send(f"{ctx.author.mention} Check your DMs.")
# else:
# for b in [str(a)[i:i+n] for i in range(0, len(str(a)), n)]:
# await ctx.send(f'```py\n{b}```')
# else:
# await ctx.send("Invalid number. Please enter a number between 0 and 20,000")
@commands.command()
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def fact(self, ctx, number: int):
"""Returns the given factorial up to 20,000!"""
if 0 < number < 20001:
n = 1990
with ctx.channel.typing():
a = await self.bot.loop.run_in_executor(None, self.get_factorial, number)
if len(str(a)) > 6000:
for b in [str(a)[i:i+n] for i in range(0, len(str(a)), n)]:
await ctx.author.send(f'```py\n{b}```')
await ctx.send(f"{ctx.author.mention} Check your DMs.")
else:
for b in [str(a)[i:i+n] for i in range(0, len(str(a)), n)]:
await ctx.send(f'```py\n{b}```')
else:
await ctx.send("Invalid number. Please enter a number between 0 and 20,000")
@commands.command(hidden=True)
@commands.is_owner()
@@ -141,11 +148,28 @@ class Fun:
else:
await ctx.send('Not connected to that voice channel.')
# noinspection PyUnusedLocal
@commands.command(hidden=True)
@commands.is_owner()
async def volume(self, ctx, volume: float):
self.bot.player.volume = volume
@commands.command(name='explode', aliases=['splode'])
async def explode_user(self, ctx, member: discord.Member=None):
"""Trolls user by punching them to oblivion."""
if member is None or member.id == 396588996706304010:
member = ctx.author
trans = await self.bot.db_con.fetchval('select code from geeksbot_emojis where id = 405943174809255956')
msg = await ctx.send(f'{member.mention}{trans*20}{self.bot.unicode_emojis["left_fist"]}')
for i in range(4):
await asyncio.sleep(0.5)
await msg.edit(content=f'{member.mention}{trans*(20-(i*5))}{self.bot.unicode_emojis["left_fist"]}')
await asyncio.sleep(0.1)
await msg.edit(content=f'{self.bot.unicode_emojis["boom"]}')
await asyncio.sleep(0.5)
await msg.edit(content=f'{self.bot.unicode_emojis["boom"]} <---- {member.mention} that was you...')
def setup(bot):
bot.add_cog(Fun(bot))
+64
View File
@@ -0,0 +1,64 @@
import discord
from discord.ext import commands
import logging
from src.imports.utils import Paginator, run_command, Book
import asyncio
owner_id = 351794468870946827
embed_color = discord.Colour.from_rgb(49, 107, 111)
git_log = logging.getLogger('git')
class Git:
def __init__(self, bot):
self.bot = bot
@commands.group(case_insensitive=True, invoke_without_command=True)
async def git(self, ctx):
"""Shows my Git link"""
em = discord.Embed(style='rich',
title=f'Here is where you can find my code',
url='https://github.com/dustinpianalto/Geeksbot/tree/development',
description='I am the development branch of Geeksbot. You can find the master branch here:\n'
'https://github.com/dustinpianalto/Geeksbot/',
color=embed_color)
em.set_thumbnail(url=f'{ctx.guild.me.avatar_url}')
await ctx.send(embed=em)
@git.command()
@commands.is_owner()
async def pull(self, ctx):
"""Pulls updates from GitHub rebasing branch."""
pag = Paginator(self.bot, max_line_length=44, embed=True)
pag.set_embed_meta(title='Git Pull',
color=self.bot.embed_color,
thumbnail=f'{ctx.guild.me.avatar_url}')
pag.add('\uFFF6' + await asyncio.wait_for(self.bot.loop.create_task(run_command('git fetch --all')), 120))
pag.add(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))
pag.add('\uFFF7\n\uFFF8')
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command('git show --stat | '
'sed "s/.*@.*[.].*/ /g"')), 10))
book = Book(pag, (None, ctx.channel, self.bot, ctx.message))
await book.create_book()
@git.command()
@commands.is_owner()
async def status(self, ctx):
"""Gets status of current branch."""
pag = Paginator(self.bot, max_line_length=44, max_lines=30, embed=True)
pag.set_embed_meta(title='Git Status',
color=self.bot.embed_color,
thumbnail=f'{ctx.guild.me.avatar_url}')
result = await asyncio.wait_for(self.bot.loop.create_task(run_command('git status')), 10)
pag.add(result)
book = Book(pag, (None, ctx.channel, self.bot, ctx.message))
await book.create_book()
def setup(bot):
bot.add_cog(Git(bot))
+32 -32
View File
@@ -1,7 +1,7 @@
import discord
from discord.ext import commands
import json
from .imports import checks
from src.imports import checks
config_dir = 'config'
extension_dir = 'extensions'
@@ -17,12 +17,11 @@ class Patreon:
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def get_patreon_links(self, ctx, target: discord.Member=None):
"""Prints Patreon information for creators on the server."""
if self.bot.con.one('select patreon_enabled from guild_config where guild_id = %(id)s', {'id': ctx.guild.id}):
patreon_info = self.bot.con.one('select patreon_message,patreon_links\
from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
message = patreon_info[0].replace('\\n', '\n')
patreon_links = json.loads(patreon_info[1])
if await self.bot.db_con.fetchval('select patreon_enabled from guild_config where guild_id = $1', ctx.guild.id):
patreon_info = await self.bot.db_con.fetchrow('select patreon_message,patreon_links from guild_config '
'where guild_id = $1', ctx.guild.id)
message = patreon_info['patreon_message'].replace('\\n', '\n')
patreon_links = json.loads(patreon_info['patreon_links'])
for key in patreon_links:
message = message + '\n{0}: {1}'.format(key, patreon_links[key])
if target is None:
@@ -34,23 +33,23 @@ class Patreon:
@commands.command(aliases=['patreon_message'])
async def set_patreon_message(self, ctx, message):
if checks.is_admin(self.bot, ctx):
patreon_message = self.bot.con.one('select patreon_message from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if await checks.is_admin(self.bot, ctx):
patreon_message = await self.bot.db_con.fetchval('select patreon_message from guild_config '
'where guild_id = $1', ctx.guild.id)
if message == patreon_message:
await ctx.send('That is already the current message for this guild.')
else:
self.bot.con.run('update guild_config set patreon_message = %(message)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'message': message})
await self.bot.db_con.execute('update guild_config set patreon_message = $2 where guild_id = $1',
ctx.guild.id, message)
await ctx.send(f'The patreon message for this guild has been set to:\n{message}')
else:
await ctx.send(f'You are not authorized to run this command.')
@commands.command(aliases=['add_patreon', 'set_patreon'])
async def add_patreon_info(self, ctx, name, url):
if checks.is_admin(self.bot, ctx):
patreon_info = self.bot.con.one('select patreon_links from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if await checks.is_admin(self.bot, ctx):
patreon_info = await self.bot.db_con.fetchval('select patreon_links from guild_config where guild_id = $1',
ctx.guild.id)
patreon_links = {}
update = 0
if patreon_info:
@@ -58,8 +57,8 @@ class Patreon:
if name in patreon_links:
update = 1
patreon_links[name] = url
self.bot.con.run('update guild_config set patreon_links = %(links)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'links': json.dumps(patreon_links)})
await self.bot.db_con.execute('update guild_config set patreon_links = $2 where guild_id = $1',
ctx.guild.id, json.dumps(patreon_links))
await ctx.send(f"The Patreon link for {name} has been "
f"{'updated to the new url.' if update else'added to the config for this guild.'}")
else:
@@ -67,15 +66,15 @@ class Patreon:
@commands.command(aliases=['remove_patreon'])
async def remove_patreon_info(self, ctx, name):
if checks.is_admin(self.bot, ctx):
patreon_info = self.bot.con.one('select patreon_links from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if await checks.is_admin(self.bot, ctx):
patreon_info = await self.bot.db_con.fetchval('select patreon_links from guild_config where guild_id = $1',
ctx.guild.id)
if patreon_info:
patreon_links = json.loads(patreon_info)
if name in patreon_links:
del patreon_links[name]
self.bot.con.run('update guild_config set patreon_links = %(links)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'links': json.dumps(patreon_links)})
await self.bot.db_con.execute('update guild_config set patreon_links = $2 where guild_id = $1',
ctx.guild.id, json.dumps(patreon_links))
await ctx.send(f'The Patreon link for {name} has been removed from the config for this guild.')
return
else:
@@ -87,18 +86,18 @@ class Patreon:
@commands.command()
async def enable_patreon(self, ctx, state: bool=True):
if checks.is_admin(self.bot, ctx):
patreon_status = self.bot.con.one('select patreon_enabled from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if await checks.is_admin(self.bot, ctx):
patreon_status = await self.bot.db_con.fetchval('select patreon_enabled from guild_config '
'where guild_id = $1', ctx.guild.id)
if patreon_status and state:
await ctx.send('Patreon is already enabled for this guild.')
elif patreon_status and not state:
self.bot.con.run('update guild_config set patreon_enabled = %(state)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'state': state})
await self.bot.db_con.execute('update guild_config set patreon_enabled = $2 where guild_id = $1',
ctx.guild.id, state)
await ctx.send('Patreon has been disabled for this guild.')
elif not patreon_status and state:
self.bot.con.run('update guild_config set patreon_enabled = %(state)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'state': state})
await self.bot.db_con.execute('update guild_config set patreon_enabled = $2 where guild_id = $1',
ctx.guild.id, state)
await ctx.send('Patreon has been enabled for this guild.')
elif not patreon_status and not state:
await ctx.send('Patreon is already disabled for this guild.')
@@ -107,9 +106,10 @@ class Patreon:
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def referral_links(self, ctx, target: discord.Member=None):
"""Prints G-Portal Referral Links."""
if self.bot.con.one('select referral_enabled from guild_config where guild_id = %(id)s', {'id': ctx.guild.id}):
referral_info = self.bot.con.one('select referral_message,referral_links from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id})
if await self.bot.db_con.fetchval('select referral_enabled from guild_config where guild_id = $1',
ctx.guild.id):
referral_info = await self.bot.db_con.fetchval('select referral_message,referral_links from guild_config '
'where guild_id = $1', ctx.guild.id)
message = referral_info[0]
referral_links = json.loads(referral_info[1])
for key in referral_links:
+56 -48
View File
@@ -7,7 +7,7 @@ import logging
from datetime import datetime
import asyncio
import traceback
from .imports import checks
from src.imports import checks
config_dir = 'config'
admin_id_file = 'admin_ids'
@@ -120,6 +120,7 @@ class Rcon:
True)
con.exec_command('ServerChatToPlayer "{0}" GeeksBot: Admin Geeks have been notified you need assistance. '
'Please be patient.'.format(player))
# noinspection PyProtectedMember
con._sock.close()
for role in admin_roles:
msg = '{0} {1}'.format(msg, discord.utils.get(ctx.guild.roles, id=admin_roles[role]).mention)
@@ -137,15 +138,16 @@ class Rcon:
first_last
"first last"
To view all the valid ARK servers for this guild see list_ark_servers."""
if checks.is_rcon_admin(self.bot, ctx):
if await checks.is_rcon_admin(self.bot, ctx):
if server is not None:
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
'from guild_config where guild_id = $1',
ctx.guild.id))
server = server.replace('_', ' ').title()
if server in rcon_connections:
rcon_connections[server]["monitoring_chat"] = 1
self.bot.con.run('update guild_config set rcon_connections = %(json)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'json': json.dumps(rcon_connections)})
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
ctx.guild.id, json.dumps(rcon_connections))
channel = self.bot.get_channel(rcon_connections[server]['game_chat_chan_id'])
await channel.send('Started monitoring on the {0} server.'.format(server))
await ctx.message.add_reaction('')
@@ -158,6 +160,7 @@ class Rcon:
True)
messages = await self.bot.loop.run_in_executor(None, self.server_chat_background_process,
ctx.guild.id, con)
# noinspection PyProtectedMember
con._sock.close()
except TimeoutError:
rcon_log.error(traceback.format_exc())
@@ -180,8 +183,10 @@ class Rcon:
message = func(ctx, message, rcon_connections['server'])
await channel.send('{0}'.format(message))
await asyncio.sleep(1)
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
'from guild_config '
'where guild_id = $1',
ctx.guild.id))
await channel.send('Monitoring Stopped')
else:
await ctx.send(f'Server not found: {server}')
@@ -195,15 +200,16 @@ class Rcon:
async def end_monitor_chat(self, ctx, *, server=None):
"""Ends chat monitoring on the specified server.
Context is the same as monitor_chat"""
if checks.is_rcon_admin(self.bot, ctx):
if await checks.is_rcon_admin(self.bot, ctx):
if server is not None:
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
'from guild_config where guild_id = $1',
ctx.guild.id))
server = server.replace('_', ' ').title()
if server in rcon_connections:
rcon_connections[server]["monitoring_chat"] = 0
self.bot.con.run('update guild_config set rcon_connections = %(json)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'json': json.dumps(rcon_connections)})
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
ctx.guild.id, json.dumps(rcon_connections))
else:
await ctx.send(f'Server not found: {server}')
else:
@@ -223,9 +229,9 @@ class Rcon:
first_last
"first last"
To view all the valid ARK servers for this guild see list_ark_servers."""
if checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if await checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
if server is not None:
server = server.replace('_', ' ').title()
if server in rcon_connections:
@@ -241,9 +247,10 @@ class Rcon:
'"ip" port "password" if you would like to get info from it.'.format(server))
else:
for server in rcon_connections:
msg = await ctx.send('Getting Data for the {0} server'.format(server.title()))
# noinspection PyBroadException
try:
connection_info = rcon_connections[server]
msg = await ctx.send('Getting Data for the {0} server'.format(server.title()))
async with ctx.channel.typing():
message = self._listplayers(connection_info)
except Exception as e:
@@ -260,10 +267,10 @@ class Rcon:
async def add_rcon_server(self, ctx, server, ip, port, password):
"""Adds the specified server to the current guild\'s rcon config.
All strings (<server>, <ip>, <password>) must be contained inside double quotes."""
if checks.is_rcon_admin(self.bot, ctx):
if await checks.is_rcon_admin(self.bot, ctx):
server = server.title()
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
if server not in rcon_connections:
rcon_connections[server] = {
'ip': ip,
@@ -274,8 +281,8 @@ class Rcon:
'msg_chan_id': 0,
'monitoring_chat': 0
}
self.bot.con.run('update guild_config set rcon_connections = %(connections)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'connections': json.dumps(rcon_connections)})
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
ctx.guild.id, json.dumps(rcon_connections))
await ctx.send('{0} server has been added to my configuration.'.format(server))
else:
await ctx.send('This server name is already in my configuration. Please choose another.')
@@ -289,14 +296,14 @@ class Rcon:
async def remove_rcon_server(self, ctx, server):
"""removes the specified server from the current guild\'s rcon config.
All strings <server> must be contained inside double quotes."""
if checks.is_rcon_admin(self.bot, ctx):
if await checks.is_rcon_admin(self.bot, ctx):
server = server.title()
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
if server in rcon_connections:
del rcon_connections[server]
self.bot.con.run('update guild_config set rcon_connections = %(connections)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'connections': json.dumps(rcon_connections)})
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
ctx.guild.id, json.dumps(rcon_connections))
await ctx.send('{0} has been removed from my configuration.'.format(server))
else:
await ctx.send('{0} is not in my configuration.'.format(server))
@@ -309,10 +316,11 @@ class Rcon:
"""Adds the included Steam 64 IDs to the running whitelist on all the ARK servers in the current guild\'s rcon config.
Steam 64 IDs should be a comma seperated list of IDs.
Example: 76561198024193239,76561198024193239,76561198024193239"""
if checks.is_rcon_admin(self.bot, ctx):
if await checks.is_rcon_admin(self.bot, ctx):
if steam_ids is not None:
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
'from guild_config where guild_id = $1',
ctx.guild.id))
error = 0
error_msg = ''
success_msg = 'Adding to the running whitelist on all servers.'
@@ -356,9 +364,9 @@ class Rcon:
"""Runs SaveWorld on the specified ARK server.
If a server is not specified it will default to running saveworld on all servers in the guild\'s config.
Will print out "World Saved" for each server when the command completes successfully."""
if checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if await checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
success_msg = 'Running saveworld'
if server is None:
success_msg += ' on all the servers:'
@@ -369,7 +377,7 @@ class Rcon:
await msg.edit(content=success_msg.strip())
message = await self.bot.loop.run_in_executor(None, self._saveworld, rcon_connections[server])
except Exception as e:
success_msg = '{0}\n{1}'.format(success_msg, e.strip())
success_msg = '{0}\n{1}'.format(success_msg, e)
await msg.edit(content=success_msg.strip())
else:
success_msg = '{0}\n{1}'.format(success_msg, message.strip())
@@ -398,9 +406,9 @@ class Rcon:
"""Sends a broadcast message to all servers in the guild config.
The message will be prefixed with the Discord name of the person running the command.
Will print "Success" for each server once the broadcast is sent."""
if checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if await checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
if message is not None:
message = f'{ctx.author.display_name}: {message}'
success_msg = f'Broadcasting "{message}" to all servers.'
@@ -414,7 +422,7 @@ class Rcon:
rcon_connections[server],
message)
except Exception as e:
success_msg = '{0}\n{1}'.format(success_msg, e.strip())
success_msg = '{0}\n{1}'.format(success_msg, e)
await msg.edit(content=success_msg.strip())
else:
for mesg in messages:
@@ -435,9 +443,9 @@ class Rcon:
The message will be prefixed with the Discord name of the person running the command.
If <server> has more than one word in it's name it will either need to be surrounded
by double quotes or the words separated by _"""
if checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if await checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
if server is not None:
server = server.replace('_', ' ').title()
if message is not None:
@@ -473,9 +481,9 @@ class Rcon:
and these channel's permissions will be synced to the category.
These channels will be added to the guild's rcon config and are where the
server chat messages will be sent when monitor_chat is run."""
if checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if await checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
edited = 0
category = discord.utils.get(ctx.guild.categories, name='Server Chats')
if category is None:
@@ -498,8 +506,8 @@ class Rcon:
rcon_connections[server]['game_chat_chan_id'] = chan.id
edited = 1
if edited == 1:
self.bot.con.run('update guild_config set rcon_connections = %(json)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'json': json.dumps(rcon_connections)})
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
ctx.guild.id, json.dumps(rcon_connections))
await ctx.message.add_reaction('')
else:
await ctx.send(f'You are not authorized to run this command.')
@@ -509,8 +517,8 @@ class Rcon:
@commands.check(checks.is_restricted_chan)
async def list_ark_servers(self, ctx):
"""Returns a list of all the ARK servers in the current guild\'s config."""
servers = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
servers = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
em = discord.Embed(style='rich',
title=f'__**There are currently {len(servers)} ARK servers in my config:**__',
color=discord.Colour.green()
+22 -27
View File
@@ -7,7 +7,7 @@ import inspect
import textwrap
from contextlib import redirect_stdout
import io
from .imports.utils import paginate, run_command
from src.imports.utils import run_command, format_output, Paginator
ownerids = [351794468870946827, 275280442884751360]
ownerid = 351794468870946827
@@ -37,6 +37,7 @@ class Repl:
async def _eval(self, ctx, *, body: str):
if ctx.author.id != ownerid:
return
pag = Paginator(self.bot)
env = {
'bot': self.bot,
'ctx': ctx,
@@ -60,8 +61,10 @@ class Repl:
with redirect_stdout(stdout):
ret = await func()
except Exception:
value = stdout.getvalue()
await ctx.send('```py\n{}{}\n```'.format(value, traceback.format_exc()))
pag.add(stdout.getvalue())
pag.add(traceback.format_exc())
for page in pag.pages():
await ctx.send(page)
else:
value = stdout.getvalue()
# noinspection PyBroadException
@@ -69,17 +72,12 @@ class Repl:
await ctx.message.add_reaction('')
except Exception:
pass
if ret is None:
if value:
for page in paginate(value):
await ctx.send(page)
else:
self._last_result = ret
if value:
for page in paginate(value):
await ctx.send(page)
for page in paginate(ret):
await ctx.send(page)
value = format_output(value)
pag.add(value)
pag.add(f'\nReturned: {ret}')
self._last_result = ret
for page in pag.pages():
await ctx.send(page)
@commands.command(hidden=True)
async def repl(self, ctx):
@@ -143,12 +141,11 @@ class Repl:
fmt = '{}'.format(value)
try:
if fmt is not None:
if len(fmt) > 1990:
for page in paginate(fmt):
await response.channel.send(page)
await ctx.send(response.channel)
else:
await response.channel.send(f'```py\n{fmt}\n```')
pag = Paginator(self.bot)
pag.add(fmt)
for page in pag.pages():
await response.channel.send(page)
await ctx.send(response.channel)
except discord.Forbidden:
pass
except discord.HTTPException as e:
@@ -159,16 +156,14 @@ class Repl:
if ctx.author.id != ownerid:
return
try:
body = self.cleanup_code(body).split(' ')
result = await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 10)
value = result
for page in paginate(value):
body = self.cleanup_code(body)
pag = Paginator(self.bot)
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 10))
for page in pag.pages():
await ctx.send(page)
await ctx.message.add_reaction('')
except asyncio.TimeoutError:
value = f"Command did not complete in the time allowed."
for page in paginate(value):
await ctx.send(page)
await ctx.send(f"Command did not complete in the time allowed.")
await ctx.message.add_reaction('')
+311 -63
View File
@@ -7,10 +7,19 @@ import psutil
from datetime import datetime, timedelta
import asyncio
import async_timeout
from .imports import checks
from src.imports import checks, utils
import pytz
import gspread
from oauth2client.service_account import ServiceAccountCredentials
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from io import BytesIO
from itertools import chain
import numpy as np
from dateutil.parser import parse
from copy import copy
config_dir = 'config/'
admin_id_file = 'admin_ids'
@@ -22,6 +31,7 @@ invite_match = '(https?://)?(www.)?discord(app.com/(invite|oauth2)|.gg|.io)/[\w\
utils_log = logging.getLogger('utils')
clock_emojis = ['🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚']
replace_tzs = {'MST': 'US/Mountain', 'HST': 'US/Hawaii', 'EST': 'US/Eastern'}
class Utils:
@@ -44,20 +54,20 @@ class Utils:
msg = await self.bot.wait_for('message', timeout=5, check=check)
self.bot.ping_times[i]['rec'] = msg
@commands.command()
@commands.command(hidden=True)
async def channel_ping(self, ctx, wait_time: float=10, message: str='=bump', channel: int=265828729970753537):
await ctx.send('Starting Background Process.')
self.bot.loop.create_task(self._4_hour_ping(channel, message, wait_time))
@commands.command()
@commands.command(hidden=True)
@commands.is_owner()
async def sysinfo(self, ctx):
await ctx.send(f'''
```ml
CPU Percentages: {psutil.cpu_percent(percpu=True)}
Memory Usage: {psutil.virtual_memory().percent}%
Disc Usage: {psutil.disk_usage("/").percent}%
```''')
"""Gets system status for my server."""
await ctx.send(f'```ml\n'
f'CPU Percentages: {psutil.cpu_percent(percpu=True)}\n'
f'Memory Usage: {psutil.virtual_memory().percent}%\n'
f'Disc Usage: {psutil.disk_usage("/").percent}%\n'
f'```')
@commands.command(hidden=True)
async def role(self, ctx, role: str):
@@ -168,7 +178,10 @@ class Utils:
@commands.command()
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def ping(self, ctx, mode='normal', count: int=2):
"""Check the Bot\'s connection to Discord"""
"""Check the Bot\'s connection to Discord
For more detailed information set the <mode> as comp and it will test the ping
<count> number of times."""
em = discord.Embed(style='rich',
title=f'Pong 🏓',
color=discord.Colour.green()
@@ -176,8 +189,8 @@ class Utils:
msg = await ctx.send(embed=em)
time1 = ctx.message.created_at
time = (msg.created_at - time1).total_seconds() * 1000
em.description = f'''Response Time: **{math.ceil(time)}ms**
Discord Latency: **{math.ceil(self.bot.latency*1000)}ms**'''
em.description = f'Response Time: **{math.ceil(time)}ms**\n' \
f'Discord Latency: **{math.ceil(self.bot.latency*1000)}ms**'
await msg.edit(embed=em)
if mode == 'comp':
@@ -206,8 +219,8 @@ class Utils:
time = time.total_seconds()
times.append(time)
value = f"Message Sent:" \
f"{datetime.strftime(self.bot.ping_times[i]['snd'].created_at, '%H:%M:%S.%f')}" \
f"Response Received: {datetime.strftime(now, '%H:%M:%S.%f')}" \
f"{datetime.strftime(self.bot.ping_times[i]['snd'].created_at, '%H:%M:%S.%f')}\n" \
f"Response Received: {datetime.strftime(now, '%H:%M:%S.%f')}\n" \
f"Total Time: {math.ceil(time * 1000)}ms"
await self.bot.ping_times[i]['rec'].delete()
em.add_field(name=f'Ping Test {i}', value=value, inline=True)
@@ -220,11 +233,11 @@ class Utils:
em.add_field(value=f'Total Time for Comprehensive test: {math.ceil(total_time)}ms',
name=f'Average: **{round(total_time/count,1)}ms**',
inline=False)
await msg.edit(embed=em)
await msg.edit(embed=em)
@commands.group(case_insensitive=True)
async def admin(self, ctx):
"""Run help admin for more info"""
"""Group for Admin help requests"""
pass
@admin.command(name='new', aliases=['nr'])
@@ -236,25 +249,20 @@ class Utils:
if ctx.guild:
if request_msg is not None:
if len(request_msg) < 1000:
self.bot.con.run('insert into admin_requests (issuing_member_id, guild_orig, request_text,'
'request_time) values (%(member_id)s, %(guild_id)s, %(text)s, %(time)s)',
{'member_id': ctx.author.id, 'guild_id': ctx.guild.id, 'text': request_msg,
'time': ctx.message.created_at})
channel = self.bot.con.one(f'select admin_chat from guild_config where guild_id = {ctx.guild.id}')
await self.bot.db_con.execute('insert into admin_requests (issuing_member_id, guild_orig, '
'request_text, request_time) values ($1, $2, $3, $4)',
ctx.author.id, ctx.guild.id, request_msg, ctx.message.created_at)
channel = await self.bot.db_con.fetchval(f'select admin_chat from guild_config where guild_id = $1',
ctx.guild.id)
if channel:
chan = discord.utils.get(ctx.guild.channels, id=channel)
msg = ''
admin_roles = []
roles = self.bot.con.one(f'select admin_roles,rcon_admin_roles from guild_config where '
f'guild_id = %(id)s', {'id': ctx.guild.id})
request_id = self.bot.con.one(f'select id from admin_requests where '
f'issuing_member_id = %(member_id)s and request_time = %(time)s',
{'member_id': ctx.author.id, 'time': ctx.message.created_at})
for item in roles:
i = json.loads(item)
for j in i:
if i[j] not in admin_roles:
admin_roles.append(i[j])
roles = await self.bot.db_con.fetchval(f'select admin_roles,rcon_admin_roles from guild_config '
f'where guild_id = $1', ctx.guild.id)
request_id = await self.bot.db_con.fetchval(f'select id from admin_requests where '
f'issuing_member_id = $1 and request_time = $2',
ctx.author.id, ctx.message.created_at)
admin_roles = json.loads(roles).values()
for role in admin_roles:
msg = '{0} {1}'.format(msg, discord.utils.get(ctx.guild.roles, id=role).mention)
msg += f"New Request ID: {request_id} " \
@@ -273,7 +281,7 @@ class Utils:
@admin.command(name='list', aliases=['lr'])
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def list_admin_requests(self, ctx, assigned_to: discord.Member=None):
"""Returns a list of all active Admin help requests for this guild
"""List of all active Admin help requests
If a user runs this command it will return all the requests that they have submitted and are still open.
- The [assigned_to] argument is ignored but will still give an error if an incorrect value is entered.
@@ -286,10 +294,10 @@ class Utils:
title=f'Admin Help Requests',
color=discord.Colour.green()
)
if checks.is_admin(self.bot, ctx) or checks.is_rcon_admin(self.bot, ctx):
if await checks.is_admin(self.bot, ctx) or await checks.is_rcon_admin(self.bot, ctx):
if assigned_to is None:
requests = self.bot.con.all(f'select * from admin_requests where guild_orig = %(guild_id)s '
f'and completed_time is null', {'guild_id': ctx.guild.id})
requests = await self.bot.db_con.fetch(f'select * from admin_requests where guild_orig = $1 '
f'and completed_time is null', ctx.guild.id)
em.title = f'Admin help requests for {ctx.guild.name}'
if requests:
for request in requests:
@@ -306,11 +314,11 @@ class Utils:
else:
em.add_field(name='There are no pending requests for this guild.', value='', inline=False)
else:
if checks.check_admin_role(self.bot, ctx, assigned_to)\
or checks.check_rcon_role(self.bot, ctx, assigned_to):
requests = self.bot.con.all('select * from admin_requests where assigned_to = %(admin_id)s '
'and guild_orig = %(guild_id)s and completed_time is null',
{'admin_id': assigned_to.id, 'guild_id': ctx.guild.id})
if await checks.check_admin_role(self.bot, ctx, assigned_to)\
or await checks.check_rcon_role(self.bot, ctx, assigned_to):
requests = await self.bot.db_con.fetch('select * from admin_requests where assigned_to = $1 '
'and guild_orig = $2 and completed_time is null',
assigned_to.id, ctx.guild.id)
em.title = f'Admin help requests assigned to {assigned_to.display_name} in {ctx.guild.name}'
if requests:
for request in requests:
@@ -322,15 +330,16 @@ class Utils:
"",
inline=False)
else:
em.add_field(name=f'There are no pending requests for {assigned_to.display_name} on this guild.',
em.add_field(name=f'There are no pending requests for '
f'{assigned_to.display_name} on this guild.',
value='',
inline=False)
else:
em.title = f'{assigned_to.display_name} is not an admin in this guild.'
else:
requests = self.bot.con.all('select * from admin_requests where issuing_member_id = %(member_id)s '
'and guild_orig = %(guild_id)s and completed_time is null',
{'member_id': ctx.author.id, 'guild_id': ctx.guild.id})
requests = await self.bot.db_con.fetch('select * from admin_requests where issuing_member_id = $1 '
'and guild_orig = $2 and completed_time is null',
ctx.author.id, ctx.guild.id)
em.title = f'Admin help requests for {ctx.author.display_name}'
if requests:
for request in requests:
@@ -352,7 +361,7 @@ class Utils:
"""Allows Admin to close admin help tickets.
[request_id] must be a valid integer pointing to an open Request ID
"""
if checks.is_admin(self.bot, ctx) or checks.is_rcon_admin(self.bot, ctx):
if await checks.is_admin(self.bot, ctx) or await checks.is_rcon_admin(self.bot, ctx):
if request_ids:
request_ids = request_ids.replace(' ', '').split(',')
for request_id in request_ids:
@@ -361,14 +370,13 @@ class Utils:
except ValueError:
await ctx.send(f'{request_id} is not a valid request id.')
else:
request = self.bot.con.one(f'select * from admin_requests where id = %(request_id)s',
{'request_id': request_id})
request = await self.bot.db_con.fetchrow(f'select * from admin_requests where id = $1',
request_id)
if request:
if request[3] == ctx.guild.id:
if request[6] is None:
self.bot.con.run('update admin_requests set completed_time = %(time_now)s where '
'id = %(request_id)s',
{'time_now': ctx.message.created_at, 'request_id': request_id})
await self.bot.db_con.execute('update admin_requests set completed_time = $1 where '
'id = $2', ctx.message.created_at, request_id)
await ctx.send(f'Request {request_id} by '
f'{ctx.guild.get_member(request[1]).display_name}'
f' has been marked complete.')
@@ -386,7 +394,8 @@ class Utils:
@commands.command(name='weather', aliases=['wu'])
@commands.cooldown(5, 15, type=commands.BucketType.default)
async def get_weather(self, ctx, *, location='palmer ak'):
"""Gets the weather data for the location provided,
"""Gets the weather data for the location given
If no location is included then it will get the weather for the Bot's home location.
"""
try:
@@ -423,7 +432,12 @@ class Utils:
@commands.command(name='localtime', aliases=['time', 'lt'])
@commands.cooldown(1, 3, type=commands.BucketType.user)
async def get_localtime(self, ctx, timezone: str='Anchorage'):
"""Shows the current time in the timezone given
This defaults to the Bot's local timezone of Anchorage Alaska USA if none are given."""
em = discord.Embed()
try:
tz = pytz.timezone(timezone)
localtime = datetime.now(tz=tz)
@@ -444,13 +458,91 @@ class Utils:
em.colour = discord.Colour.red()
await ctx.send(embed=em)
# noinspection PyUnboundLocalVariable
@commands.command(name='gettimein', aliases=['timein', 'gti'])
@commands.cooldown(1, 3, type=commands.BucketType.user)
async def get_time_in_timezone(self, ctx, timezone: str='US/Eastern', *, time: str=None):
"""Convert the time provided to given timezone
Attempts to process the given time and timezone and convert into the given timezone.
Example: g$gti CET Friday June 15 2018 US/Alaska
This will be processed into a datetime with US/Alaska set as the timezone and will
convert it into CET timezone and return both times."""
em = discord.Embed()
if time is None:
em.set_footer(text='Time not given... using current UTC time.')
in_time = datetime.utcnow()
parsed_tz = pytz.timezone('UTC')
else:
try:
orig_time = copy(time)
split_time = time.split()
try:
parsed_tz = pytz.timezone(replace_tzs.get(split_time[-1].upper()) or split_time[-1])
time = utils.replace_text_ignorecase(time, old=split_time[-1], new='')
except pytz.exceptions.UnknownTimeZoneError:
for tz in pytz.all_timezones:
if split_time[-1].lower() in tz.lower():
time = utils.replace_text_ignorecase(time, old=split_time[-1], new='')
if tz in replace_tzs:
tz = replace_tzs['tz']
parsed_tz = pytz.timezone(tz)
break
else:
em.set_footer(text='Valid timezone not found in time string. Using UTC...')
parsed_tz = pytz.timezone('UTC')
if not time.isspace() and not time == '':
in_time = parse(time.upper())
in_time = parsed_tz.localize(in_time)
else:
em.set_footer(text='Time not given. Using current time.')
in_time = datetime.now(tz=parsed_tz)
except ValueError:
raise commands.CommandError(f'For some reason I can\'t parse this time string: \n'
f'{orig_time} {time} {parsed_tz}\n'
f'Examples of valid time strings are in my help documentation.\n'
f'Please try again.')
try:
out_tz = pytz.timezone(timezone)
except pytz.exceptions.UnknownTimeZoneError:
for tz in pytz.all_timezones:
if timezone.lower() in tz.lower():
out_tz = pytz.timezone(tz)
break
else:
out_tz = None
em.title = 'Unknown Timezone.'
em.colour = discord.Colour.red()
finally:
if out_tz:
out_time = in_time.astimezone(out_tz)
em.add_field(name=f'{parsed_tz}',
value=f'{clock_emojis[(in_time.hour % 12)]} {in_time.strftime("%c")}', inline=False)
em.add_field(name=f'{out_tz}',
value=f'{clock_emojis[(out_time.hour % 12)]} {out_time.strftime("%c")}', inline=False)
em.colour = self.bot.embed_color
await ctx.send(embed=em)
@commands.command(name='purge', aliases=['clean', 'erase'])
@commands.cooldown(1, 3, type=commands.BucketType.user)
async def purge_messages(self, ctx, number: int=20, member: discord.Member=None):
"""Purge messages from the current channel
By default this will only purge messages sent by Geeksbot and any messages that appear to
have called Geeksbot (aka start with one of the Geeksbot's prefixes for this Guild)
If you want to purge messages from a different user you must provide a number and member
Note: Geeksbot will not find <number> of messages by the given member, it will instead
search the last <number> messages in the channel and delete any by the given member"""
prefixes = await self.bot.db_con.fetchval('select prefix from guild_config '
'where guild_id = $1', ctx.guild.id)
def is_me(message):
nonlocal prefixes
if message.author == self.bot.user:
return True
prefixes = self.bot.con.one('select prefix from guild_config where guild_id = %(id)s', {'id': ctx.guild.id})
if prefixes:
for prefix in prefixes:
if message.content.startswith(prefix):
@@ -464,7 +556,7 @@ class Utils:
def is_author(message):
return message.author == ctx.author
if checks.is_admin(self.bot, ctx):
if await checks.is_admin(self.bot, ctx):
if member:
deleted = await ctx.channel.purge(limit=number, check=is_member)
if member != ctx.author:
@@ -480,7 +572,12 @@ class Utils:
@commands.command(name='purge_all', aliases=['cls', 'clear'])
@commands.cooldown(1, 3, type=commands.BucketType.user)
async def purge_all(self, ctx, number: int=20, contents: str='all'):
if checks.is_admin(self.bot, ctx):
"""Purge all messages from the current channel
Will delete all of the last <number> of messages from the channel
If <contents> is not 'all' then only messages containing <contents>
will be deleted."""
if await checks.is_admin(self.bot, ctx):
if contents != 'all':
deleted = await ctx.channel.purge(limit=number, check=lambda message: message.content == contents)
else:
@@ -493,19 +590,24 @@ class Utils:
@commands.command(name='google', aliases=['g', 'search'])
async def google_search(self, ctx, *, search):
"""WIP Search Google for the given string"""
res = self.bot.gcs_service.cse().list(q=search, cx=self.bot.bot_secrets['cx']).execute()
results = res['items'][:4]
em = discord.Embed()
em.title = f'Google Search'
em.description = f'Top 4 results for "{search}"'
em.colour = embed_color
results = res['items']
pag = utils.Paginator(self.bot, max_line_length=100, embed=True)
pag.set_embed_meta(title='Google Search', description=f'Top results for "{search}"', color=self.bot.embed_color)
for result in results:
em.add_field(name=f'{result["title"]}', value=f'{result["snippet"]}\n{result["link"]}')
await ctx.send(embed=em)
pag.add(f'\uFFF6{result["title"]}\n{result["link"]}', keep_intact=True)
pag.add(f'{result["snippet"]}')
pag.add('\uFFF7\n\uFFF8')
msg = await ctx.send('Starting Book')
book = utils.Book(pag, (msg, ctx.channel, self.bot, ctx.message))
await book.create_book()
@commands.command(hidden=True, name='sheets')
async def google_sheets(self, ctx, member: discord.Member):
if checks.is_admin(self.bot, ctx):
"""Access Google Sheets and looks for the member"""
if await checks.is_admin(self.bot, ctx):
scope = ['https://spreadsheets.google.com/feeds',
'https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name('config/google_client_secret.json', scope)
@@ -528,6 +630,152 @@ class Utils:
value=f'Steam ID: {steam[i]}\nPatreon Level: {tier[i]}\nPatron of: {patron[i]}')
await ctx.send(embed=em)
@commands.command(name='iss')
async def iss_loc(self, ctx):
"""Locate the International Space Station
Gets the location of the ISS and display on a
Blue Marble map."""
def gen_image(iss_loc):
lat = iss_loc['latitude']
lon = iss_loc['longitude']
plt.figure(figsize=(5, 5))
m = Basemap(projection='ortho', resolution=None, lat_0=lat, lon_0=lon)
m.bluemarble(scale=0.5)
x, y = m(lon, lat)
plt.plot(x, y, 'ok', markersize=10, color='red')
plt.text(x, y, ' ISS', fontsize=20, color='red')
plt.tight_layout()
img = BytesIO()
plt.savefig(img, format='png', transparent=True)
img.seek(0)
self.bot.loop.create_task(ctx.send('Current ISS Location', file=discord.File(img, 'output.png')))
async with ctx.typing():
async with self.bot.aio_session.get('https://api.wheretheiss.at/v1/satellites/25544') as response:
loc = await response.json()
await self.bot.loop.run_in_executor(self.bot.tpe, gen_image, loc)
@commands.command(name='location', aliases=['loc', 'map'])
async def map_location(self, ctx, *, location):
"""WIP Displays the given location on a map
Searches for the location provided and plots the Lat Long on a map.
Note: This is SLOW!!! Be prepared to wait up to a minute for the result"""
def draw_map(m, scale=1):
# draw a shaded-relief image
m.shadedrelief(scale=scale)
m.fillcontinents(color="#FFDDCC", lake_color='#DDEEFF')
m.drawmapboundary(fill_color="#DDEEFF")
m.drawcoastlines(color='gray')
m.drawcountries(color='gray')
m.drawstates(color='gray')
# lats and longs are returned as a dictionary
lats = m.drawparallels(np.linspace(-90, 90, 30))
lons = m.drawmeridians(np.linspace(-180, 180, 90))
# keys contain the plt.Line2D instances
lat_lines = chain(*(tup[1][0] for tup in lats.items()))
lon_lines = chain(*(tup[1][0] for tup in lons.items()))
all_lines = chain(lat_lines, lon_lines)
# cycle through these lines and set the desired style
for line in all_lines:
line.set(linestyle='-', alpha=0.3, color='gray')
def gen_image(loc):
lat = loc['lat']
lon = loc['lng']
plt.figure(figsize=(4, 4))
m = Basemap(projection='lcc', width=2E6, height=2E6, resolution='i', lat_0=lat, lon_0=lon)
draw_map(m)
x, y = m(lon, lat)
plt.plot(x, y, 'ok', markersize=5, color='red')
plt.text(x, y, f' {location.title()}', fontsize=12, color='red')
plt.tight_layout()
img = BytesIO()
plt.savefig(img, format='png', transparent=True)
img.seek(0)
self.bot.loop.create_task(ctx.send(file=discord.File(img, f'{location} map.png')))
self.bot.loop.create_task(ctx.trigger_typing())
msg = await ctx.send(f'Checking on location data for {location.title()}')
async with ctx.typing():
async with self.bot.aio_session.get(
f'https://api.opencagedata.com/geocode/v1/json?q={location}&key={self.bot.geo_api}') as result:
data = await result.json()
if data['total_results'] != 0:
location_data = data['results'][0]['geometry']
await msg.edit(content=f'Got Location. Please wait, Generating the image can take up to a minute.')
async with ctx.typing():
await self.bot.loop.run_in_executor(self.bot.tpe, gen_image, location_data)
await msg.delete()
else:
await msg.edit(content=f'I can\'t find any data for that location.\nPlease try again.')
@commands.command(name='help', aliases=['h'])
@commands.cooldown(1, 5, commands.BucketType.user)
async def custom_help(self, ctx, *, command: str=None):
"""This help message"""
pag = utils.Paginator(self.bot, embed=True, max_line_length=48)
prefixes = await self.bot.get_custom_prefix(self.bot, ctx.message)
if isinstance(prefixes, list):
prefixes = ', '.join(prefixes)
owner = await self.bot.get_user_info(self.bot.owner_id)
if command is None:
pag.set_embed_meta(title='Geeksbot Help',
description=f'For more information about a command please run\n'
f'{prefixes.split(",")[0]}help [group] <command>',
thumbnail=f'{ctx.guild.me.avatar_url}')
pag.add(f"\uFFF6Welcome to Geeksbot's help command.\n"
f"< {self.bot.description} >\n\n"
f"Below you will find some basic information about me.\n\n"
f"Version: <{self.bot.__version__}>\n\n"
f"Owner: <Dusty.P>\n"
f"> Username: {owner.name}#{owner.discriminator}\n"
f"> ID: {owner.id}\n\n"
f"Prefixes available for this guild:\n"
f"> {prefixes}\n\uFFF7\n\uFFF8")
for cog in sorted(self.bot.cogs):
for command in sorted(self.bot.get_cog_commands(cog), key=lambda x: x.name):
if not command.hidden:
pag.add(f'\uFFF6{command.name}')
pag.add(f'> {command.short_doc}', truncate=True)
try:
for com in sorted(command.commands, key=lambda x: x.name):
if not com.hidden:
pag.add(f'# {com.name}')
pag.add(f'> {com.short_doc}', truncate=True)
except AttributeError as e:
pass
pag.add('\uFFF7')
pag.add('\uFFF8')
else:
pag.set_embed_meta(title='Geeksbot Help',
thumbnail=f'{ctx.guild.me.avatar_url}')
command = command.split(maxsplit=1)
if command[0] in self.bot.all_commands:
if len(command) > 1 and self.bot.all_commands[command[0]].group:
command = self.bot.all_commands[command[0]].all_commands.get(command[1], None)
else:
command = self.bot.all_commands[command[0]]
else:
command = None
if command and not command.hidden:
pag.add(f'\uFFF6{command.name}')
pag.add(f'Usage: {prefixes.split()[0]}{command.signature}\n')
pag.add(f'\uFFF0\n{command.help}')
else:
pag.add('\uFFF6There is no command by that name.\n>')
book = utils.Book(pag, (None, ctx.channel, self.bot, ctx.message))
await book.create_book()
def setup(bot):
bot.add_cog(Utils(bot))
View File
@@ -1,46 +1,46 @@
import discord
import json
from . import utils
from src.imports import utils
owner_id = 351794468870946827
def check_admin_role(bot, ctx, member):
admin_roles = json.loads(bot.con.one(f"select admin_roles from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id}))
async def check_admin_role(bot, ctx, member):
admin_roles = json.loads(await bot.db_con.fetchval(f"select admin_roles from guild_config where guild_id = $1",
ctx.guild.id))
for role in admin_roles:
if discord.utils.get(ctx.guild.roles, id=admin_roles[role]) in member.roles:
return True
return member.id == ctx.guild.owner.id or member.id == owner_id
def check_rcon_role(bot, ctx, member):
rcon_admin_roles = json.loads(bot.con.one("select rcon_admin_roles from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id}))
async def check_rcon_role(bot, ctx, member):
rcon_admin_roles = json.loads(await bot.db_con.fetchval("select rcon_admin_roles from guild_config "
"where guild_id = $1", ctx.guild.id))
for role in rcon_admin_roles:
if discord.utils.get(ctx.guild.roles, id=rcon_admin_roles[role]) in member.roles:
return True
return member.id == ctx.guild.owner.id or member.id == owner_id
def is_admin(bot, ctx):
admin_roles = json.loads(bot.con.one("select admin_roles from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id}))
async def is_admin(bot, ctx):
admin_roles = json.loads(await bot.db_con.fetchval("select admin_roles from guild_config where guild_id = $1",
ctx.guild.id))
for role in admin_roles:
if discord.utils.get(ctx.guild.roles, id=admin_roles[role]) in ctx.message.author.roles:
return True
return ctx.message.author.id == ctx.guild.owner.id or ctx.message.author.id == owner_id
def is_guild_owner(ctx):
async def is_guild_owner(ctx):
if ctx.guild:
return ctx.message.author.id == ctx.guild.owner.id or ctx.message.author.id == owner_id
return False
def is_rcon_admin(bot, ctx):
rcon_admin_roles = json.loads(bot.con.one("select rcon_admin_roles from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id}))
async def is_rcon_admin(bot, ctx):
rcon_admin_roles = json.loads(await bot.db_con.fetchval("select rcon_admin_roles from guild_config "
"where guild_id = $1", ctx.guild.id))
for role in rcon_admin_roles:
if discord.utils.get(ctx.guild.roles, id=rcon_admin_roles[role]) in ctx.message.author.roles:
return True
+449
View File
@@ -0,0 +1,449 @@
from io import StringIO
import sys
import asyncio
import discord
from discord.ext.commands.formatter import Paginator as DannyPag
from src.imports import checks
import re
import typing
from datetime import datetime
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
async def mute(bot, ctx, admin=0, member_id=None):
mute_role = bot.db_con.fetchval(f'select muted_role from guild_config where guild_id = $1', ctx.guild.id)
if mute_role:
if admin or await checks.is_admin(bot, ctx):
if ctx.guild.me.guild_permissions.manage_roles:
if member_id:
ctx.guild.get_member(member_id).edit(roles=[discord.utils.get(ctx.guild.roles, id=mute_role)])
def to_list_of_str(items, out: list=list(), level=1, recurse=0):
# noinspection PyShadowingNames
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 format_output(text):
if type(text) == list:
text = to_list_of_str(text)
elif type(text) == dict:
text = to_list_of_str(text)
return text
def replace_text_ignorecase(in_str: str, old: str, new: str='') -> str:
re_replace = re.compile(re.escape(old), re.IGNORECASE)
return re_replace.sub(f'{new}', in_str)
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()
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='\uFFF0',
description: str='\uFFF0',
color: discord.Colour=None,
thumbnail: str=None,
url: str=None):
if len(title) <= self._max_field_name:
self._embed_title = title
else:
raise RuntimeError('Provided Title is too long')
if len(description) <= self._max_description:
self._embed_description = description
else:
raise RuntimeError('Provided Description is too long')
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.color = 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
+23
View File
@@ -0,0 +1,23 @@
import asyncpg
import asyncio
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