Compare commits

...

167 Commits
4.3.9 ... v4

Author SHA1 Message Date
Kwoth
cb7743e244 Update CHANGELOG.md 2024-05-10 15:31:16 +00:00
Kwoth
7b5fbd184b Update CHANGELOG.md 2024-05-09 06:43:10 +00:00
Kwoth
a9cd1534c2 docs: Updated changelog to have v5 changes. Probably last commit to this branch. 2024-05-09 06:41:43 +00:00
Kwoth
bca845d76a Merge branch 'v4' of https://gitlab.com/kwoth/nadekobot into v4 2024-04-26 12:50:03 +00:00
Kwoth
6c305860bc Fixed some compilation warnings 2024-04-26 12:49:55 +00:00
Kwoth
eee4e64d80 Merge branch 'hokutochen-v4-patch-28749' into 'v4'
Removed random W

See merge request Kwoth/nadekobot!316
2024-04-26 11:55:54 +00:00
Hokuto Chen
236a8aa9ac Removed random W 2024-04-26 05:00:41 +00:00
Kwoth
f491e3d37d Merge branch 'Kieteyuku-v4-patch-93427' into 'v4'
Correctly label auto-restart options

See merge request Kwoth/nadekobot!315
2024-04-24 09:59:12 +00:00
Ene
1f11e4f227 Correctly label auto-restart options 2024-04-24 09:59:11 +00:00
Kwoth
5060d606cf Merge branch 'v4' of https://gitlab.com/kwoth/nadekobot into v4 2024-04-23 09:17:34 +00:00
Kwoth
23b3ad5837 * Fixed pagination emoji, closes #427
* Version upped to 4.3.22
2024-04-23 09:17:02 +00:00
Kwoth
7297318538 Fixed pagination emoji, closes #427 2024-04-23 09:14:27 +00:00
Kwoth
18619b4d3a Merge branch 'banner' into 'v4'
SetBanner command

See merge request Kwoth/nadekobot!314
2024-04-19 16:08:51 +00:00
Cata
ef06388335 Added .setbanner command 2024-04-19 16:08:51 +00:00
Kwoth
d1db54498b Updatec changelog. Upped version to 4.3.21 2024-04-16 13:55:19 +00:00
Kwoth
eef5b3f948 Fixed .clubapply hopefully, and removed some redundant discriminators 2024-04-16 13:48:23 +00:00
Kwoth
d86b5b2b6c Fixed .stock 2024-04-05 03:07:40 +00:00
Kwoth
127a46a9b8 Possible fix for duplicate .bank take in .h .bank 2024-02-09 02:32:34 +00:00
Kwoth
a7e1e8a982 Fixed .config searches followedStreams.maxCount. Version upped to 4.3.20 2024-01-20 15:06:45 +00:00
Kwoth
b0ac35b82e Updated changelog. Version upped to 4.3.19 2024-01-20 14:15:30 +00:00
Kwoth
367135be6a Merge branch 'v4' of https://gitlab.com/kwoth/nadekobot into v4 2024-01-20 14:06:59 +00:00
Kwoth
f69f8548b0 Added followedStreams.maxCount to searches configx 2024-01-20 14:05:20 +00:00
Kwoth
449dbafff7 Merge branch 'v4' into 'v4'
Update GPT ChatterBot

See merge request Kwoth/nadekobot!313
2024-01-16 09:12:14 +00:00
Alexandra
afba004d85 Update GPT ChatterBot
* Updates endpoint from v1/completions to v1/chat/completions
* Add SharpTokens as a library to calculate input token usage
* Subtract input tokens from max_tokens to ensure the API tokens don't exceed the max specified
* Add Chat history support since this API supports it
* Add a personality prompt to tweak the way the bot behaves
* Add a min_tokens config to increase the quality of chat messages when history is enabled
* Adjust the response function to throw an exception so that a null message isn't added to the list.
2024-01-16 09:12:14 +00:00
Kwoth
ef4d38bc87 Changelog updated, version updated to 4.3.18 2023-12-26 17:10:44 +00:00
Kwoth
96851c7c2d Updated lib, fixed icon_url in .showembed 2023-12-26 17:02:02 +00:00
Kwoth
41ae182373 Removed .revimg and .revav, closes #417 2023-12-24 07:51:42 +00:00
Kwoth
9bf5a5a3cd Merge branch 'force-add' into 'v4'
Implemented command to force users into the database, closes #425

Closes #425

See merge request Kwoth/nadekobot!312
2023-12-21 12:45:34 +00:00
Kaoticz
bab23c25a5 Implemented owner-only command .cacheusers to force users into the database, closes #425 2023-12-21 12:45:34 +00:00
Kwoth
0ba8555f56 Merge branch 'v4' into 'v4'
Potential fix for no-show quoteshow

See merge request Kwoth/nadekobot!311
2023-12-11 23:24:04 +00:00
Cata
340c5b2268 Potential fix for no-show quoteshow 2023-12-11 23:24:04 +00:00
Kwoth
77e8c66b73 Merge branch 'hokutochen-v4-patch-27691' into 'v4'
Notifications will be sent even if dms are off when using .give

See merge request Kwoth/nadekobot!310
2023-11-29 12:26:26 +00:00
Hokuto Chen
9d9f8f7f98 Notifications will be sent even if dms are off when using .give 2023-11-29 12:26:26 +00:00
Kwoth
77358a563d Added .clubreject which lets you reject applications to the club. Updated some dependencies. 2023-10-23 17:48:54 +00:00
Kwoth
82a48b101b Fix for langset ts-ts, closes #419 2023-10-17 18:57:17 +00:00
Kwoth
6ebe321de9 Shouldn't be able to apply to clubs while in a club anymore. Fixes #423 2023-10-17 18:44:47 +00:00
Kwoth
b4fec10ee6 Updated changelog. Version upped to 4.3.17 2023-09-06 04:21:14 +00:00
Kwoth
42b9a01550 ci: osx-64 is the correct runtime identifier for macos 2023-09-06 03:59:56 +00:00
Kwoth
5f94b20015 First attempt at creating arm and x64 releases for all 3 platforms 2023-09-06 03:29:41 +00:00
Kwoth
201aa45c6b Merge branch 'v4' into 'v4'
Fixes for MRs 298 / 303 and 305

See merge request Kwoth/nadekobot!307
2023-09-04 04:08:08 +00:00
Ene
97ae7b5a5b fix to gift being character limited (!302), and fixes UserUpdated and UserPresence not correctly ignoring users that are logignored. 2023-09-04 04:08:07 +00:00
Kwoth
9b09f223d9 Merge branch 'dylan.snijder93-v4-patch-73879' into 'v4'
Install yt-dlp instead of youtube-dl

See merge request Kwoth/nadekobot!300
2023-08-16 07:14:39 +00:00
Clithulhu
2caf406254 Install yt-dlp instead of youtube-dl 2023-08-16 07:14:39 +00:00
Kwoth
ce5e04b398 Merge branch 'Amie-chan-v4-patch-89425' into 'v4'
Added Trim() to activity names since apparently some activities have trailing spaces.

See merge request Kwoth/nadekobot!297
2023-06-30 14:15:16 +00:00
Amie
775487ad47 Added Trim() to activity names since apparently some activities have trailing spaces. 2023-06-30 14:15:15 +00:00
Kwoth
41e4936f52 - Fixed .logevents and .log bugs related to thread logging
- Upped version to 4.3.16
- closes #418
2023-05-24 10:33:45 +02:00
Kwoth
d831a116d9 Merge branch 'hokutochen-v4-patch-63450' into 'v4'
Remove %users%

See merge request Kwoth/nadekobot!296
2023-05-20 22:42:56 +00:00
Hokuto Chen
8f43b44677 Remove %users% placeholder from docs 2023-05-20 22:42:56 +00:00
Kwoth
93df4f3bf3 Upped version to 4.3.15, Updated CHANGELOG.md 2023-05-21 00:37:29 +02:00
Kwoth
073b832065 Fixed .rps 'amount' field, closes #415 2023-05-17 17:39:25 +02:00
Kwoth
a01e580e03 Merge branch 'hokutochen-v4-patch-69027' into 'v4'
%img:stuff% Deprecated

See merge request Kwoth/nadekobot!295
2023-05-07 16:14:48 +00:00
Hokuto Chen
6124e2fab5 %img:stuff% Deprecated 2023-05-07 16:14:48 +00:00
Kwoth
4dd31d6a0b Fixed .showembed, closes #410 2023-05-03 02:23:10 +02:00
Kwoth
e8706d4006 Fixed -w 0 for .trivia which allows for infinite games, closes #413 2023-05-02 09:36:27 +02:00
Kwoth
140cc43c98 Merge branch 'hokutochen-v4-patch-43149' into 'v4'
updated updater link

See merge request Kwoth/nadekobot!292
2023-04-06 00:40:30 +00:00
Hokuto Chen
26b7149435 updated updater link 2023-04-06 00:40:29 +00:00
Kwoth
b354ee7269 Merge branch 'v4' into 'v4'
fixed bank award giving error message instead of checkmark

See merge request Kwoth/nadekobot!294
2023-04-06 00:40:02 +00:00
Kamal Tufekcic
b829ca0109 fixed bank award giving error message instead of checkmark 2023-04-06 00:40:02 +00:00
Kwoth
37acdb81e8 Updated CHANGELOG.md, upped version to 4.3.14 2023-04-02 20:46:54 +02:00
Kwoth
a9aea65134 Possible fix for voice issues, thanks to anonymous contributor 2023-04-02 20:37:12 +02:00
Kwoth
5c03c5ba16 .banktake had ok/error responses flipped. No functional change, closes #409 2023-03-23 16:14:45 +01:00
Kwoth
42f00c08fa PermRole should deny messages in threads too, closes #407 2023-03-23 16:13:20 +01:00
Kwoth
598d3b8967 Merge branch 'v4' into 'v4'
Replaced CN API

See merge request Kwoth/nadekobot!291
2023-03-12 05:50:35 +00:00
Kamal Tufekcic
8df41c749b Replaced CN API 2023-03-12 05:50:35 +00:00
Kwoth
0eaa8be2d2 Merge branch 'v4' into 'v4'
Updated LMGTFY to be more offensive like before

See merge request Kwoth/nadekobot!289
2023-03-03 14:54:47 +00:00
Kamal Tufekcic
bdbe76f9f8 Update file Searches.cs 2023-03-03 14:21:59 +00:00
Kamal Tufekcic
08b609a4b4 Update file JokeCommands.cs 2023-02-28 19:50:52 +00:00
Snake26183
42d13b32b2 Merge branch 'v4' of https://gitlab.com/Kwoth/nadekobot into v4 2023-02-28 21:43:53 +00:00
Kwoth
096ada367f Cleanup 2023-02-28 14:49:50 +01:00
Kwoth
0fed33ebda Fixed .h <customeraction> not working with non-ascii characters 2023-02-28 13:22:44 +01:00
snake
cb9e918681 Merge branch 'v4' of https://gitlab.com/Kwoth/nadekobot into v4 2023-02-25 23:39:51 +02:00
Kwoth
bbf167df4d .logserver will now enable newer log events as well, closes #403 2023-02-22 14:43:29 +01:00
Kwoth
595a2b401c Merge branch 'v4' into 'v4'
Fixed incorrect `gpt.model` values, added ChatGPT to docs

See merge request Kwoth/nadekobot!287
2023-02-22 13:37:39 +00:00
Kamal Tufekcic
5713e8414e Fixed incorrect gpt.model values, added ChatGPT to docs 2023-02-22 13:37:39 +00:00
Kwoth
a551caf0da Merge branch 'hokutochen-v4-patch-39233' into 'v4'
removed 21.04 and 21.10 (EOL)

See merge request Kwoth/nadekobot!288
2023-02-21 20:42:14 +00:00
Hokuto Chen
b13f05c4c0 removed 21.04 and 21.10 (EOL) 2023-02-21 20:42:14 +00:00
Kwoth
6d6a3a811f Upped version to 4.2.13, updated changelog 2023-02-20 21:57:42 +01:00
Kwoth
fb4aac9f0d Fixed .log userpresence, closes #402 2023-02-20 21:53:25 +01:00
Kwoth
a98981de86 Changing the searches.yml ytprovider setting should now properly affect music queueing provider, but will require bot restart, closes #401 2023-02-20 02:12:00 +01:00
Kamal Tufekcic
9ef3646711 Update file config-guide.md 2023-02-13 12:26:21 +00:00
Kamal Tufekcic
9c174b8b6f Update file config-guide.md 2023-02-13 12:24:08 +00:00
Kamal Tufekcic
657c1e461c Update file config-guide.md 2023-02-13 12:01:33 +00:00
Kamal Tufekcic
f5a4a698bd Update file GamesConfig.cs 2023-02-13 11:52:33 +00:00
Kwoth
8112337aaf Merge branch 'hokutochen-v4-patch-25972' into 'v4'
fixed withURL not working

See merge request Kwoth/nadekobot!286
2023-02-12 10:20:22 +00:00
Hokuto Chen
73f7394e31 fixed withURL not working 2023-02-12 09:52:56 +00:00
Kwoth
987d88287a Revert update to .net 7 by mistake 2023-02-12 04:36:03 +01:00
Kwoth
9dc783b36f Fixed .betstats not working on european locales.
- Upped version to 4.3.12
2023-02-11 23:04:31 +01:00
Kwoth
13741b8317 Use ytdlp instead of ytdl by default, not retroactive, only new users affected 2023-02-11 22:35:07 +01:00
Kwoth
313ca2674e timed ban should now work with userids (users who are not in the server yet), closes #400 2023-02-11 22:32:36 +01:00
Kwoth
98956481e9 Nhentai removed as it's currently unfixable, closes #396 2023-02-11 22:17:39 +01:00
Kwoth
51e887fe04 Merge branch 'v4' of https://gitlab.com/Kwoth/nadekobot into v4 2023-02-10 20:17:20 +01:00
Kwoth
8ceab64b96 Added default value support to medusa system, thanks to kotz 2023-02-10 20:17:13 +01:00
Kwoth
92b8511cf1 Merge branch 'gpt' into 'v4'
Added GPT-3

See merge request Kwoth/nadekobot!284
2023-02-10 13:10:45 +00:00
Alan Beatty
a6a052571e Added GPT-3 as an alternative to cleverbot in .config games and data/games.yml 2023-02-10 13:10:45 +00:00
Kwoth
699a5e0c8c Added bot_owner_only attribute to medusa system. Upped version to 4..3.11 2023-01-21 00:33:07 +01:00
Kwoth
76fedc5ff1 .jr will no longer fail if the user isn't in the database yet, fixes #399 2023-01-20 22:36:57 +01:00
Kwoth
992aa781fa .translangs title is now a response string 2023-01-20 06:51:29 +01:00
Kwoth
e52bf798cf Added full list of supported languages for .trans command, improved .translangs output 2023-01-20 05:28:13 +01:00
Kwoth
710f4f2ed8 Updated .translangs, it looks better now and makes it clear which shortcode is for which language 2023-01-20 04:43:25 +01:00
Kwoth
dcc27a4a99 Fixed trivia --nohint, fixes #398 2023-01-19 06:55:33 +01:00
Kwoth
032b6bfd57 Added Bot propety for easy access to the ISelfUser to all medusa *Context types 2023-01-15 04:15:51 +01:00
Kwoth
737bbb8ec1 Added bot_perm support to medusa system 2023-01-15 04:07:04 +01:00
Kwoth
e71708f5e8 .h command show now properly show both channel and server user permission requirements 2023-01-15 00:50:25 +01:00
Kwoth
9841d72622 allow multiple [user_perm] attributes on snek commands 2023-01-15 00:15:27 +01:00
Kwoth
391a3f0513 Medusae names should now be case sensitive and will be saved in the config with the proper capitalization 2023-01-15 00:01:42 +01:00
Kwoth
aa3409a9cf Added [user_perm()] support to medusa system 2023-01-14 22:43:13 +01:00
Kwoth
9a80383327 Added filter support to medusa system 2023-01-14 00:37:38 +01:00
Kwoth
7e61f7fb46 Improved how stickeradd works. Optional parameters will be a hidden feature as it's hard to explain. Tags are now also optional. 2023-01-13 22:55:09 +01:00
Kwoth
d526a2fac6 Merge branch 'stickeradd' into 'v4'
See merge request Kwoth/nadekobot!276
2023-01-11 23:39:14 +00:00
Kwoth
80efb954f1 Added .stickeradd command
Updated docs for medusa
2023-01-11 23:39:14 +00:00
Kwoth
67c156e40f Merge branch 'hokutochen-v4-patch-11221' into 'v4'
Fix for DMHelpText

See merge request Kwoth/nadekobot!283
2023-01-10 14:39:24 +00:00
Hokuto Chen
26f76ef7b9 Fix for DMHelpText 2023-01-10 14:39:23 +00:00
Kwoth
90cee1bfa9 Merge branch 'hokutochen-v4-patch-97342' into 'v4'
Removed Debian 9 support. Added debian 11 support

See merge request Kwoth/nadekobot!282
2023-01-10 14:38:21 +00:00
Hokuto Chen
26171a0ff7 Removed Debian 9 support. Added debian 11 support 2023-01-10 14:38:21 +00:00
Kwoth
59447d7aa8 .deletecurrency will now also reset banked currency, closes #395 2023-01-09 02:59:41 +01:00
Kwoth
a4053d0666 Removed Id property from patronuser 2023-01-09 02:48:11 +01:00
Kwoth
fcd016aed3 Reverted PK fix for patron as it's causing migration to fail 2023-01-09 02:43:18 +01:00
Kwoth
719f62a0ac Merge branch 'v4' of https://gitlab.com/Kwoth/nadekobot into v4 2023-01-09 02:05:54 +01:00
Kwoth
9b9fa2f357 - Fixed some potential causes for ratelimit due to default message retry settings
- Fixed a patron rewards bug caused by monthly donation checking not accounting for year increase
- Fixed a patron rewards bug for users who connected the same discord account with multiple patreon accounts
2023-01-09 02:05:33 +01:00
Kwoth
823f4731c3 Update responses.uk-UA.json (POEditor.com) 2022-12-29 14:38:57 +00:00
Kwoth
5feff8f4b2 Update responses.es-ES.json (POEditor.com) 2022-12-29 14:38:56 +00:00
Kwoth
95d20609a8 Update responses.ru-RU.json (POEditor.com) 2022-12-29 14:38:55 +00:00
Kwoth
b416b9f963 Update responses.pt-BR.json (POEditor.com) 2022-12-29 14:38:53 +00:00
Kwoth
a7c48b13a0 Update responses.pl-PL.json (POEditor.com) 2022-12-29 14:38:52 +00:00
Kwoth
003b71ba00 Update responses.it-IT.json (POEditor.com) 2022-12-29 14:38:51 +00:00
Kwoth
89593dcc2c Update responses.id-ID.json (POEditor.com) 2022-12-29 14:38:50 +00:00
Kwoth
fa9352d1f8 Update responses.de-DE.json (POEditor.com) 2022-12-29 14:38:49 +00:00
Kwoth
4a2f7ffc76 Update responses.fr-FR.json (POEditor.com) 2022-12-29 14:38:48 +00:00
Kwoth
fb1555c075 Update responses.nl-NL.json (POEditor.com) 2022-12-29 14:38:47 +00:00
Kwoth
7a0b409d88 Update responses.zh-TW.json (POEditor.com) 2022-12-29 14:38:46 +00:00
Kwoth
c869f2e335 More nullref fixes in streamrole, ref #392 2022-12-23 17:08:13 +01:00
Kwoth
01da7e813e Fixed a nullref in streamrole service 2022-12-23 17:04:08 +01:00
Kwoth
76b7e09905 Greet/bye messages will now get disabled if they're set to a deleted/unknown channel 2022-12-22 21:37:01 +01:00
Kwoth
8dca948224 Fixed some build warnings. Fixed TimeOut punishment not allowing duration 2022-12-22 21:18:26 +01:00
Kwoth
ffcbfe6467 Fixed a bug for .quotedeleteauthor causing the executing user to delete own messages 2022-12-22 21:11:21 +01:00
Kwoth
d5c70def93 Merge branch 'hokutochen-v4-patch-86494' into 'v4'
added 22.04 warning

See merge request Kwoth/nadekobot!277
2022-12-09 03:06:32 +00:00
Hokuto Chen
34dccab16b added 22.04 warning 2022-12-08 19:43:33 +00:00
Kwoth
2fab61c4f8 Merge branch 'feeds' into 'v4'
Feeds custom message

See merge request Kwoth/nadekobot!273
2022-11-22 21:08:42 +00:00
Kwoth
9f96edbb46 You can now specify an optional custom message in .feed and .yun which will be posted along with an update 2022-11-22 21:08:42 +00:00
Kwoth
3c23b58088 Merge branch 'thread_log' into 'v4'
Thread log

See merge request Kwoth/nadekobot!274
2022-11-22 20:41:57 +00:00
Kwoth
e10530bc0e Thread log 2022-11-22 20:41:57 +00:00
Kwoth
f24692e79b Added .doas command which executes the command as if you were the target user 2022-11-19 22:36:18 +01:00
Kwoth
8a6edc17e4 Optimized .waifuinfo 2022-11-15 22:45:00 +01:00
Kwoth
9ce2837f5a Possible optimization for .waifuinfo 2022-11-13 20:40:37 +01:00
Kwoth
e49e3eec69 Version upped to 4.3.10, CHANGELOG.md updated 2022-11-10 22:42:34 +01:00
Kwoth
3992ae392b Fixed nullref in xploop 2022-11-10 22:32:27 +01:00
Kwoth
8f0c5fab47 Fixed nullref in cmdcd 2022-11-08 07:51:29 +01:00
Kwoth
780a260b88 If cleverbot is disabled through permissions, let other things execute 2022-11-08 07:51:20 +01:00
Kwoth
25692b9585 Fixed .feedadd for real 2022-11-04 17:21:05 +01:00
Kwoth
ed3ce52865 Fixed .feedadd, closes #388 2022-11-04 05:08:22 +01:00
Kwoth
f5f0f1e250 Fixed .prune @Target not working bug 2022-10-25 02:07:05 +02:00
Kwoth
9d9e61fdfb Fixed command cooldown calculation. Closes #387 2022-10-25 01:57:58 +02:00
Kwoth
e68e948a80 Aliases now support %target% placeholder. For example 'alias .bft .bf %target% t' 2022-10-22 23:33:42 +02:00
Kwoth
cb98f4aa15 Added .exprtoggleglobal / .extg which can be used to toggle usage of global expressions on the server 2022-10-21 21:31:59 +02:00
Kwoth
bfec0cbcbf OwnerId will be autofilled in creds.yml if it's missing at startup 2022-10-19 23:43:44 +02:00
Kwoth
3e1268f3bb You can now specify time+date (time is optional) in remind instead of relative time, in the format HH:mm dd.MM.YYYY 2022-10-18 23:31:34 +02:00
Kwoth
c28f458972 Updated help text for .antispam and .antiraid 2022-10-18 01:45:00 +02:00
Kwoth
27ac948463 Added .forwardtochannel which will forward messages to the current channel. It has lower priority than fwtoall 2022-10-17 22:20:48 +02:00
Kwoth
3f9a3c4c18 Merge branch 'hokutochen-v4-patch-12507' into 'v4'
upped medusa version and updated brew command

See merge request Kwoth/nadekobot!271
2022-10-17 11:11:37 +00:00
Hokuto Chen
9a5545a951 docs: upped medusa version and updated brew command 2022-10-17 11:11:37 +00:00
Kwoth
584193db18 Added .filterlist command 2022-10-16 22:57:36 +02:00
Kwoth
1a132fd234 Added a missing return 2022-10-15 13:17:30 +02:00
Kwoth
fd6a51ac82 Added appropriate error messages for every club related command. Removed obsolete error messages. 2022-10-15 13:13:31 +02:00
Kwoth
eb1fabb2b7 Prepared several more enums to clarify club related action results 2022-10-14 23:02:43 +02:00
Kwoth
d079e684bd Clubleave errors clarified. Moved enum results to their own files 2022-10-13 22:27:12 +02:00
Kwoth
bf817a1436 Medusa modules (sneks) should now inherit medusa description when listed in .mdls command 2022-10-12 23:29:48 +02:00
Kwoth
78f1624aaf .meload and .meunload are now case sensitive. Previously loaded medusae may need to be reloaded or data/medusae/medusa.yml may need to be edited manually 2022-10-12 20:59:04 +02:00
174 changed files with 34561 additions and 1562 deletions

View File

@@ -14,8 +14,16 @@ variables:
tests: "NadekoBot.Tests"
LINUX_X64_OUTPUT_DIR: "nadekobot-linux-x64"
LINUX_X64_RELEASE: "$CI_COMMIT_TAG-linux-x64-build.tar"
LINUX_ARM64_OUTPUT_DIR: "nadekobot-linux-arm64"
LINUX_ARM64_RELEASE: "$CI_COMMIT_TAG-linux-arm64-build.tar"
MACOS_X64_OUTPUT_DIR: "nadekobot-osx-x64"
MACOS_X64_RELEASE: "$CI_COMMIT_TAG-osx-x64-build.tar"
MACOS_ARM64_OUTPUT_DIR: "nadekobot-osx-arm64"
MACOS_ARM64_RELEASE: "$CI_COMMIT_TAG-osx-arm64-build.tar"
WIN_X64_OUTPUT_DIR: "nadekobot-windows-x64"
WIN_X64_RELEASE: "$CI_COMMIT_TAG-windows-x64-build.zip"
WIN_ARM64_OUTPUT_DIR: "nadekobot-windows-arm64"
WIN_ARM64_RELEASE: "$CI_COMMIT_TAG-windows-arm64-build.zip"
PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/NadekoBot-build/${CI_COMMIT_TAG}"
INSTALLER_OUTPUT_DIR: "nadeko-installers/${CI_COMMIT_TAG}"
INSTALLER_FILE_NAME: "nadeko-setup-${CI_COMMIT_TAG}.exe"
@@ -24,11 +32,19 @@ build:
stage: build
script:
- "dotnet publish -c Release -r linux-x64 --self-contained -o $LINUX_X64_OUTPUT_DIR src/NadekoBot/NadekoBot.csproj"
- "dotnet publish -c Release -r linux-arm64 --self-contained -o $LINUX_ARM64_OUTPUT_DIR src/NadekoBot/NadekoBot.csproj"
- "dotnet publish -c Release -r win7-x64 --self-contained -o $WIN_X64_OUTPUT_DIR src/NadekoBot/NadekoBot.csproj"
- "dotnet publish -c Release -r win7-arm64 --self-contained -o $WIN_ARM64_OUTPUT_DIR src/NadekoBot/NadekoBot.csproj"
- "dotnet publish -c Release -r osx-x64 --self-contained -o $MACOS_X64_OUTPUT_DIR src/NadekoBot/NadekoBot.csproj"
- "dotnet publish -c Release -r osx-arm64 --self-contained -o $MACOS_ARM64_OUTPUT_DIR src/NadekoBot/NadekoBot.csproj"
artifacts:
paths:
- "$LINUX_X64_OUTPUT_DIR/"
- "$LINUX_ARM64_OUTPUT_DIR/"
- "$WIN_X64_OUTPUT_DIR/"
- "$WIN_ARM64_OUTPUT_DIR/"
- "$MACOS_X64_OUTPUT_DIR/"
- "$MACOS_ARM64_OUTPUT_DIR/"
upload-builds:
stage: upload-builds
@@ -38,12 +54,23 @@ upload-builds:
script:
- apk add --no-cache curl tar zip
- "tar cvf $LINUX_X64_RELEASE $LINUX_X64_OUTPUT_DIR/*"
- "tar cvf $LINUX_ARM64_RELEASE $LINUX_ARM64_OUTPUT_DIR/*"
- "tar cvf $MACOS_X64_RELEASE $MACOS_X64_OUTPUT_DIR/*"
- "tar cvf $MACOS_ARM64_RELEASE $MACOS_ARM64_OUTPUT_DIR/*"
- "zip -r $WIN_X64_RELEASE $WIN_X64_OUTPUT_DIR/*"
- "zip -r $WIN_ARM64_RELEASE $WIN_ARM64_OUTPUT_DIR/*"
- |
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $LINUX_X64_RELEASE $PACKAGE_REGISTRY_URL/$LINUX_X64_RELEASE
- |
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $LINUX_ARM64_RELEASE $PACKAGE_REGISTRY_URL/$LINUX_ARM64_RELEASE
- |
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $WIN_X64_RELEASE $PACKAGE_REGISTRY_URL/$WIN_X64_RELEASE
- |
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $WIN_ARM64_RELEASE $PACKAGE_REGISTRY_URL/$WIN_ARM64_RELEASE
- |
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $MACOS_X64_RELEASE $PACKAGE_REGISTRY_URL/$MACOS_X64_RELEASE
- |
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $MACOS_ARM64_RELEASE $PACKAGE_REGISTRY_URL/$MACOS_ARM64_RELEASE
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
@@ -53,7 +80,11 @@ release:
- |
release-cli create --name "NadekoBot v$CI_COMMIT_TAG" --description "## [Changelog](https://gitlab.com/Kwoth/nadekobot/-/blob/v4/CHANGELOG.md#$(echo "$CI_COMMIT_TAG" | sed "s/\.//g")-$(date +%d%m%Y))" --tag-name $CI_COMMIT_TAG \
--assets-link "{\"name\":\"${LINUX_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_X64_RELEASE}\"}" \
--assets-link "{\"name\":\"${WIN_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${WIN_X64_RELEASE}\"}"
--assets-link "{\"name\":\"${LINUX_ARM64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_ARM64_RELEASE}\"}" \
--assets-link "{\"name\":\"${WIN_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${WIN_X64_RELEASE}\"}" \
--assets-link "{\"name\":\"${WIN_ARM64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${WIN_ARM64_RELEASE}\"}" \
--assets-link "{\"name\":\"${MACOS_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${MACOS_X64_RELEASE}\"}" \
--assets-link "{\"name\":\"${MACOS_ARM64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${MACOS_ARM64_RELEASE}\"}"
test:
stage: test

View File

@@ -1,10 +1,244 @@
# Changelog
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
## [5.0.4] - 10.05.2024
### Added
- Added `.shopadd command` You can now sell commands in the shop. The command will execute as if you were the one running it when someone buys it
- type `.h .shopadd` for more info
- Added `.stickyroles` Users leaving the server will have their roles saved to the database and reapplied if they rejoin within 30 days.
- Giveaway commands
- `.ga start <duration> <text>` starts the giveway with the specified duration and message (prize). You may have up to 5 giveaways on the server at once
- `.ga end <id>` prematurely ends the giveaway and selects a winner
- `.ga cancel <id>` cancels the giveaway and doesn't select a winner
- `.ga list` lists active giveaways on the current server
- `.ga reroll <id>` rerolls the winner on the completed giveaway. This only works for 24 hours after the giveaway has ended, or until the bot restarts.
- Users can join the giveaway by adding a :tada: reaction
- Added Todo Commands
- `.todo add <name>` - adds a new todo
- `.todo delete <id>` - deletes a todo item
- `.todo done <id>` - completes a todo (marks it with a checkmark)
- `.todo list` - lists all todos
- `.todo edit <id> <new message>` - edits a todo item message
- `.todo show <id>` - Shows the text of the specified todo item
- In addition to that, there are also Todo archive commands
- `.todo archive add <name>` - adds all current todos (completed and not completed) to the archived list, your current todo list will become cleared
- `.todo archive list` - lists all your archived todo lists
- `.todo archive show <id>` - shows the todo items from one of your archived lists
- `.todo archive delete <id>` - deletes and archived todo list
- Added `.queufairplay` / `.qfp` (music feature) re-added but it works differently
- Once you run it, it will reorganize currently queued songs so that they're in a fair order.
- Added `.clubrename` command to uh rename your club
- For self-hosters:
- Added `.sqlselectcsv` which will return results in a csv file instead of an embed.
- You can set whether nadeko ignores other bots in `bot.yml`
- You can set shop sale cut in `gambling.yml`
- Added a page parameter to `.feedlist`
- Added seconds/sec/s to `.convert` command
- Added `.prunecancel` to cancel an active prune
- Added progress reporting when using `.prune`.
- Added audit log reason for `.setrole` and some other features
### Changed
- Users who have manage messages perm in the channel will now be excluded from link and invite filtering (`.sfi` and `.sfl`)
- `.send` command should work consistently and correctly now. You can have targets from other shards too. The usage has been changed. refer to `.h .send` for more info
- `.serverinfo` no longer takes a server name. It only takes an id or no arguments
- You can now target a different channel with .repeat
- `.cmds <module name>`, `.cmds <group name` and `.mdls` looks better
- The bot will now send a discord Reply to every command
- `.queuesearch` / `.qs` will now show the results with respective video thumbnails
- A lot of code cleanup (still a lot to be done) and Quality of Life improvements
- `.inrole` will now show mentions primarily, and use a spoiler to show usernames
### Fixed
- `.feed` should now correctly accept (and show) the message which can be passed as the third parameter
- `.say` will now correctly report errors if the user or the bot don't have sufficent perms to send a message in the targeted channel
- Fixed `.invitelist` not paginating correctly
- `.serverinfo` will now correctly work for other shards
- `.send` will now correctly work for other shards
- `.translate` command will no longer fail if the user capitalizes the language name
- Fixed xp card user avatar not showing for some users
### Removed
- `.poll` commands removed as discord added polls
- `.scpl` and other music soundcloud commands have been removed as soundcloud isn't issuing new api tokens for years now
- Removed a lot of useless and nsfw commands
- Removed log voice presence TTS
- Cleanup: Removed a lot of obsolete aliases from aliases.yml
## [4.3.22] - 23.04.2023
### Added
- Added `.setbanner` command (thx cata)
### Fixed
- Fixed pagination error due to a missing emoji
## [4.3.21] - 19.04.2023
### Fixed
- Possible fix for a duplicate in `.h bank`
- Fixed `.stock` command
- Fixed `.clubapply` and `.clubaccept`
- Removed some redundant discriminators
## [4.3.20] - 20.01.2024
### Fixed
- Fixed `.config searches followedStreams.maxCount` not working
## [4.3.19] - 20.01.2024
### Added
- Added `followedStreams.maxCount` to `searches.yml` which lets bot owners change the default of 10 per server
### Changed
- Improvements to GPT ChatterBot (thx alexandra)
- Add a personality prompt to tweak the way chatgpt bot behaves
- Added Chat history support to chatgpt ChatterBot
- Chatgpt token usage now correctly calculated
- More chatgpt configs in `games.yml`
## [4.3.18] - 26.12.2023
### Added
- Added `.cacheusers` command (thx Kotz)
- Added `.clubreject` which lets you reject club applications
### Changed
- Updated discord lib, there should be less console errors now
### Fixed
- Fixed `icon_url` when using `.showembed`
- Fixed `.quoteshow` not showing sometimes (thx Cata)
- Notifications will no longer be sent if dms are off when using `.give`
- Users should no longer be able to apply to clubs while in a club already (especially not to the same club they're already in)
### Removed
- `.revimg` and `.revav` as google removed reverse image search
## [4.3.17] - 06.09.2023
### Fixed
- Fix to waifu gifts being character limited
- Fixes UserUpdated and UserPresence not correctly ignoring users that are logignored
- Added Trim() to activity names since apparently some activities have trailing spaces.
## [4.3.16] - 24.05.2023
### Fixed
- Fixed missing events from `.logevents`
- Fixed `.log` thread deleted and thread created events not working properly
## [4.3.15] - 21.05.2023
### Fixed
- Fixed -w 0 in trivia
- Fixed `.rps` amount field in the response
- Fixed `.showembed` output
- Fixed bank award's incorrect output message
## [4.3.14] - 02.04.2023
### Fixed
- Fixed voice hearbeat issue
- `.banktake` had ok/error responses flipped. No functional change
- PermRole should deny messages in threads todo
- Fixed chucknorris jokes
- `.logserver` will now
## [4.3.13] - 20.02.2023
### Fixed
- Fixed `.log userpresence`
- `.q` will now use `yt-dlp` if anything other than `ytProvider: Ytdl` is set in `data/searches.yml`
- Fixed Title links on some embeds
## [4.3.12] - 12.02.2023
### Fixed
- Fixed `.betstats` not working on european locales
- Timed `.ban` will work on users who are not in the server
- Fixed some bugs in the medusa system
## [4.3.11] - 21.01.2023
### Added
- Added `.doas` Bot owner only command
- Added `.stickeradd` command
### Changed
- `.waifuinfo` optimized
- You can now specify an optional custom message in `.feed` and `.yun` which will be posted along with an update
- Greet/bye messages will now get disabled if they're set to a deleted/unknown channel
- Updated response strings
- `.translate` now supports many more languages
- `.translangs` prettier output
### Fixed
- Added logging for thread events
- Fixed a bug for `.quotedeleteauthor` causing the executing user to delete own messages
- Fixed TimeOut punishment not alklowing duration
- Fixed a nullref in streamrole service
- Fixed some potential causes for ratelimit due to default message retry settings
- Fixed a patron rewards bug caused by monthly donation checking not accounting for year increase
- Fixed a patron rewards bug for users who connected the same discord account with multiple patreon accounts
- `.deletecurrency` will now also reset banked currency
- Fixed DMHelpText reply
- `.h` command show now properly show both channel and server user permission requirements
- Many fixes and improvements to medusa system
- Fixed trivia --nohint
- `.joinrace` will no longer fail if the user isn't in the database yet
## [4.3.10] - 10.11.2022
### Added
- `.filterlist` / `.fl` command which lists link and invite filtering channels and status
- Added support for `%target%` placeholder in `.alias` command
- Added .forwardtochannel which will forward messages to the current channel. It has lower priority than fwtoall
- Added .exprtoggleglobal / .extg which can be used to toggle usage of global expressions on the server
### Changed
- .meload and .meunload are now case sensitive. Previously loaded medusae may need to be reloaded or data/medusae/medusa.yml may need to be edited manually
- Several club related command have their error messages improved
- Updated help text for .antispam and .antiraid
- You can now specify time and date (time is optional) in `.remind` command instead of relative time, in the format `HH:mm dd.MM.YYYY`
- OwnerId will be automatically added to `creds.yml` at bot startup if it's missing
### Fixed
- Fixed `.cmdcd` console error
- Fixed an error when currency is add per xp
- Fixed an issue preventing execution of expressions starting with @Bot when cleverbot is enabled on the server
- Fixed `.feedadd`
- Fixed `.prune @target` not working
- Medusa modules (sneks) should now inherit medusa description when listed in .mdls command
- Fixed command cooldown calculation
## [4.3.9] - 12.10.2022
### Added
### Added
- `.betstats` shows sum of all bets, payouts and the payout rate in %. Updates once an hour

View File

@@ -28,12 +28,11 @@ WORKDIR /app
RUN set -xe; \
useradd -m nadeko; \
apt-get update; \
apt-get install -y --no-install-recommends libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 python3-pip sudo; \
apt-get install -y --no-install-recommends libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 sudo; \
update-alternatives --install /usr/bin/python python /usr/bin/python3.9 1; \
echo 'Defaults>nadeko env_keep+="ASPNETCORE_* DOTNET_* NadekoBot_* shard_id total_shards TZ"' > /etc/sudoers.d/nadeko; \
pip3 install --no-cache-dir --upgrade youtube-dl; \
apt-get purge -y python3-pip; \
chmod +x /usr/local/bin/youtube-dl; \
curl -Lo /usr/local/bin/yt-dlp https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp; \
chmod a+rx /usr/local/bin/yt-dlp; \
apt-get autoremove -y; \
apt-get autoclean -y

23
docker-compose.yml Normal file
View File

@@ -0,0 +1,23 @@
version: "3.7"
services:
nadeko:
image: insert-image-name-here:latest
depends_on:
- redis
environment:
TZ: Europe/Paris
NadekoBot_RedisOptions: redis,name=nadeko
#NadekoBot_ShardRunCommand: dotnet
#NadekoBot_ShardRunArguments: /app/NadekoBot.dll {0} {1}
volumes:
- /srv/nadeko/conf:/app/conf:ro
- /srv/nadeko/data:/app/data
redis:
image: redis:4-alpine
sysctls:
- net.core.somaxconn=511
command: redis-server --maxmemory 32M --maxmemory-policy volatile-lru
volumes:
- /srv/nadeko/redis-data:/data

View File

@@ -24,6 +24,10 @@ The list below is not complete. Use commands above to see up-to-date list for yo
`trivia.min_win_req` - Restricts a user's ability to make a trivia game with a win requirement less than the set value.
`trivia.currency_reward` - Sets the amount of currency a user will win if they place first in a completed trivia game.
`hangman.currency_reward` - Sets the amount of currency a user will win if they win a game of hangman.
`chatbot` - Sets which chatbot API the bot should use, values: `gpt3`, `cleverbot`.
`gpt.model` - Sets which GPT-3 model the bot should use, values: `ada001`, `babbage001`, `curie001`, `davinci003`.
`gpt.max_tokens` - Sets the limit of tokens GPT-3 can use per call. Find out more about tokens [here](https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them).
*more settings may be available in `data/games.yml` file*

View File

@@ -1,4 +1,4 @@
w# Setting up NadekoBot on Linux
# Setting up NadekoBot on Linux
| Table of Contents |
| :-------------------------------------------------- |
@@ -15,11 +15,14 @@ w# Setting up NadekoBot on Linux
It is recommended that you use **Ubuntu 20.04**, as there have been nearly no problems with it. Also, **32-bit systems are incompatible**.
### Ubuntu 22.04 is ruled as incompatible so double check which ubuntu version you are using.
##### Compatible operating systems:
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10, 22.04
- Ubuntu: 16.04, 18.04, 20.04
- Mint: 19, 20
- Debian: 9, 10
- Debian: 10, 11
- CentOS: 7
- openSUSE
- Fedora: 33, 34, 35
@@ -177,8 +180,8 @@ Choose:
[1] to Run NadekoBot with Auto Restart on "die" command without updating.
[2] to Run with Auto Updating on restart after using "die" command.
```
- Run `1` to update the bot upon restart. (This is done using the `.die` command)
- Run `2` to restart the bot without updating. (This is also done using the `.die` command)
- Run `1` to restart the bot without updating. (This is done using the `.die` command)
- Run `2` to update the bot upon restart. (This is also done using the `.die` command)
5. That's it! to detatch the tmux session:
- Press `Ctrl` + `B`

View File

@@ -7,7 +7,7 @@ Open Terminal (if you don't know how to, click on the magnifying glass on the to
###### Homebrew/wget
*Skip this step if you already have homebrew installed*
- Copy and paste this command, then press Enter:
- `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
- `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
- Install wget
- `brew install wget`

View File

@@ -123,7 +123,7 @@ In order to use music commands, you need ffmpeg and youtube-dl installed.
- [ffmpeg-32bit] | [ffmpeg-64bit] - Download the **appropriate version** for your system (32 bit if you're running a 32 bit OS, or 64 if you're running a 64bit OS). Unzip it, and move `ffmpeg.exe` to a path that's in your PATH environment variable. If you don't know what that is, just move the `ffmpeg.exe` file to `NadekoBot/output`.
- [youtube-dl] - Click to download the file, then move `youtube-dl.exe` to a path that's in your PATH environment variable. If you don't know what that is, just move the `youtube-dl.exe` file to `NadekoBot/system`.
[Updater]: https://dl.nadeko.bot/
[Updater]: https://dl.nadeko.bot/v3/
[Notepad++]: https://notepad-plus-plus.org/
[.net]: https://dotnet.microsoft.com/download/dotnet/5.0
[Redis]: https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.msi

View File

@@ -147,6 +147,7 @@ This section will guide you through how to create a simple custom medusa. You ca
<!-- Use latest .net features -->
<LangVersion>preview</LangVersion>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
<GenerateRequiresPreviewFeaturesAttribute>true</GenerateRequiresPreviewFeaturesAttribute>
<!-- tell .net that this library will be used as a plugin -->
<EnableDynamicLoading>true</EnableDynamicLoading>
@@ -155,7 +156,7 @@ This section will guide you through how to create a simple custom medusa. You ca
<ItemGroup>
<!-- Base medusa package. You MUST reference this in order to have a working medusa -->
<!-- Also, this package comes from MyGet, which requires you to have a NuGet.Config file next to your .csproj -->
<PackageReference Include="Nadeko.Medusa" Version="1.0.1">
<PackageReference Include="Nadeko.Medusa" Version="4.3.9">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@@ -248,4 +249,4 @@ hello:
- Unload it
- `.meunload example_medusa`
- Congrats! You've just made your first medusa!
- Congrats! You've just made your first medusa!

View File

@@ -68,12 +68,7 @@ Some features have their own specific placeholders which are noted in that featu
- `%ban.reason%` - Reason for the ban, if provided
- `%ban.duration%` - Duration of the ban in the form Days.Hours:Minutes (6.05:04)
### Bot stats placeholders
- `%servers%` - Server count bot has joined
- `%users%` - Combined user count on servers the bot has joined
### Shard stats placeholders
### Shard stats placeholders
- `%shard.servercount%` - Server count on current shard
- `%shard.usercount%` - Combined user count on current shard
@@ -89,6 +84,5 @@ Some features have their own specific placeholders which are noted in that featu
- `%rngX-Y%` - Returns a random number between X and Y
- `%target%` - Returns anything the user has written after the trigger (only works on Expressions)
- `%img:stuff%` - Returns an `imgur.com` search for "stuff" (only works on Expressions)
![img](https://puu.sh/B7mgI.png)

View File

@@ -0,0 +1,10 @@
namespace Nadeko.Snake;
/// <summary>
/// Used as a marker class for bot_perm and user_perm Attributes
/// Has no functionality.
/// </summary>
public abstract class MedusaPermAttribute : Attribute
{
}

View File

@@ -0,0 +1,7 @@
namespace Nadeko.Snake;
[AttributeUsage(AttributeTargets.Method)]
public sealed class bot_owner_onlyAttribute : MedusaPermAttribute
{
}

View File

@@ -0,0 +1,23 @@
using Discord;
namespace Nadeko.Snake;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class bot_permAttribute : MedusaPermAttribute
{
public GuildPermission? GuildPerm { get; }
public ChannelPermission? ChannelPerm { get; }
public bot_permAttribute(GuildPermission perm)
{
GuildPerm = perm;
ChannelPerm = null;
}
public bot_permAttribute(ChannelPermission perm)
{
ChannelPerm = perm;
GuildPerm = null;
}
}

View File

@@ -0,0 +1,22 @@
using Discord;
namespace Nadeko.Snake;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class user_permAttribute : MedusaPermAttribute
{
public GuildPermission? GuildPerm { get; }
public ChannelPermission? ChannelPerm { get; }
public user_permAttribute(GuildPermission perm)
{
GuildPerm = perm;
ChannelPerm = null;
}
public user_permAttribute(ChannelPermission perm)
{
ChannelPerm = perm;
GuildPerm = null;
}
}

View File

@@ -22,6 +22,11 @@ public abstract class AnyContext
/// The user who invoked the command
/// </summary>
public abstract IUser User { get; }
/// <summary>
/// Bot user
/// </summary>
public abstract ISelfUser Bot { get; }
/// <summary>
/// Provides access to strings used by this medusa

View File

@@ -10,7 +10,7 @@ public static class MedusaExtensions
embed: embed.Build(),
options: new()
{
RetryMode = RetryMode.AlwaysRetry
RetryMode = RetryMode.Retry502
});
// unlocalized

View File

@@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net.Core" Version="3.104.0" />
<PackageReference Include="Discord.Net.Core" Version="3.204.0" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />
</ItemGroup>

View File

@@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" PrivateAssets="all" GeneratePathProperty="true" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" PrivateAssets="all" GeneratePathProperty="true" />
</ItemGroup>
<PropertyGroup>

View File

@@ -69,7 +69,7 @@ public sealed class Bot
: GatewayIntents.AllUnprivileged,
LogGatewayIntentWarnings = false,
FormatUsersInBidirectionalUnicode = false,
DefaultRetryMode = RetryMode.AlwaysRetry ^ RetryMode.RetryRatelimit
DefaultRetryMode = RetryMode.Retry502
});
_commandService = new(new()
@@ -313,10 +313,29 @@ public sealed class Bot
await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
// await _interactionService.AddModulesAsync(typeof(Bot).Assembly, Services);
IsReady = true;
await EnsureBotOwnershipAsync();
_ = Task.Run(ExecuteReadySubscriptions);
Log.Information("Shard {ShardId} ready", Client.ShardId);
}
private async ValueTask EnsureBotOwnershipAsync()
{
try
{
if (_creds.OwnerIds.Count != 0)
return;
Log.Information("Initializing Owner Id...");
var info = await Client.GetApplicationInfoAsync();
_credsProvider.ModifyCredsFile(x => x.OwnerIds = new[] { info.Owner.Id });
}
catch (Exception ex)
{
Log.Warning("Getting application info failed: {ErrorMessage}", ex.Message);
}
}
private Task ExecuteReadySubscriptions()
{
var readyExecutors = Services.GetServices<IReadyExecutor>();

View File

@@ -3,7 +3,7 @@ using NadekoBot.Modules.Administration.Services;
namespace Discord;
[AttributeUsage(AttributeTargets.Method)]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class UserPermAttribute : RequireUserPermissionAttribute
{
public UserPermAttribute(GuildPerm permission)

View File

@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Configs;
public sealed partial class BotConfig : ICloneable<BotConfig>
{
[Comment(@"DO NOT CHANGE")]
public int Version { get; set; } = 4;
public int Version { get; set; } = 5;
[Comment(@"Most commands, when executed, have a small colored line
next to the response. The color depends whether the command
@@ -39,6 +39,10 @@ Allowed values: Simple, Normal, None")]
@"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
or all owners? (this might cause the bot to lag if there's a lot of owners specified)")]
public bool ForwardToAllOwners { get; set; }
[Comment(@"Any messages sent by users in Bot's DM to be forwarded to the specified channel.
This option will only work when ForwardToAllOwners is set to false")]
public ulong? ForwardToChannel { get; set; }
[Comment(@"When a user DMs the bot with a message which is not a command
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.

View File

@@ -53,6 +53,9 @@ go to https://www.patreon.com/portal -> my clients -> create client")]
[Comment(@"Official cleverbot api key.")]
public string CleverbotApiKey { get; set; }
[Comment(@"Official GPT-3 api key.")]
public string Gpt3ApiKey { get; set; }
[Comment(@"Which cache implementation should bot use.
'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset.
@@ -118,7 +121,7 @@ Windows default
public Creds()
{
Version = 6;
Version = 7;
Token = string.Empty;
UsePrivilegedIntents = true;
OwnerIds = new List<ulong>();
@@ -128,6 +131,7 @@ Windows default
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
BotListToken = string.Empty;
CleverbotApiKey = string.Empty;
Gpt3ApiKey = string.Empty;
BotCache = BotCacheImplemenation.Memory;
RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=";
Db = new()

View File

@@ -14,6 +14,7 @@ public interface IBotCredentials
int TotalShards { get; }
Creds.PatreonSettings Patreon { get; }
string CleverbotApiKey { get; }
string Gpt3ApiKey { get; }
RestartConfig RestartCommand { get; }
Creds.VotesSettings Votes { get; }
string BotListToken { get; }

View File

@@ -29,4 +29,7 @@ public enum LogType
VoicePresenceTts,
UserMuted,
UserWarned,
ThreadDeleted,
ThreadCreated
}

View File

@@ -5,9 +5,11 @@ public sealed class DmContextAdapter : DmContext
public override IMedusaStrings Strings { get; }
public override IDMChannel Channel { get; }
public override IUserMessage Message { get; }
public override ISelfUser Bot { get; }
public override IUser User
=> Message.Author;
private readonly IServiceProvider _services;
private readonly Lazy<IEmbedBuilderService> _ebs;
private readonly Lazy<IBotStrings> _botStrings;
@@ -26,6 +28,7 @@ public sealed class DmContextAdapter : DmContext
Channel = ch;
Message = ctx.Message;
Bot = ctx.Client.CurrentUser;
_ebs = new(_services.GetRequiredService<IEmbedBuilderService>());

View File

@@ -0,0 +1,31 @@
namespace Nadeko.Medusa.Adapters;
public class FilterAdapter : PreconditionAttribute
{
private readonly FilterAttribute _filterAttribute;
private readonly IMedusaStrings _strings;
public FilterAdapter(FilterAttribute filterAttribute,
IMedusaStrings strings)
{
_filterAttribute = filterAttribute;
_strings = strings;
}
public override async Task<PreconditionResult> CheckPermissionsAsync(
ICommandContext context,
CommandInfo command,
IServiceProvider services)
{
var medusaContext = ContextAdapterFactory.CreateNew(context,
_strings,
services);
var result = await _filterAttribute.CheckAsync(medusaContext);
if (!result)
return PreconditionResult.FromError($"Precondition '{_filterAttribute.GetType().Name}' failed.");
return PreconditionResult.FromSuccess();
}
}

View File

@@ -11,6 +11,7 @@ public sealed class GuildContextAdapter : GuildContext
public override IMedusaStrings Strings { get; }
public override IGuild Guild { get; }
public override ITextChannel Channel { get; }
public override ISelfUser Bot { get; }
public override IUserMessage Message
=> _ctx.Message;
@@ -28,6 +29,7 @@ public sealed class GuildContextAdapter : GuildContext
Strings = strings;
User = (IGuildUser)ctx.User;
Bot = ctx.Client.CurrentUser;
_services = services;
_ebs = new(_services.GetRequiredService<IEmbedBuilderService>());

View File

@@ -22,8 +22,6 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
public void AddLoadedMedusa(string name)
{
name = name.Trim().ToLowerInvariant();
ModifyConfig(conf =>
{
if (conf.Loaded is null)
@@ -36,8 +34,6 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
public void RemoveLoadedMedusa(string name)
{
name = name.Trim().ToLowerInvariant();
ModifyConfig(conf =>
{
if (conf.Loaded is null)

View File

@@ -1,5 +1,6 @@
using Discord.Commands.Builders;
using Microsoft.Extensions.DependencyInjection;
using Nadeko.Medusa.Adapters;
using NadekoBot.Common.ModuleBehaviors;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
@@ -186,7 +187,6 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
return MedusaLoadResult.AlreadyLoaded;
var safeName = Uri.EscapeDataString(name);
name = name.ToLowerInvariant();
await _lock.WaitAsync();
try
@@ -383,6 +383,11 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
{
var m = mb.WithName(snekInfo.Name);
foreach (var f in snekInfo.Filters)
{
m.AddPrecondition(new FilterAdapter(f, strings));
}
foreach (var cmd in snekInfo.Commands)
{
m.AddCommand(cmd.Aliases.First(),
@@ -391,7 +396,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
new(cmd),
new(medusaServices),
strings),
CreateCommandFactory(medusaName, cmd));
CreateCommandFactory(medusaName, cmd, strings));
}
foreach (var subInfo in snekInfo.Subsneks)
@@ -400,7 +405,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
private static readonly RequireContextAttribute _reqGuild = new RequireContextAttribute(ContextType.Guild);
private static readonly RequireContextAttribute _reqDm = new RequireContextAttribute(ContextType.DM);
private Action<CommandBuilder> CreateCommandFactory(string medusaName, SnekCommandData cmd)
private Action<CommandBuilder> CreateCommandFactory(string medusaName, SnekCommandData cmd, IMedusaStrings strings)
=> (cb) =>
{
cb.AddAliases(cmd.Aliases.Skip(1).ToArray());
@@ -409,6 +414,31 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
cb.AddPrecondition(_reqGuild);
else if (cmd.ContextType == CommandContextType.Dm)
cb.AddPrecondition(_reqDm);
foreach (var f in cmd.Filters)
cb.AddPrecondition(new FilterAdapter(f, strings));
foreach (var ubp in cmd.UserAndBotPerms)
{
if (ubp is user_permAttribute up)
{
if (up.GuildPerm is { } gp)
cb.AddPrecondition(new UserPermAttribute(gp));
else if (up.ChannelPerm is { } cp)
cb.AddPrecondition(new UserPermAttribute(cp));
}
else if (ubp is bot_permAttribute bp)
{
if (bp.GuildPerm is { } gp)
cb.AddPrecondition(new BotPermAttribute(gp));
else if (bp.ChannelPerm is { } cp)
cb.AddPrecondition(new BotPermAttribute(cp));
}
else if (ubp is bot_owner_onlyAttribute)
{
cb.AddPrecondition(new OwnerOnlyAttribute());
}
}
cb.WithPriority(cmd.Priority);
@@ -429,6 +459,9 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
pb.WithIsMultiple(paramData.IsParams)
.WithIsOptional(paramData.IsOptional)
.WithIsRemainder(paramData.IsLeftover);
if (paramData.IsOptional)
pb.WithDefault(paramData.DefaultValue);
};
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -525,7 +558,6 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
[MethodImpl(MethodImplOptions.NoInlining)]
private async Task<MedusaUnloadResult> InternalUnloadAsync(string name)
{
name = name.ToLowerInvariant();
if (!_resolved.Remove(name, out var lsi))
return MedusaUnloadResult.NotLoaded;
@@ -752,8 +784,10 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
var cmds = new List<SnekCommandData>();
foreach (var method in methodInfos)
{
var filters = method.GetCustomAttributes<FilterAttribute>().ToArray();
var prio = method.GetCustomAttribute<prioAttribute>()?.Priority ?? 0;
var filters = method.GetCustomAttributes<FilterAttribute>(true).ToArray();
var userAndBotPerms = method.GetCustomAttributes<MedusaPermAttribute>(true)
.ToArray();
var prio = method.GetCustomAttribute<prioAttribute>(true)?.Priority ?? 0;
var paramInfos = method.GetParameters();
var cmdParams = new List<ParamData>();
@@ -769,6 +803,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
var leftoverAttribute = pi.GetCustomAttribute<leftoverAttribute>(true);
var hasDefaultValue = pi.HasDefaultValue;
var defaultValue = pi.DefaultValue;
var isLeftover = leftoverAttribute != null;
var isParams = pi.GetCustomAttribute<ParamArrayAttribute>() is not null;
var paramType = pi.ParameterType;
@@ -805,7 +840,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
continue;
}
canInject = false;
canInject = false;
if (isParams)
{
@@ -826,11 +861,11 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
throw new ArgumentException("Leftover attribute error.");
}
cmdParams.Add(new ParamData(paramType, paramName, hasDefaultValue, isLeftover, isParams));
cmdParams.Add(new ParamData(paramType, paramName, hasDefaultValue, defaultValue, isLeftover, isParams));
}
var cmdAttribute = method.GetCustomAttribute<cmdAttribute>()!;
var cmdAttribute = method.GetCustomAttribute<cmdAttribute>(true)!;
var aliases = cmdAttribute.Aliases;
if (aliases.Length == 0)
aliases = new[] { method.Name.ToLowerInvariant() };
@@ -840,6 +875,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
method,
instance,
filters,
userAndBotPerms,
cmdContext,
diParams,
cmdParams,

View File

@@ -4,6 +4,7 @@ public sealed record ParamData(
Type Type,
string Name,
bool IsOptional,
object? DefaultValue,
bool IsLeftover,
bool IsParams
);

View File

@@ -11,6 +11,7 @@ public sealed class SnekCommandData
MethodInfo methodInfo,
Snek module,
FilterAttribute[] filters,
MedusaPermAttribute[] userAndBotPerms,
CommandContextType contextType,
IReadOnlyList<Type> injectedParams,
IReadOnlyList<ParamData> parameters,
@@ -21,6 +22,7 @@ public sealed class SnekCommandData
MethodInfo = methodInfo;
Module = module;
Filters = filters;
UserAndBotPerms = userAndBotPerms;
ContextType = contextType;
InjectedParams = injectedParams;
Parameters = parameters;
@@ -28,6 +30,8 @@ public sealed class SnekCommandData
OptionalStrings = strings;
}
public MedusaPermAttribute[] UserAndBotPerms { get; set; }
public CommandStrings OptionalStrings { get; set; }
public IReadOnlyCollection<string> Aliases { get; }

View File

@@ -1,15 +1,14 @@
#nullable disable
using CommandLine;
using CommandLine;
namespace NadekoBot.Common;
public static class OptionsParser
{
public static T ParseFrom<T>(string[] args)
public static T ParseFrom<T>(string[]? args)
where T : INadekoCommandOptions, new()
=> ParseFrom(new T(), args).Item1;
public static (T, bool) ParseFrom<T>(T options, string[] args)
public static (T, bool) ParseFrom<T>(T options, string[]? args)
where T : INadekoCommandOptions
{
using var p = new Parser(x =>

View File

@@ -1,5 +1,6 @@
#nullable disable warnings
using SixLabors.ImageSharp.PixelFormats;
using System.Text.Json.Serialization;
namespace NadekoBot;
@@ -7,14 +8,14 @@ public sealed record SmartEmbedArrayElementText : SmartEmbedTextBase
{
public string Color { get; init; } = string.Empty;
public SmartEmbedArrayElementText() : base()
public SmartEmbedArrayElementText()
{
}
public SmartEmbedArrayElementText(IEmbed eb) : base(eb)
{
Color = eb.Color is { } c ? new Rgba32(c.R, c.G, c.B).ToHex() : string.Empty;
}
protected override EmbedBuilder GetEmbedInternal()
@@ -63,6 +64,7 @@ public abstract record SmartEmbedTextBase : SmartText
public SmartTextEmbedFooter Footer { get; init; }
public SmartTextEmbedField[] Fields { get; init; }
[JsonIgnore]
public bool IsValid
=> !string.IsNullOrWhiteSpace(Title)
|| !string.IsNullOrWhiteSpace(Description)
@@ -100,7 +102,7 @@ public abstract record SmartEmbedTextBase : SmartText
IconUrl = ef.IconUrl
}
: null;
if (eb.Fields.Length > 0)
{
Fields = eb.Fields.Select(field

View File

@@ -1,4 +1,6 @@
#nullable disable
using System.Text.Json.Serialization;
namespace NadekoBot;
public sealed record SmartEmbedTextArray : SmartText
@@ -6,6 +8,7 @@ public sealed record SmartEmbedTextArray : SmartText
public string Content { get; set; }
public SmartEmbedArrayElementText[] Embeds { get; set; }
[JsonIgnore]
public bool IsValid
=> Embeds?.All(x => x.IsValid) ?? false;

View File

@@ -1,16 +1,20 @@
#nullable disable
using Newtonsoft.Json.Linq;
using System.Text.Json.Serialization;
namespace NadekoBot;
public abstract record SmartText
{
[JsonIgnore]
public bool IsEmbed
=> this is SmartEmbedText;
[JsonIgnore]
public bool IsPlainText
=> this is SmartPlainText;
[JsonIgnore]
public bool IsEmbedArray
=> this is SmartEmbedTextArray;

View File

@@ -1,5 +1,6 @@
#nullable disable
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace NadekoBot;
@@ -8,6 +9,7 @@ public class SmartTextEmbedAuthor
public string Name { get; set; }
[JsonProperty("icon_url")]
[JsonPropertyName("icon_url")]
public string IconUrl { get; set; }
public string Url { get; set; }

View File

@@ -1,5 +1,6 @@
#nullable disable
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace NadekoBot;
@@ -8,5 +9,6 @@ public class SmartTextEmbedFooter
public string Text { get; set; }
[JsonProperty("icon_url")]
[JsonPropertyName("icon_url")]
public string IconUrl { get; set; }
}

View File

@@ -46,9 +46,7 @@ public sealed class CommandOrExprTypeReader : NadekoTypeReader<CommandOrExprInfo
public override async ValueTask<TypeReaderResult<CommandOrExprInfo>> ReadAsync(ICommandContext ctx, string input)
{
input = input.ToUpperInvariant();
if (_exprs.ExpressionExists(ctx.Guild?.Id, input) || _exprs.ExpressionExists(null, input))
if (_exprs.ExpressionExists(ctx.Guild?.Id, input))
return TypeReaderResult.FromSuccess(new CommandOrExprInfo(input, CommandOrExprInfo.Type.Custom));
var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(ctx, input);

View File

@@ -4,11 +4,60 @@ using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
using NadekoBot.Services.Database;
using System.Collections.Immutable;
namespace NadekoBot.Db;
public static class DiscordUserExtensions
{
/// <summary>
/// Adds the specified <paramref name="users"/> to the database. If a database user with placeholder name
/// and discriminator is present in <paramref name="users"/>, their name and discriminator get updated accordingly.
/// </summary>
/// <param name="ctx">This database context.</param>
/// <param name="users">The users to add or update in the database.</param>
/// <returns>A tuple with the amount of new users added and old users updated.</returns>
public static async Task<(long UsersAdded, long UsersUpdated)> RefreshUsersAsync(this NadekoContext ctx, List<IUser> users)
{
var presentDbUsers = await ctx.DiscordUser
.Select(x => new { x.UserId, x.Username, x.Discriminator })
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
.ToArrayAsyncEF();
var usersToAdd = users
.Where(x => !presentDbUsers.Select(x => x.UserId).Contains(x.Id))
.Select(x => new DiscordUser()
{
UserId = x.Id,
AvatarId = x.AvatarId,
Username = x.Username,
Discriminator = x.Discriminator
});
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
var toUpdateUserIds = presentDbUsers
.Where(x => x.Username == "Unknown" && x.Discriminator == "????")
.Select(x => x.UserId)
.ToArray();
foreach (var user in users.Where(x => toUpdateUserIds.Contains(x.Id)))
{
await ctx.DiscordUser
.Where(x => x.UserId == user.Id)
.UpdateAsync(x => new DiscordUser()
{
Username = user.Username,
Discriminator = user.Discriminator,
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
AvatarId = user.AvatarId,
DateAdded = x.DateAdded ?? DateTime.UtcNow
});
}
return (added, toUpdateUserIds.Length);
}
public static Task<DiscordUser> GetByUserIdAsync(
this IQueryable<DiscordUser> set,
ulong userId)

View File

@@ -12,10 +12,4 @@ public static class NadekoExpressionExtensions
public static IEnumerable<NadekoExpression> ForId(this DbSet<NadekoExpression> exprs, ulong id)
=> exprs.AsNoTracking().AsQueryable().Where(x => x.GuildId == id).ToList();
public static NadekoExpression GetByGuildIdAndInput(
this DbSet<NadekoExpression> exprs,
ulong? guildId,
string input)
=> exprs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);
}

View File

@@ -10,6 +10,7 @@ namespace NadekoBot.Db;
public class WaifuInfoStats
{
public int WaifuId { get; init; }
public string FullName { get; init; }
public long Price { get; init; }
public string ClaimerName { get; init; }
@@ -17,9 +18,6 @@ public class WaifuInfoStats
public int AffinityCount { get; init; }
public int DivorceCount { get; init; }
public int ClaimCount { get; init; }
public List<WaifuItem> Items { get; init; }
public List<string> Claims { get; init; }
public List<string> Fans { get; init; }
}
public static class WaifuExtensions
@@ -103,6 +101,7 @@ public static class WaifuExtensions
.FirstOrDefault())
.Select(w => new WaifuInfoStats
{
WaifuId = w.WaifuId,
FullName =
ctx.Set<DiscordUser>()
.AsQueryable()
@@ -135,17 +134,6 @@ public static class WaifuExtensions
&& x.NewId == null
&& x.UpdateType == WaifuUpdateType.Claimed),
Price = w.Price,
Claims = ctx.WaifuInfo.AsQueryable()
.Include(x => x.Waifu)
.Where(x => x.ClaimerId == w.WaifuId)
.Select(x => x.Waifu.Username + "#" + x.Waifu.Discriminator)
.ToList(),
Fans = ctx.WaifuInfo.AsQueryable()
.Include(x => x.Waifu)
.Where(x => x.AffinityId == w.WaifuId)
.Select(x => x.Waifu.Username + "#" + x.Waifu.Discriminator)
.ToList(),
Items = w.Items
})
.FirstOrDefault();

View File

@@ -28,5 +28,10 @@ public class DiscordUser : DbEntity
=> UserId.GetHashCode();
public override string ToString()
=> Username + "#" + Discriminator;
{
if (string.IsNullOrWhiteSpace(Discriminator) || Discriminator == "0000")
return Username;
return Username + "#" + Discriminator;
}
}

View File

@@ -8,6 +8,8 @@ public class FeedSub : DbEntity
public ulong ChannelId { get; set; }
public string Url { get; set; }
public string Message { get; set; }
public override int GetHashCode()
=> Url.GetHashCode(StringComparison.InvariantCulture) ^ GuildConfigId.GetHashCode();

View File

@@ -95,6 +95,8 @@ public class GuildConfig : DbEntity
public int WarnExpireHours { get; set; }
public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear;
public bool DisableGlobalExpressions { get; set; } = false;
#region Boost Message
public bool SendBoostMessage { get; set; }

View File

@@ -19,6 +19,10 @@ public class LogSetting : DbEntity
public ulong? ChannelCreatedId { get; set; }
public ulong? ChannelDestroyedId { get; set; }
public ulong? ChannelUpdatedId { get; set; }
public ulong? ThreadDeletedId { get; set; }
public ulong? ThreadCreatedId { get; set; }
public ulong? UserMutedId { get; set; }

View File

@@ -7,7 +7,7 @@ public class WaifuInfo : DbEntity
{
public int WaifuId { get; set; }
public DiscordUser Waifu { get; set; }
public int? ClaimerId { get; set; }
public DiscordUser Claimer { get; set; }
@@ -19,25 +19,25 @@ public class WaifuInfo : DbEntity
public override string ToString()
{
var claimer = "no one";
var status = string.Empty;
var waifuUsername = Waifu.Username.TrimTo(20);
var claimerUsername = Claimer?.Username.TrimTo(20);
var waifuUsername = Waifu.ToString().TrimTo(20);
var claimer = Claimer?.ToString().TrimTo(20)
?? "no one";
var affinity = Affinity?.ToString().TrimTo(20);
if (ClaimerId is not null)
claimer = $"{claimerUsername}#{Claimer.Discriminator}";
if (AffinityId is null)
status = $"... but {waifuUsername}'s heart is empty";
else if (AffinityId == ClaimerId)
status = $"... and {waifuUsername} likes {claimerUsername} too <3";
status = $"... and {waifuUsername} likes {claimer} too <3";
else
{
status =
$"... but {waifuUsername}'s heart belongs to {Affinity.Username.TrimTo(20)}#{Affinity.Discriminator}";
$"... but {waifuUsername}'s heart belongs to {affinity}";
}
return $"**{waifuUsername}#{Waifu.Discriminator}** - claimed by **{claimer}**\n\t{status}";
return $"**{waifuUsername}** - claimed by **{claimer}**\n\t{status}";
}
}

View File

@@ -7,4 +7,4 @@ public class WaifuItem : DbEntity
public int? WaifuInfoId { get; set; }
public string ItemEmoji { get; set; }
public string Name { get; set; }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class toggleglobalexpressions : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "disableglobalexpressions",
table: "guildconfigs",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "disableglobalexpressions",
table: "guildconfigs");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class logthread : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<ulong>(
name: "threadcreatedid",
table: "logsettings",
type: "bigint unsigned",
nullable: true);
migrationBuilder.AddColumn<ulong>(
name: "threaddeletedid",
table: "logsettings",
type: "bigint unsigned",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "threadcreatedid",
table: "logsettings");
migrationBuilder.DropColumn(
name: "threaddeletedid",
table: "logsettings");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class feedtext : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "message",
table: "feedsub",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "message",
table: "feedsub");
}
}
}

View File

@@ -975,6 +975,10 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("int")
.HasColumnName("guildconfigid");
b.Property<string>("Message")
.HasColumnType("longtext")
.HasColumnName("message");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("varchar(255)")
@@ -1258,6 +1262,10 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("tinyint(1)")
.HasColumnName("deletestreamonlinemessage");
b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("tinyint(1)")
.HasColumnName("disableglobalexpressions");
b.Property<string>("DmGreetMessageText")
.HasColumnType("longtext")
.HasColumnName("dmgreetmessagetext");
@@ -1514,6 +1522,14 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("bigint unsigned")
.HasColumnName("messageupdatedid");
b.Property<ulong?>("ThreadCreatedId")
.HasColumnType("bigint unsigned")
.HasColumnName("threadcreatedid");
b.Property<ulong?>("ThreadDeletedId")
.HasColumnType("bigint unsigned")
.HasColumnName("threaddeletedid");
b.Property<ulong?>("UserBannedId")
.HasColumnType("bigint unsigned")
.HasColumnName("userbannedid");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class toggleglobalexpressions : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "disableglobalexpressions",
table: "guildconfigs",
type: "boolean",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "disableglobalexpressions",
table: "guildconfigs");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class logthread : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "threadcreatedid",
table: "logsettings",
type: "numeric(20,0)",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "threaddeletedid",
table: "logsettings",
type: "numeric(20,0)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "threadcreatedid",
table: "logsettings");
migrationBuilder.DropColumn(
name: "threaddeletedid",
table: "logsettings");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class feedtext : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "message",
table: "feedsub",
type: "text",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "message",
table: "feedsub");
}
}
}

View File

@@ -1023,6 +1023,10 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("integer")
.HasColumnName("guildconfigid");
b.Property<string>("Message")
.HasColumnType("text")
.HasColumnName("message");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text")
@@ -1322,6 +1326,10 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("boolean")
.HasColumnName("deletestreamonlinemessage");
b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("boolean")
.HasColumnName("disableglobalexpressions");
b.Property<string>("DmGreetMessageText")
.HasColumnType("text")
.HasColumnName("dmgreetmessagetext");
@@ -1586,6 +1594,14 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("numeric(20,0)")
.HasColumnName("messageupdatedid");
b.Property<decimal?>("ThreadCreatedId")
.HasColumnType("numeric(20,0)")
.HasColumnName("threadcreatedid");
b.Property<decimal?>("ThreadDeletedId")
.HasColumnType("numeric(20,0)")
.HasColumnName("threaddeletedid");
b.Property<decimal?>("UserBannedId")
.HasColumnType("numeric(20,0)")
.HasColumnName("userbannedid");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class toggleglobalexpressions : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "DisableGlobalExpressions",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DisableGlobalExpressions",
table: "GuildConfigs");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class logthread : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<ulong>(
name: "ThreadCreatedId",
table: "LogSettings",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<ulong>(
name: "ThreadDeletedId",
table: "LogSettings",
type: "INTEGER",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ThreadCreatedId",
table: "LogSettings");
migrationBuilder.DropColumn(
name: "ThreadDeletedId",
table: "LogSettings");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class feedtext : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Message",
table: "FeedSub",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Message",
table: "FeedSub");
}
}
}

View File

@@ -37,7 +37,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("AutoPublishChannel");
b.ToTable("AutoPublishChannel", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
@@ -60,7 +60,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId")
.IsUnique();
b.ToTable("BankUsers");
b.ToTable("BankUsers", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
@@ -75,7 +75,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("ClubApplicants");
b.ToTable("ClubApplicants", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubBans", b =>
@@ -90,7 +90,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("ClubBans");
b.ToTable("ClubBans", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b =>
@@ -126,7 +126,7 @@ namespace NadekoBot.Migrations
b.HasIndex("OwnerId")
.IsUnique();
b.ToTable("Clubs");
b.ToTable("Clubs", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.DiscordUser", b =>
@@ -185,7 +185,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("DiscordUser");
b.ToTable("DiscordUser", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
@@ -219,7 +219,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FollowedStream");
b.ToTable("FollowedStream", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.PatronQuota", b =>
@@ -246,7 +246,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("PatronQuotas");
b.ToTable("PatronQuotas", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b =>
@@ -272,7 +272,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UniquePlatformUserId")
.IsUnique();
b.ToTable("Patrons");
b.ToTable("Patrons", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.StreamOnlineMessage", b =>
@@ -298,7 +298,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("StreamOnlineMessages");
b.ToTable("StreamOnlineMessages", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.XpShopOwnedItem", b =>
@@ -328,7 +328,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId", "ItemType", "ItemKey")
.IsUnique();
b.ToTable("XpShopOwnedItem");
b.ToTable("XpShopOwnedItem", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
@@ -357,7 +357,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiAltSetting");
b.ToTable("AntiAltSetting", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
@@ -389,7 +389,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiRaidSetting");
b.ToTable("AntiRaidSetting", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b =>
@@ -411,7 +411,7 @@ namespace NadekoBot.Migrations
b.HasIndex("AntiSpamSettingId");
b.ToTable("AntiSpamIgnore");
b.ToTable("AntiSpamIgnore", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b =>
@@ -443,7 +443,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiSpamSetting");
b.ToTable("AntiSpamSetting", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoCommand", b =>
@@ -481,7 +481,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("AutoCommands");
b.ToTable("AutoCommands", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b =>
@@ -509,7 +509,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId");
b.ToTable("AutoTranslateChannels");
b.ToTable("AutoTranslateChannels", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b =>
@@ -537,7 +537,7 @@ namespace NadekoBot.Migrations
b.HasAlternateKey("ChannelId", "UserId");
b.ToTable("AutoTranslateUsers");
b.ToTable("AutoTranslateUsers", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BanTemplate", b =>
@@ -563,7 +563,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("BanTemplates");
b.ToTable("BanTemplates", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistEntry", b =>
@@ -583,7 +583,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("Blacklist");
b.ToTable("Blacklist", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
@@ -608,7 +608,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("CommandAlias");
b.ToTable("CommandAlias", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b =>
@@ -633,7 +633,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("CommandCooldown");
b.ToTable("CommandCooldown", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b =>
@@ -671,7 +671,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("CurrencyTransactions");
b.ToTable("CurrencyTransactions", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.DelMsgOnCmdChannel", b =>
@@ -696,7 +696,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("DelMsgOnCmdChannel");
b.ToTable("DelMsgOnCmdChannel", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordPermOverride", b =>
@@ -722,7 +722,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId", "Command")
.IsUnique();
b.ToTable("DiscordPermOverrides");
b.ToTable("DiscordPermOverrides", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b =>
@@ -747,7 +747,7 @@ namespace NadekoBot.Migrations
b.HasIndex("XpSettingsId");
b.ToTable("ExcludedItem");
b.ToTable("ExcludedItem", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b =>
@@ -765,6 +765,9 @@ namespace NadekoBot.Migrations
b.Property<int>("GuildConfigId")
.HasColumnType("INTEGER");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("TEXT");
@@ -773,7 +776,7 @@ namespace NadekoBot.Migrations
b.HasAlternateKey("GuildConfigId", "Url");
b.ToTable("FeedSub");
b.ToTable("FeedSub", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
@@ -795,7 +798,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilterChannelId");
b.ToTable("FilterChannelId", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b =>
@@ -817,7 +820,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilteredWord");
b.ToTable("FilteredWord", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b =>
@@ -839,7 +842,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilterLinksChannelId");
b.ToTable("FilterLinksChannelId", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterWordsChannelId", b =>
@@ -861,7 +864,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilterWordsChannelId");
b.ToTable("FilterWordsChannelId", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
@@ -887,7 +890,7 @@ namespace NadekoBot.Migrations
b.HasIndex("Feature")
.IsUnique();
b.ToTable("GamblingStats");
b.ToTable("GamblingStats", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
@@ -909,7 +912,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("GCChannelId");
b.ToTable("GCChannelId", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GroupName", b =>
@@ -935,7 +938,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId", "Number")
.IsUnique();
b.ToTable("GroupName");
b.ToTable("GroupName", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
@@ -986,6 +989,9 @@ namespace NadekoBot.Migrations
b.Property<bool>("DeleteStreamOnlineMessage")
.HasColumnType("INTEGER");
b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("INTEGER");
b.Property<string>("DmGreetMessageText")
.HasColumnType("TEXT");
@@ -1064,7 +1070,7 @@ namespace NadekoBot.Migrations
b.HasIndex("WarnExpireHours");
b.ToTable("GuildConfigs");
b.ToTable("GuildConfigs", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b =>
@@ -1090,7 +1096,7 @@ namespace NadekoBot.Migrations
b.HasIndex("LogSettingId", "LogItemId", "ItemType")
.IsUnique();
b.ToTable("IgnoredLogChannels");
b.ToTable("IgnoredLogChannels", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
@@ -1112,7 +1118,7 @@ namespace NadekoBot.Migrations
b.HasIndex("LogSettingId");
b.ToTable("IgnoredVoicePresenceCHannels");
b.ToTable("IgnoredVoicePresenceCHannels", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ImageOnlyChannel", b =>
@@ -1138,7 +1144,7 @@ namespace NadekoBot.Migrations
b.HasIndex("ChannelId")
.IsUnique();
b.ToTable("ImageOnlyChannels");
b.ToTable("ImageOnlyChannels", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
@@ -1183,6 +1189,12 @@ namespace NadekoBot.Migrations
b.Property<ulong?>("MessageUpdatedId")
.HasColumnType("INTEGER");
b.Property<ulong?>("ThreadCreatedId")
.HasColumnType("INTEGER");
b.Property<ulong?>("ThreadDeletedId")
.HasColumnType("INTEGER");
b.Property<ulong?>("UserBannedId")
.HasColumnType("INTEGER");
@@ -1206,7 +1218,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("LogSettings");
b.ToTable("LogSettings", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlayerSettings", b =>
@@ -1243,7 +1255,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("MusicPlayerSettings");
b.ToTable("MusicPlayerSettings", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b =>
@@ -1266,7 +1278,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("MusicPlaylists");
b.ToTable("MusicPlaylists", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b =>
@@ -1288,7 +1300,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("MutedUserId");
b.ToTable("MutedUserId", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.NadekoExpression", b =>
@@ -1326,7 +1338,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("Expressions");
b.ToTable("Expressions", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklistedTag", b =>
@@ -1348,7 +1360,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId");
b.ToTable("NsfwBlacklistedTags");
b.ToTable("NsfwBlacklistedTags", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b =>
@@ -1388,7 +1400,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("Permissions");
b.ToTable("Permissions", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlantedCurrency", b =>
@@ -1425,7 +1437,7 @@ namespace NadekoBot.Migrations
b.HasIndex("MessageId")
.IsUnique();
b.ToTable("PlantedCurrency");
b.ToTable("PlantedCurrency", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b =>
@@ -1459,7 +1471,7 @@ namespace NadekoBot.Migrations
b.HasIndex("MusicPlaylistId");
b.ToTable("PlaylistSong");
b.ToTable("PlaylistSong", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Poll", b =>
@@ -1485,7 +1497,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("Poll");
b.ToTable("Poll", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PollAnswer", b =>
@@ -1510,7 +1522,7 @@ namespace NadekoBot.Migrations
b.HasIndex("PollId");
b.ToTable("PollAnswer");
b.ToTable("PollAnswer", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PollVote", b =>
@@ -1535,7 +1547,7 @@ namespace NadekoBot.Migrations
b.HasIndex("PollId");
b.ToTable("PollVote");
b.ToTable("PollVote", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b =>
@@ -1571,7 +1583,7 @@ namespace NadekoBot.Migrations
b.HasIndex("Keyword");
b.ToTable("Quotes");
b.ToTable("Quotes", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleV2", b =>
@@ -1612,7 +1624,7 @@ namespace NadekoBot.Migrations
b.HasIndex("MessageId", "Emote")
.IsUnique();
b.ToTable("ReactionRoles");
b.ToTable("ReactionRoles", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
@@ -1646,7 +1658,7 @@ namespace NadekoBot.Migrations
b.HasIndex("When");
b.ToTable("Reminders");
b.ToTable("Reminders", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b =>
@@ -1681,7 +1693,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("Repeaters");
b.ToTable("Repeaters", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b =>
@@ -1710,7 +1722,7 @@ namespace NadekoBot.Migrations
b.HasIndex("PlatformUserId")
.IsUnique();
b.ToTable("RewardedUsers");
b.ToTable("RewardedUsers", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RotatingPlayingStatus", b =>
@@ -1730,7 +1742,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("RotatingStatus");
b.ToTable("RotatingStatus", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b =>
@@ -1761,7 +1773,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId", "RoleId")
.IsUnique();
b.ToTable("SelfAssignableRoles");
b.ToTable("SelfAssignableRoles", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
@@ -1804,7 +1816,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("ShopEntry");
b.ToTable("ShopEntry", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b =>
@@ -1826,7 +1838,7 @@ namespace NadekoBot.Migrations
b.HasIndex("ShopEntryId");
b.ToTable("ShopEntryItem");
b.ToTable("ShopEntryItem", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b =>
@@ -1848,7 +1860,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("SlowmodeIgnoredRole");
b.ToTable("SlowmodeIgnoredRole", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b =>
@@ -1870,7 +1882,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("SlowmodeIgnoredUser");
b.ToTable("SlowmodeIgnoredUser", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b =>
@@ -1895,7 +1907,7 @@ namespace NadekoBot.Migrations
b.HasIndex("StreamRoleSettingsId");
b.ToTable("StreamRoleBlacklistedUser");
b.ToTable("StreamRoleBlacklistedUser", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b =>
@@ -1927,7 +1939,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("StreamRoleSettings");
b.ToTable("StreamRoleSettings", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b =>
@@ -1952,7 +1964,7 @@ namespace NadekoBot.Migrations
b.HasIndex("StreamRoleSettingsId");
b.ToTable("StreamRoleWhitelistedUser");
b.ToTable("StreamRoleWhitelistedUser", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnbanTimer", b =>
@@ -1977,7 +1989,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("UnbanTimer");
b.ToTable("UnbanTimer", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b =>
@@ -2002,7 +2014,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("UnmuteTimer");
b.ToTable("UnmuteTimer", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnroleTimer", b =>
@@ -2030,7 +2042,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("UnroleTimer");
b.ToTable("UnroleTimer", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b =>
@@ -2070,7 +2082,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId", "GuildId")
.IsUnique();
b.ToTable("UserXpStats");
b.ToTable("UserXpStats", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b =>
@@ -2095,7 +2107,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("VcRoleInfo");
b.ToTable("VcRoleInfo", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b =>
@@ -2130,7 +2142,7 @@ namespace NadekoBot.Migrations
b.HasIndex("WaifuId")
.IsUnique();
b.ToTable("WaifuInfo");
b.ToTable("WaifuInfo", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b =>
@@ -2155,7 +2167,7 @@ namespace NadekoBot.Migrations
b.HasIndex("WaifuInfoId");
b.ToTable("WaifuItem");
b.ToTable("WaifuItem", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b =>
@@ -2187,7 +2199,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("WaifuUpdates");
b.ToTable("WaifuUpdates", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b =>
@@ -2230,7 +2242,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("Warnings");
b.ToTable("Warnings", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b =>
@@ -2261,7 +2273,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("WarningPunishment");
b.ToTable("WarningPunishment", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpCurrencyReward", b =>
@@ -2286,7 +2298,7 @@ namespace NadekoBot.Migrations
b.HasIndex("XpSettingsId");
b.ToTable("XpCurrencyReward");
b.ToTable("XpCurrencyReward", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b =>
@@ -2315,7 +2327,7 @@ namespace NadekoBot.Migrations
b.HasIndex("XpSettingsId", "Level")
.IsUnique();
b.ToTable("XpRoleReward");
b.ToTable("XpRoleReward", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b =>
@@ -2338,7 +2350,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("XpSettings");
b.ToTable("XpSettings", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>

View File

@@ -78,6 +78,7 @@ public class DangerousCommandsService : INService
await ctx.CurrencyTransactions.DeleteAsync();
await ctx.PlantedCurrency.DeleteAsync();
await ctx.BankUsers.DeleteAsync();
await ctx.SaveChangesAsync();
}

View File

@@ -191,7 +191,7 @@ public class GreetService : INService, IReadyExecutor
if (conf.AutoDeleteByeMessagesTimer > 0)
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
}
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions)
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions || ex.DiscordCode == DiscordErrorCode.UnknownChannel)
{
Log.Warning(ex, "Missing permissions to send a bye message, the bye message will be disabled on server: {GuildId}", channel.GuildId);
await SetBye(channel.GuildId, channel.Id, false);
@@ -224,7 +224,7 @@ public class GreetService : INService, IReadyExecutor
if (conf.AutoDeleteGreetMessagesTimer > 0)
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
}
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions)
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions || ex.DiscordCode == DiscordErrorCode.UnknownChannel)
{
Log.Warning(ex, "Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}", channel.GuildId);
await SetGreet(channel.GuildId, channel.Id, false);

View File

@@ -65,7 +65,10 @@ public partial class Administration
_localization.SetGuildCulture(ctx.Guild, ci);
}
await ReplyConfirmLocalizedAsync(strs.lang_set(Format.Bold(ci.ToString()), Format.Bold(ci.NativeName)));
var nativeName = ci.NativeName;
if (ci.Name == "ts-TS")
nativeName = _supportedLocales[ci.Name];
await ReplyConfirmLocalizedAsync(strs.lang_set(Format.Bold(ci.ToString()), Format.Bold(nativeName)));
}
catch (Exception)
{

View File

@@ -19,6 +19,7 @@ public class MuteService : INService
private static readonly OverwritePermissions _denyOverwrite = new(addReactions: PermValue.Deny,
sendMessages: PermValue.Deny,
sendMessagesInThreads: PermValue.Deny,
attachFiles: PermValue.Deny);
public event Action<IGuildUser, IUser, MuteType, string> UserMuted = delegate { };
@@ -356,24 +357,24 @@ public class MuteService : INService
public async Task TimedBan(
IGuild guild,
IUser user,
ulong userId,
TimeSpan after,
string reason,
int pruneDays)
{
await guild.AddBanAsync(user.Id, pruneDays, reason);
await guild.AddBanAsync(userId, pruneDays, reason);
await using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.UnbanTimer));
config.UnbanTimer.Add(new()
{
UserId = user.Id,
UserId = userId,
UnbanAt = DateTime.UtcNow + after
}); // add teh unmute timer to the database
uow.SaveChanges();
await uow.SaveChangesAsync();
}
StartUn_Timer(guild.Id, user.Id, after, TimerType.Ban); // start the timer
StartUn_Timer(guild.Id, userId, after, TimerType.Ban); // start the timer
}
public async Task TimedRole(

View File

@@ -457,6 +457,7 @@ public class ProtectionService : INService
case PunishmentAction.ChatMute:
case PunishmentAction.VoiceMute:
case PunishmentAction.AddRole:
case PunishmentAction.TimeOut:
return true;
default:
return false;

View File

@@ -72,7 +72,7 @@ public partial class Administration
[BotPerm(ChannelPerm.ManageMessages)]
[NadekoOptions(typeof(PruneOptions))]
[Priority(0)]
public Task Prune(IGuildUser user, int count = 100, string args = null)
public Task Prune(IGuildUser user, int count = 100, params string[] args)
=> Prune(user.Id, count, args);
//prune userid [x]

View File

@@ -0,0 +1,153 @@
using MessageType = Discord.MessageType;
namespace NadekoBot.Modules.Administration;
public sealed class DoAsUserMessage : IUserMessage
{
private readonly string _message;
private readonly IUserMessage _msg;
private readonly IUser _user;
public DoAsUserMessage(SocketUserMessage msg, IUser user, string message)
{
_msg = msg;
_user = user;
_message = message;
}
public ulong Id => _msg.Id;
public DateTimeOffset CreatedAt => _msg.CreatedAt;
public Task DeleteAsync(RequestOptions? options = null)
{
return _msg.DeleteAsync(options);
}
public Task AddReactionAsync(IEmote emote, RequestOptions? options = null)
{
return _msg.AddReactionAsync(emote, options);
}
public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions? options = null)
{
return _msg.RemoveReactionAsync(emote, user, options);
}
public Task RemoveReactionAsync(IEmote emote, ulong userId, RequestOptions? options = null)
{
return _msg.RemoveReactionAsync(emote, userId, options);
}
public Task RemoveAllReactionsAsync(RequestOptions? options = null)
{
return _msg.RemoveAllReactionsAsync(options);
}
public Task RemoveAllReactionsForEmoteAsync(IEmote emote, RequestOptions? options = null)
{
return _msg.RemoveAllReactionsForEmoteAsync(emote, options);
}
public IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(
IEmote emoji,
int limit,
RequestOptions? options = null,
ReactionType type = ReactionType.Normal)
=> _msg.GetReactionUsersAsync(emoji, limit, options, type);
public IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emoji, int limit,
RequestOptions? options = null)
{
return _msg.GetReactionUsersAsync(emoji, limit, options);
}
public MessageType Type => _msg.Type;
public MessageSource Source => _msg.Source;
public bool IsTTS => _msg.IsTTS;
public bool IsPinned => _msg.IsPinned;
public bool IsSuppressed => _msg.IsSuppressed;
public bool MentionedEveryone => _msg.MentionedEveryone;
public string Content => _message;
public string CleanContent => _msg.CleanContent;
public DateTimeOffset Timestamp => _msg.Timestamp;
public DateTimeOffset? EditedTimestamp => _msg.EditedTimestamp;
public IMessageChannel Channel => _msg.Channel;
public IUser Author => _user;
public IThreadChannel Thread => _msg.Thread;
public IReadOnlyCollection<IAttachment> Attachments => _msg.Attachments;
public IReadOnlyCollection<IEmbed> Embeds => _msg.Embeds;
public IReadOnlyCollection<ITag> Tags => _msg.Tags;
public IReadOnlyCollection<ulong> MentionedChannelIds => _msg.MentionedChannelIds;
public IReadOnlyCollection<ulong> MentionedRoleIds => _msg.MentionedRoleIds;
public IReadOnlyCollection<ulong> MentionedUserIds => _msg.MentionedUserIds;
public MessageActivity Activity => _msg.Activity;
public MessageApplication Application => _msg.Application;
public MessageReference Reference => _msg.Reference;
public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions => _msg.Reactions;
public IReadOnlyCollection<IMessageComponent> Components => _msg.Components;
public IReadOnlyCollection<IStickerItem> Stickers => _msg.Stickers;
public MessageFlags? Flags => _msg.Flags;
[Obsolete("Obsolete in favor of InteractionMetadata")]
public IMessageInteraction Interaction => _msg.Interaction;
public MessageRoleSubscriptionData RoleSubscriptionData => _msg.RoleSubscriptionData;
public Task ModifyAsync(Action<MessageProperties> func, RequestOptions? options = null)
{
return _msg.ModifyAsync(func, options);
}
public Task PinAsync(RequestOptions? options = null)
{
return _msg.PinAsync(options);
}
public Task UnpinAsync(RequestOptions? options = null)
{
return _msg.UnpinAsync(options);
}
public Task CrosspostAsync(RequestOptions? options = null)
{
return _msg.CrosspostAsync(options);
}
public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
TagHandling roleHandling = TagHandling.Name,
TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
{
return _msg.Resolve(userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
}
public MessageResolvedData ResolvedData => _msg.ResolvedData;
public IUserMessage ReferencedMessage => _msg.ReferencedMessage;
public IMessageInteractionMetadata InteractionMetadata
=> _msg.InteractionMetadata;
}

View File

@@ -1,5 +1,6 @@
#nullable disable
using Nadeko.Medusa;
using NadekoBot.Db;
using NadekoBot.Modules.Administration.Services;
using NadekoBot.Services.Database.Models;
@@ -22,19 +23,73 @@ public partial class Administration
private readonly IBotStrings _strings;
private readonly IMedusaLoaderService _medusaLoader;
private readonly ICoordinator _coord;
private readonly DbService _db;
public SelfCommands(
DiscordSocketClient client,
DbService db,
IBotStrings strings,
ICoordinator coord,
IMedusaLoaderService medusaLoader)
{
_client = client;
_db = db;
_strings = strings;
_coord = coord;
_medusaLoader = medusaLoader;
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public Task CacheUsers()
=> CacheUsers(ctx.Guild);
[Cmd]
[OwnerOnly]
public async Task CacheUsers(IGuild guild)
{
var downloadUsersTask = guild.DownloadUsersAsync();
var message = await ReplyPendingLocalizedAsync(strs.cache_users_pending);
using var dbContext = _db.GetDbContext();
await downloadUsersTask;
var users = (await guild.GetUsersAsync(CacheMode.CacheOnly))
.Cast<IUser>()
.ToList();
var (added, updated) = await dbContext.RefreshUsersAsync(users);
await message.ModifyAsync(x =>
x.Embed = _eb.Create()
.WithDescription(GetText(strs.cache_users_done(added, updated)))
.WithOkColor()
.Build()
);
}
[Cmd]
[OwnerOnly]
public async Task DoAs(IUser user, [Leftover] string message)
{
if (ctx.User is not IGuildUser { GuildPermissions.Administrator: true })
return;
if (ctx.Guild is SocketGuild sg && ctx.Channel is ISocketMessageChannel ch
&& ctx.Message is SocketUserMessage msg)
{
var fakeMessage = new DoAsUserMessage(msg, user, message);
await _cmdHandler.TryRunCommand(sg, ch, fakeMessage);
}
else
{
await ReplyErrorLocalizedAsync(strs.error_occured);
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
@@ -230,6 +285,19 @@ public partial class Administration
await ReplyPendingLocalizedAsync(strs.fwall_stop);
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task ForwardToChannel()
{
var enabled = _service.ForwardToChannel(ctx.Channel.Id);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.fwch_start);
else
await ReplyPendingLocalizedAsync(strs.fwch_stop);
}
[Cmd]
public async Task ShardStats(int page = 1)
{
@@ -433,6 +501,16 @@ public partial class Administration
if (success)
await ReplyConfirmLocalizedAsync(strs.set_avatar);
}
[Cmd]
[OwnerOnly]
public async Task SetBanner([Leftover] string img = null)
{
var success = await _service.SetBanner(img);
if (success)
await ReplyConfirmLocalizedAsync(strs.set_banner);
}
[Cmd]
[OwnerOnly]

View File

@@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Services.Database.Models;
using System.Collections.Immutable;
using Nadeko.Common;
namespace NadekoBot.Modules.Administration.Services;
@@ -85,12 +84,12 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
await using var uow = _db.GetDbContext();
autoCommands = uow.AutoCommands.AsNoTracking()
.Where(x => x.Interval >= 5)
.AsEnumerable()
.GroupBy(x => x.GuildId)
.ToDictionary(x => x.Key,
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
.ToConcurrent();
.Where(x => x.Interval >= 5)
.AsEnumerable()
.GroupBy(x => x.GuildId)
.ToDictionary(x => x.Key,
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
.ToConcurrent();
var startupCommands = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
foreach (var cmd in startupCommands)
@@ -169,18 +168,18 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
private async Task LoadOwnerChannels()
{
var channels = await _creds.OwnerIds.Select(id =>
{
var user = _client.GetUser(id);
if (user is null)
return Task.FromResult<IDMChannel>(null);
{
var user = _client.GetUser(id);
if (user is null)
return Task.FromResult<IDMChannel>(null);
return user.CreateDMChannelAsync();
})
.WhenAll();
return user.CreateDMChannelAsync();
})
.WhenAll();
ownerChannels = channels.Where(x => x is not null)
.ToDictionary(x => x.Recipient.Id, x => x)
.ToImmutableDictionary();
.ToDictionary(x => x.Recipient.Id, x => x)
.ToImmutableDictionary();
if (!ownerChannels.Any())
{
@@ -202,7 +201,7 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
{
var bs = _bss.Data;
if (msg.Channel is IDMChannel && bs.ForwardMessages && ownerChannels.Any())
if (msg.Channel is IDMChannel && bs.ForwardMessages && (ownerChannels.Any() || bs.ForwardToChannel is not null))
{
var title = _strings.GetText(strs.dm_from) + $" [{msg.Author}]({msg.Author.Id})";
@@ -232,6 +231,18 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
}
}
}
else if (bs.ForwardToChannel is ulong cid)
{
try
{
if (_client.GetChannel(cid) is ITextChannel ch)
await ch.SendConfirmAsync(_eb, title, toSend);
}
catch
{
Log.Warning("Error forwarding message to the channel");
}
}
else
{
var firstOwnerChannel = ownerChannels.Values.First();
@@ -309,6 +320,40 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
return true;
}
public async Task<bool> SetBanner(string img)
{
if (string.IsNullOrWhiteSpace(img))
{
return false;
}
if (!Uri.IsWellFormedUriString(img, UriKind.Absolute))
{
return false;
}
var uri = new Uri(img);
using var http = _httpFactory.CreateClient();
using var sr = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
if (!sr.IsImage())
{
return false;
}
if (sr.GetContentLength() > 8.Megabytes().Bytes)
{
return false;
}
await using var imageStream = await sr.Content.ReadAsStreamAsync();
await _client.CurrentUser.ModifyAsync(x => x.Banner = new Image(imageStream));
return true;
}
public void ClearStartupCommands()
{
using var uow = _db.GetDbContext();
@@ -333,6 +378,20 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
return isToAll;
}
public bool ForwardToChannel(ulong? channelId)
{
using var uow = _db.GetDbContext();
_bss.ModifyConfig(config =>
{
config.ForwardToChannel = channelId == config.ForwardToChannel
? null
: channelId;
});
return channelId is not null;
}
private void HandleStatusChanges()
=> _pubSub.Sub(_activitySetKey,
async data =>

View File

@@ -9,9 +9,9 @@ using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Administration;
public sealed class LogCommandService : ILogCommandService, IReadyExecutor
#if !GLOBAL_NADEKO
, INService // don't load this service on global nadeko
#endif
#if !GLOBAL_NADEKO
, INService // don't load this service on global nadeko
#endif
{
public ConcurrentDictionary<ulong, LogSetting> GuildLogSettings { get; }
@@ -49,15 +49,15 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
_prot = prot;
_tz = tz;
_punishService = punishService;
using (var uow = db.GetDbContext())
{
var guildIds = client.Guilds.Select(x => x.Id).ToList();
var configs = uow.LogSettings.AsQueryable()
.AsNoTracking()
.Where(x => guildIds.Contains(x.GuildId))
.Include(ls => ls.LogIgnores)
.ToList();
.AsNoTracking()
.Where(x => guildIds.Contains(x.GuildId))
.Include(ls => ls.LogIgnores)
.ToList();
GuildLogSettings = configs.ToDictionary(ls => ls.GuildId).ToConcurrent();
}
@@ -73,20 +73,144 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS;
_client.GuildMemberUpdated += _client_GuildUserUpdated;
_client.PresenceUpdated += _client_PresenceUpdated;
_client.UserUpdated += _client_UserUpdated;
_client.ChannelCreated += _client_ChannelCreated;
_client.ChannelDestroyed += _client_ChannelDestroyed;
_client.ChannelUpdated += _client_ChannelUpdated;
_client.RoleDeleted += _client_RoleDeleted;
_client.ThreadCreated += _client_ThreadCreated;
_client.ThreadDeleted += _client_ThreadDeleted;
_mute.UserMuted += MuteCommands_UserMuted;
_mute.UserUnmuted += MuteCommands_UserUnmuted;
_prot.OnAntiProtectionTriggered += TriggeredAntiProtection;
_punishService.OnUserWarned += PunishServiceOnOnUserWarned;
}
private async Task _client_PresenceUpdated(SocketUser user, SocketPresence? before, SocketPresence? after)
{
if (user is not SocketGuildUser gu)
return;
if (!GuildLogSettings.TryGetValue(gu.Guild.Id, out var logSetting)
|| before is null
|| after is null
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == gu.Id && ilc.ItemType == IgnoredItemType.User))
return;
ITextChannel? logChannel;
if (!user.IsBot
&& logSetting.LogUserPresenceId is not null
&& (logChannel =
await TryGetLogChannel(gu.Guild, logSetting, LogType.UserPresence)) is not null)
{
if (before.Status != after.Status)
{
var str = "🎭"
+ Format.Code(PrettyCurrentTime(gu.Guild))
+ GetText(logChannel.Guild,
strs.user_status_change("👤" + Format.Bold(gu.Username),
Format.Bold(after.Status.ToString())));
PresenceUpdates.AddOrUpdate(logChannel,
new List<string>
{
str
},
(_, list) =>
{
list.Add(str);
return list;
});
}
else if (before.Activities.FirstOrDefault()?.Name != after.Activities.FirstOrDefault()?.Name)
{
var str =
$"👾`{PrettyCurrentTime(gu.Guild)}`👤__**{gu.Username}**__ is now playing **{after.Activities.FirstOrDefault()?.Name ?? "-"}**.";
PresenceUpdates.AddOrUpdate(logChannel,
new List<string>
{
str
},
(_, list) =>
{
list.Add(str);
return list;
});
}
}
}
private Task _client_ThreadDeleted(Cacheable<SocketThreadChannel, ulong> sch)
{
_ = Task.Run(async () =>
{
try
{
if (!sch.HasValue)
return;
var ch = sch.Value;
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out var logSetting)
|| logSetting.ThreadDeletedId is null)
return;
ITextChannel? logChannel;
if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ThreadDeleted)) is null)
return;
var title = GetText(logChannel.Guild, strs.thread_deleted);
await logChannel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle("🗑 " + title)
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(CurrentTime(ch.Guild)));
}
catch (Exception)
{
// ignored
}
});
return Task.CompletedTask;
}
private Task _client_ThreadCreated(SocketThreadChannel ch)
{
_ = Task.Run(async () =>
{
try
{
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out var logSetting)
|| logSetting.ThreadCreatedId is null)
return;
ITextChannel? logChannel;
if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ThreadCreated)) is null)
return;
var title = GetText(logChannel.Guild, strs.thread_created);
await logChannel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(CurrentTime(ch.Guild)));
}
catch (Exception)
{
// ignored
}
});
return Task.CompletedTask;
}
public async Task OnReadyAsync()
=> await Task.WhenAll(PresenceUpdateTask(), IgnoreMessageIdsClearTask());
@@ -107,22 +231,24 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
var keys = PresenceUpdates.Keys.ToList();
await keys.Select(key =>
{
if (!((SocketGuild)key.Guild).CurrentUser.GetPermissions(key).SendMessages)
return Task.CompletedTask;
{
if (!((SocketGuild)key.Guild).CurrentUser.GetPermissions(key).SendMessages)
return Task.CompletedTask;
if (PresenceUpdates.TryRemove(key, out var msgs))
{
var title = GetText(key.Guild, strs.presence_updates);
var desc = string.Join(Environment.NewLine, msgs);
return key.SendConfirmAsync(_eb, title, desc.TrimTo(2048)!);
}
if (PresenceUpdates.TryRemove(key, out var msgs))
{
var title = GetText(key.Guild, strs.presence_updates);
var desc = string.Join(Environment.NewLine, msgs);
return key.SendConfirmAsync(_eb, title, desc.TrimTo(2048)!);
}
return Task.CompletedTask;
})
.WhenAll();
return Task.CompletedTask;
})
.WhenAll();
}
catch
{
}
catch { }
}
}
@@ -185,35 +311,35 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
logSetting.UserLeftId = logSetting.UserBannedId = logSetting.UserUnbannedId = logSetting.UserUpdatedId =
logSetting.ChannelCreatedId = logSetting.ChannelDestroyedId = logSetting.ChannelUpdatedId =
logSetting.LogUserPresenceId = logSetting.LogVoicePresenceId = logSetting.UserMutedId =
logSetting.LogVoicePresenceTTSId = value ? channelId : null;
logSetting.LogVoicePresenceTTSId = logSetting.ThreadCreatedId = logSetting.ThreadDeletedId
= logSetting.LogWarnsId = value ? channelId : null;
await uow.SaveChangesAsync();
GuildLogSettings.AddOrUpdate(guildId, _ => logSetting, (_, _) => logSetting);
}
private async Task PunishServiceOnOnUserWarned(Warning arg)
{
if (!GuildLogSettings.TryGetValue(arg.GuildId, out var logSetting) || logSetting.LogWarnsId is null)
return;
var g = _client.GetGuild(arg.GuildId);
ITextChannel? logChannel;
if ((logChannel = await TryGetLogChannel(g, logSetting, LogType.UserWarned)) is null)
return;
var embed = _eb.Create()
.WithOkColor()
.WithTitle($"⚠️ User Warned")
.WithDescription($"<@{arg.UserId}> | {arg.UserId}")
.AddField("Mod", arg.Moderator)
.AddField("Reason", string.IsNullOrWhiteSpace(arg.Reason) ? "-" : arg.Reason, true)
.WithFooter(CurrentTime(g));
.WithOkColor()
.WithTitle($"⚠️ User Warned")
.WithDescription($"<@{arg.UserId}> | {arg.UserId}")
.AddField("Mod", arg.Moderator)
.AddField("Reason", string.IsNullOrWhiteSpace(arg.Reason) ? "-" : arg.Reason, true)
.WithFooter(CurrentTime(g));
await logChannel.EmbedAsync(embed);
}
private Task _client_UserUpdated(SocketUser before, SocketUser uAfter)
{
_ = Task.Run(async () =>
@@ -225,7 +351,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
var g = after.Guild;
if (!GuildLogSettings.TryGetValue(g.Id, out var logSetting) || logSetting.UserUpdatedId is null)
if (!GuildLogSettings.TryGetValue(g.Id, out var logSetting) || logSetting.UserUpdatedId is null || logSetting.LogIgnores.Any(ilc => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.User))
return;
ITextChannel? logChannel;
@@ -237,18 +363,18 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
if (before.Username != after.Username)
{
embed.WithTitle("👥 " + GetText(g, strs.username_changed))
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.AddField("Old Name", $"{before.Username}", true)
.AddField("New Name", $"{after.Username}", true)
.WithFooter(CurrentTime(g))
.WithOkColor();
.WithDescription($"{before.Username} | {before.Id}")
.AddField("Old Name", $"{before.Username}", true)
.AddField("New Name", $"{after.Username}", true)
.WithFooter(CurrentTime(g))
.WithOkColor();
}
else if (before.AvatarId != after.AvatarId)
{
embed.WithTitle("👥" + GetText(g, strs.avatar_changed))
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithFooter(CurrentTime(g))
.WithOkColor();
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithFooter(CurrentTime(g))
.WithOkColor();
var bav = before.RealAvatarUrl();
if (bav.IsAbsoluteUri)
@@ -330,6 +456,12 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
case LogType.UserWarned:
channelId = logSetting.LogWarnsId = logSetting.LogWarnsId is null ? cid : default;
break;
case LogType.ThreadDeleted:
channelId = logSetting.ThreadDeletedId = logSetting.ThreadDeletedId is null ? cid : default;
break;
case LogType.ThreadCreated:
channelId = logSetting.ThreadCreatedId = logSetting.ThreadCreatedId is null ? cid : default;
break;
}
uow.SaveChanges();
@@ -412,10 +544,10 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
}
var embed = _eb.Create()
.WithAuthor(mutes)
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter(CurrentTime(usr.Guild))
.WithOkColor();
.WithAuthor(mutes)
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter(CurrentTime(usr.Guild))
.WithOkColor();
await logChannel.EmbedAsync(embed);
}
@@ -459,10 +591,10 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
}
var embed = _eb.Create()
.WithAuthor(mutes)
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter($"{CurrentTime(usr.Guild)}")
.WithOkColor();
.WithAuthor(mutes)
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter($"{CurrentTime(usr.Guild)}")
.WithOkColor();
if (!string.IsNullOrWhiteSpace(reason))
embed.WithDescription(reason);
@@ -513,11 +645,11 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
}
var embed = _eb.Create()
.WithAuthor($"🛡 Anti-{protection}")
.WithTitle(GetText(logChannel.Guild, strs.users) + " " + punishment)
.WithDescription(string.Join("\n", users.Select(u => u.ToString())))
.WithFooter(CurrentTime(logChannel.Guild))
.WithOkColor();
.WithAuthor($"🛡 Anti-{protection}")
.WithTitle(GetText(logChannel.Guild, strs.users) + " " + punishment)
.WithDescription(string.Join("\n", users.Select(u => u.ToString())))
.WithFooter(CurrentTime(logChannel.Guild))
.WithOkColor();
await logChannel.EmbedAsync(embed);
}
@@ -566,16 +698,16 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
&& (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) is not null)
{
var embed = _eb.Create()
.WithOkColor()
.WithFooter(CurrentTime(before.Guild))
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}");
.WithOkColor()
.WithFooter(CurrentTime(before.Guild))
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}");
if (before.Nickname != after.Nickname)
{
embed.WithAuthor("👥 " + GetText(logChannel.Guild, strs.nick_change))
.AddField(GetText(logChannel.Guild, strs.old_nick),
$"{before.Nickname}#{before.Discriminator}")
.AddField(GetText(logChannel.Guild, strs.new_nick),
$"{after.Nickname}#{after.Discriminator}");
.AddField(GetText(logChannel.Guild, strs.old_nick),
$"{before.Nickname}#{before.Discriminator}")
.AddField(GetText(logChannel.Guild, strs.new_nick),
$"{after.Nickname}#{after.Discriminator}");
await logChannel.EmbedAsync(embed);
}
@@ -585,7 +717,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
{
var diffRoles = after.Roles.Where(r => !before.Roles.Contains(r)).Select(r => r.Name);
embed.WithAuthor("⚔ " + GetText(logChannel.Guild, strs.user_role_add))
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
await logChannel.EmbedAsync(embed);
}
@@ -593,59 +725,19 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
{
await Task.Delay(1000);
var diffRoles = before.Roles.Where(r => !after.Roles.Contains(r) && !IsRoleDeleted(r.Id))
.Select(r => r.Name)
.ToList();
.Select(r => r.Name)
.ToList();
if (diffRoles.Any())
{
embed.WithAuthor("⚔ " + GetText(logChannel.Guild, strs.user_role_rem))
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
await logChannel.EmbedAsync(embed);
}
}
}
}
if (!before.IsBot
&& logSetting.LogUserPresenceId is not null
&& (logChannel =
await TryGetLogChannel(before.Guild, logSetting, LogType.UserPresence)) is not null)
{
if (before.Status != after.Status)
{
var str = "🎭"
+ Format.Code(PrettyCurrentTime(after.Guild))
+ GetText(logChannel.Guild,
strs.user_status_change("👤" + Format.Bold(after.Username),
Format.Bold(after.Status.ToString())));
PresenceUpdates.AddOrUpdate(logChannel,
new List<string>
{
str
},
(_, list) =>
{
list.Add(str);
return list;
});
}
else if (before.Activities.FirstOrDefault()?.Name != after.Activities.FirstOrDefault()?.Name)
{
var str =
$"👾`{PrettyCurrentTime(after.Guild)}`👤__**{after.Username}**__ is now playing **{after.Activities.FirstOrDefault()?.Name ?? "-"}**.";
PresenceUpdates.AddOrUpdate(logChannel,
new List<string>
{
str
},
(_, list) =>
{
list.Add(str);
return list;
});
}
}
}
catch
{
@@ -684,15 +776,15 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
if (before.Name != after.Name)
{
embed.WithTitle(" " + GetText(logChannel.Guild, strs.ch_name_change))
.WithDescription($"{after} | {after.Id}")
.AddField(GetText(logChannel.Guild, strs.ch_old_name), before.Name);
.WithDescription($"{after} | {after.Id}")
.AddField(GetText(logChannel.Guild, strs.ch_old_name), before.Name);
}
else if (beforeTextChannel?.Topic != afterTextChannel?.Topic)
{
embed.WithTitle(" " + GetText(logChannel.Guild, strs.ch_topic_change))
.WithDescription($"{after} | {after.Id}")
.AddField(GetText(logChannel.Guild, strs.old_topic), beforeTextChannel?.Topic ?? "-")
.AddField(GetText(logChannel.Guild, strs.new_topic), afterTextChannel?.Topic ?? "-");
.WithDescription($"{after} | {after.Id}")
.AddField(GetText(logChannel.Guild, strs.old_topic), beforeTextChannel?.Topic ?? "-")
.AddField(GetText(logChannel.Guild, strs.new_topic), afterTextChannel?.Topic ?? "-");
}
else
return;
@@ -725,6 +817,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
ITextChannel? logChannel;
if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelDestroyed)) is null)
return;
string title;
if (ch is IVoiceChannel)
title = GetText(logChannel.Guild, strs.voice_chan_destroyed);
@@ -732,10 +825,10 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
title = GetText(logChannel.Guild, strs.text_chan_destroyed);
await logChannel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(CurrentTime(ch.Guild)));
.WithOkColor()
.WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(CurrentTime(ch.Guild)));
}
catch
{
@@ -768,10 +861,10 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
title = GetText(logChannel.Guild, strs.text_chan_created);
await logChannel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(CurrentTime(ch.Guild)));
.WithOkColor()
.WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(CurrentTime(ch.Guild)));
}
catch (Exception)
{
@@ -812,7 +905,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
str = "🎙"
+ Format.Code(PrettyCurrentTime(usr.Guild))
+ GetText(logChannel.Guild,
strs.user_vmoved("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
strs.user_vmoved("👤" + Format.Bold(usr.Username),
Format.Bold(beforeVch?.Name ?? ""),
Format.Bold(afterVch?.Name ?? "")));
}
@@ -821,7 +914,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
str = "🎙"
+ Format.Code(PrettyCurrentTime(usr.Guild))
+ GetText(logChannel.Guild,
strs.user_vjoined("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
strs.user_vjoined("👤" + Format.Bold(usr.Username),
Format.Bold(afterVch?.Name ?? "")));
}
else if (afterVch is null)
@@ -829,7 +922,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
str = "🎙"
+ Format.Code(PrettyCurrentTime(usr.Guild))
+ GetText(logChannel.Guild,
strs.user_vleft("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
strs.user_vleft("👤" + Format.Bold(usr.Username),
Format.Bold(beforeVch.Name ?? "")));
}
@@ -871,11 +964,11 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserLeft)) is null)
return;
var embed = _eb.Create()
.WithOkColor()
.WithTitle("❌ " + GetText(logChannel.Guild, strs.user_left))
.WithDescription(usr.ToString())
.AddField("Id", usr.Id.ToString())
.WithFooter(CurrentTime(guild));
.WithOkColor()
.WithTitle("❌ " + GetText(logChannel.Guild, strs.user_left))
.WithDescription(usr.ToString())
.AddField("Id", usr.Id.ToString())
.WithFooter(CurrentTime(guild));
if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute))
embed.WithThumbnailUrl(usr.GetAvatarUrl());
@@ -904,17 +997,17 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
return;
var embed = _eb.Create()
.WithOkColor()
.WithTitle("✅ " + GetText(logChannel.Guild, strs.user_joined))
.WithDescription($"{usr.Mention} `{usr}`")
.AddField("Id", usr.Id.ToString())
.AddField(GetText(logChannel.Guild, strs.joined_server),
$"{usr.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "?"}",
true)
.AddField(GetText(logChannel.Guild, strs.joined_discord),
$"{usr.CreatedAt:dd.MM.yyyy HH:mm}",
true)
.WithFooter(CurrentTime(usr.Guild));
.WithOkColor()
.WithTitle("✅ " + GetText(logChannel.Guild, strs.user_joined))
.WithDescription($"{usr.Mention} `{usr}`")
.AddField("Id", usr.Id.ToString())
.AddField(GetText(logChannel.Guild, strs.joined_server),
$"{usr.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "?"}",
true)
.AddField(GetText(logChannel.Guild, strs.joined_discord),
$"{usr.CreatedAt:dd.MM.yyyy HH:mm}",
true)
.WithFooter(CurrentTime(usr.Guild));
if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute))
embed.WithThumbnailUrl(usr.GetAvatarUrl());
@@ -945,11 +1038,11 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserUnbanned)) is null)
return;
var embed = _eb.Create()
.WithOkColor()
.WithTitle("♻️ " + GetText(logChannel.Guild, strs.user_unbanned))
.WithDescription(usr.ToString()!)
.AddField("Id", usr.Id.ToString())
.WithFooter(CurrentTime(guild));
.WithOkColor()
.WithTitle("♻️ " + GetText(logChannel.Guild, strs.user_unbanned))
.WithDescription(usr.ToString()!)
.AddField("Id", usr.Id.ToString())
.WithFooter(CurrentTime(guild));
if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute))
embed.WithThumbnailUrl(usr.GetAvatarUrl());
@@ -989,16 +1082,15 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
}
catch
{
}
var embed = _eb.Create()
.WithOkColor()
.WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned))
.WithDescription(usr.ToString()!)
.AddField("Id", usr.Id.ToString())
.AddField("Reason", string.IsNullOrWhiteSpace(reason) ? "-" : reason)
.WithFooter(CurrentTime(guild));
.WithOkColor()
.WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned))
.WithDescription(usr.ToString()!)
.AddField("Id", usr.Id.ToString())
.AddField("Reason", string.IsNullOrWhiteSpace(reason) ? "-" : reason)
.WithFooter(CurrentTime(guild));
var avatarUrl = usr.GetAvatarUrl();
@@ -1044,14 +1136,14 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
var resolvedMessage = msg.Resolve(TagHandling.FullName);
var embed = _eb.Create()
.WithOkColor()
.WithTitle("🗑 "
+ GetText(logChannel.Guild, strs.msg_del(((ITextChannel)msg.Channel).Name)))
.WithDescription(msg.Author.ToString()!)
.AddField(GetText(logChannel.Guild, strs.content),
string.IsNullOrWhiteSpace(resolvedMessage) ? "-" : resolvedMessage)
.AddField("Id", msg.Id.ToString())
.WithFooter(CurrentTime(channel.Guild));
.WithOkColor()
.WithTitle("🗑 "
+ GetText(logChannel.Guild, strs.msg_del(((ITextChannel)msg.Channel).Name)))
.WithDescription(msg.Author.ToString()!)
.AddField(GetText(logChannel.Guild, strs.content),
string.IsNullOrWhiteSpace(resolvedMessage) ? "-" : resolvedMessage)
.AddField("Id", msg.Id.ToString())
.WithFooter(CurrentTime(channel.Guild));
if (msg.Attachments.Any())
{
embed.AddField(GetText(logChannel.Guild, strs.attachments),
@@ -1104,19 +1196,19 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
return;
var embed = _eb.Create()
.WithOkColor()
.WithTitle("📝 "
+ GetText(logChannel.Guild,
strs.msg_update(((ITextChannel)after.Channel).Name)))
.WithDescription(after.Author.ToString()!)
.AddField(GetText(logChannel.Guild, strs.old_msg),
string.IsNullOrWhiteSpace(before.Content)
? "-"
: before.Resolve(TagHandling.FullName))
.AddField(GetText(logChannel.Guild, strs.new_msg),
string.IsNullOrWhiteSpace(after.Content) ? "-" : after.Resolve(TagHandling.FullName))
.AddField("Id", after.Id.ToString())
.WithFooter(CurrentTime(channel.Guild));
.WithOkColor()
.WithTitle("📝 "
+ GetText(logChannel.Guild,
strs.msg_update(((ITextChannel)after.Channel).Name)))
.WithDescription(after.Author.ToString()!)
.AddField(GetText(logChannel.Guild, strs.old_msg),
string.IsNullOrWhiteSpace(before.Content)
? "-"
: before.Resolve(TagHandling.FullName))
.AddField(GetText(logChannel.Guild, strs.new_msg),
string.IsNullOrWhiteSpace(after.Content) ? "-" : after.Resolve(TagHandling.FullName))
.AddField("Id", after.Id.ToString())
.WithFooter(CurrentTime(channel.Guild));
await logChannel.EmbedAsync(embed);
}
@@ -1181,6 +1273,12 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
case LogType.UserWarned:
id = logSetting.LogWarnsId;
break;
case LogType.ThreadCreated:
id = logSetting.ThreadCreatedId;
break;
case LogType.ThreadDeleted:
id = logSetting.ThreadDeletedId;
break;
}
if (id is null or 0)
@@ -1259,4 +1357,4 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (_, _) => newLogSetting);
uow.SaveChanges();
}
}
}

View File

@@ -143,6 +143,12 @@ public partial class Administration
return l.LogVoicePresenceTTSId;
case LogType.UserMuted:
return l.UserMutedId;
case LogType.UserWarned:
return l.LogWarnsId;
case LogType.ThreadDeleted:
return l.ThreadDeletedId;
case LogType.ThreadCreated:
return l.ThreadCreatedId;
default:
return null;
}

View File

@@ -402,12 +402,21 @@ public partial class Administration
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[Priority(1)]
public async Task Ban(StoopidTime time, IUser user, [Leftover] string msg = null)
public Task Ban(StoopidTime time, IUser user, [Leftover] string msg = null)
=> Ban(time, user.Id, msg);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[Priority(0)]
public async Task Ban(StoopidTime time, ulong userId, [Leftover] string msg = null)
{
if (time.Time > TimeSpan.FromDays(49))
return;
var guildUser = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, user.Id);
var guildUser = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
if (guildUser is not null && !await CheckRoleHierarchy(guildUser))
return;
@@ -429,13 +438,14 @@ public partial class Administration
}
}
var user = await ctx.Client.GetUserAsync(userId);
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
await _mute.TimedBan(ctx.Guild, user, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
await _mute.TimedBan(ctx.Guild, userId, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
var toSend = _eb.Create()
.WithOkColor()
.WithTitle("⛔️ " + GetText(strs.banned_user))
.AddField(GetText(strs.username), user.ToString(), true)
.AddField("ID", user.Id.ToString(), true)
.AddField(GetText(strs.username), user?.ToString() ?? userId.ToString(), true)
.AddField("ID", userId.ToString(), true)
.AddField(GetText(strs.duration),
time.Time.Humanize(3, minUnit: TimeUnit.Minute, culture: Culture),
true);

View File

@@ -157,7 +157,7 @@ public class UserPunishService : INService, IReadyExecutor
if (minutes == 0)
await guild.AddBanAsync(user, reason: reason, pruneDays: banPrune);
else
await _mute.TimedBan(user.Guild, user, TimeSpan.FromMinutes(minutes), reason, banPrune);
await _mute.TimedBan(user.Guild, user.Id, TimeSpan.FromMinutes(minutes), reason, banPrune);
break;
case PunishmentAction.Softban:
banPrune = await GetBanPruneAsync(user.GuildId) ?? 7;

View File

@@ -41,6 +41,17 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
}
[Cmd]
[UserPerm(GuildPerm.Administrator)]
public async Task ExprToggleGlobal()
{
var result = await _service.ToggleGlobalExpressionsAsync(ctx.Guild.Id);
if (result)
await ReplyConfirmLocalizedAsync(strs.expr_global_disabled);
else
await ReplyConfirmLocalizedAsync(strs.expr_global_enabled);
}
[Cmd]
[UserPerm(GuildPerm.Administrator)]
public async Task ExprAddServer(string key, [Leftover] string message)

View File

@@ -7,6 +7,7 @@ using NadekoBot.Modules.Permissions.Common;
using NadekoBot.Modules.Permissions.Services;
using NadekoBot.Services.Database.Models;
using System.Runtime.CompilerServices;
using LinqToDB.EntityFrameworkCore;
using Nadeko.Common;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
@@ -33,14 +34,14 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
";
private static readonly ISerializer _exportSerializer = new SerializerBuilder()
.WithEventEmitter(args
=> new MultilineScalarFlowStyleEmitter(args))
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.WithIndentedSequences()
.ConfigureDefaultValuesHandling(DefaultValuesHandling
.OmitDefaults)
.DisableAliases()
.Build();
.WithEventEmitter(args
=> new MultilineScalarFlowStyleEmitter(args))
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.WithIndentedSequences()
.ConfigureDefaultValuesHandling(DefaultValuesHandling
.OmitDefaults)
.DisableAliases()
.Build();
public int Priority
=> 0;
@@ -56,8 +57,8 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
// 1. expressions are almost never added (compared to how many times they are being looped through)
// 2. only need write locks for this as we'll rebuild+replace the array on every edit
// 3. there's never many of them (at most a thousand, usually < 100)
private NadekoExpression[] globalReactions;
private ConcurrentDictionary<ulong, NadekoExpression[]> newGuildReactions;
private NadekoExpression[] globalExpressions = Array.Empty<NadekoExpression>();
private ConcurrentDictionary<ulong, NadekoExpression[]> newguildExpressions = new();
private readonly DbService _db;
private readonly DiscordSocketClient _client;
@@ -72,6 +73,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
private readonly Random _rng;
private bool ready;
private ConcurrentHashSet<ulong> _disabledGlobalExpressionGuilds;
public NadekoExpressionsService(
PermissionService perms,
@@ -110,32 +112,37 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
await using var uow = _db.GetDbContext();
var guildItems = await uow.Expressions.AsNoTracking()
.Where(x => allGuildIds.Contains(x.GuildId.Value))
.ToListAsync();
.Where(x => allGuildIds.Contains(x.GuildId.Value))
.ToListAsync();
newGuildReactions = guildItems.GroupBy(k => k.GuildId!.Value)
.ToDictionary(g => g.Key,
g => g.Select(x =>
{
x.Trigger = x.Trigger.Replace(MENTION_PH, _bot.Mention);
return x;
})
.ToArray())
.ToConcurrent();
newguildExpressions = guildItems.GroupBy(k => k.GuildId!.Value)
.ToDictionary(g => g.Key,
g => g.Select(x =>
{
x.Trigger = x.Trigger.Replace(MENTION_PH, _bot.Mention);
return x;
})
.ToArray())
.ToConcurrent();
_disabledGlobalExpressionGuilds = new(await uow.GuildConfigs
.Where(x => x.DisableGlobalExpressions)
.Select(x => x.GuildId)
.ToListAsyncLinqToDB());
lock (_gexprWriteLock)
{
var globalItems = uow.Expressions.AsNoTracking()
.Where(x => x.GuildId == null || x.GuildId == 0)
.AsEnumerable()
.Select(x =>
{
x.Trigger = x.Trigger.Replace(MENTION_PH, _bot.Mention);
return x;
})
.ToArray();
.Where(x => x.GuildId == null || x.GuildId == 0)
.AsEnumerable()
.Select(x =>
{
x.Trigger = x.Trigger.Replace(MENTION_PH, _bot.Mention);
return x;
})
.ToArray();
globalReactions = globalItems;
globalExpressions = globalItems;
}
ready = true;
@@ -151,14 +158,17 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
var content = umsg.Content.Trim().ToLowerInvariant();
if (newGuildReactions.TryGetValue(channel.Guild.Id, out var reactions) && reactions.Length > 0)
if (newguildExpressions.TryGetValue(channel.Guild.Id, out var expressions) && expressions.Length > 0)
{
var expr = MatchExpressions(content, reactions);
var expr = MatchExpressions(content, expressions);
if (expr is not null)
return expr;
}
var localGrs = globalReactions;
if (_disabledGlobalExpressionGuilds.Contains(channel.Guild.Id))
return null;
var localGrs = globalExpressions;
return MatchExpressions(content, localGrs);
}
@@ -345,7 +355,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
if (maybeGuildId is { } guildId)
{
newGuildReactions.AddOrUpdate(guildId,
newguildExpressions.AddOrUpdate(guildId,
new[] { expr },
(_, old) =>
{
@@ -363,7 +373,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
lock (_gexprWriteLock)
{
var exprs = globalReactions;
var exprs = globalExpressions;
for (var i = 0; i < exprs.Length; i++)
{
if (exprs[i].Id == expr.Id)
@@ -379,7 +389,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
expr.Trigger = expr.Trigger.Replace(MENTION_PH, _client.CurrentUser.Mention);
if (maybeGuildId is { } guildId)
newGuildReactions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr));
newguildExpressions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr));
else
return _pubSub.Pub(_gexprAddedKey, expr);
@@ -390,7 +400,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
if (maybeGuildId is { } guildId)
{
newGuildReactions.AddOrUpdate(guildId,
newguildExpressions.AddOrUpdate(guildId,
Array.Empty<NadekoExpression>(),
(key, old) => DeleteInternal(old, id, out _));
@@ -399,7 +409,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
lock (_gexprWriteLock)
{
var expr = Array.Find(globalReactions, item => item.Id == id);
var expr = Array.Find(globalExpressions, item => item.Id == id);
if (expr is not null)
return _pubSub.Pub(_gexprDeletedkey, expr.Id);
}
@@ -456,7 +466,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
await using (var uow = _db.GetDbContext())
{
expr = uow.Expressions.GetById(id);
if (expr is null || expr.GuildId != guildId)
return (false, false);
if (field == ExprField.AutoDelete)
@@ -492,16 +502,32 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
var count = uow.Expressions.ClearFromGuild(guildId);
uow.SaveChanges();
newGuildReactions.TryRemove(guildId, out _);
newguildExpressions.TryRemove(guildId, out _);
return count;
}
public bool ExpressionExists(ulong? guildId, string input)
{
using var uow = _db.GetDbContext();
var expr = uow.Expressions.GetByGuildIdAndInput(guildId, input);
return expr is not null;
input = input.ToLowerInvariant();
var gexprs = globalExpressions;
foreach (var t in gexprs)
{
if (t.Trigger == input)
return true;
}
if (guildId is ulong gid && newguildExpressions.TryGetValue(gid, out var guildExprs))
{
foreach (var t in guildExprs)
{
if (t.Trigger == input)
return true;
}
}
return false;
}
public string ExportExpressions(ulong? guildId)
@@ -532,17 +558,17 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
var trigger = entry.Key;
await uow.Expressions.AddRangeAsync(entry.Value.Where(expr => !string.IsNullOrWhiteSpace(expr.Res))
.Select(expr => new NadekoExpression
{
GuildId = guildId,
Response = expr.Res,
Reactions = expr.React?.Join("@@@"),
Trigger = trigger,
AllowTarget = expr.At,
ContainsAnywhere = expr.Ca,
DmResponse = expr.Dm,
AutoDeleteTrigger = expr.Ad
}));
.Select(expr => new NadekoExpression
{
GuildId = guildId,
Response = expr.Res,
Reactions = expr.React?.Join("@@@"),
Trigger = trigger,
AllowTarget = expr.At,
ContainsAnywhere = expr.Ca,
DmResponse = expr.Dm,
AutoDeleteTrigger = expr.Ad
}));
}
await uow.SaveChangesAsync();
@@ -562,10 +588,10 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
lock (_gexprWriteLock)
{
var newGlobalReactions = new NadekoExpression[globalReactions.Length + 1];
Array.Copy(globalReactions, newGlobalReactions, globalReactions.Length);
newGlobalReactions[globalReactions.Length] = c;
globalReactions = newGlobalReactions;
var newGlobalReactions = new NadekoExpression[globalExpressions.Length + 1];
Array.Copy(globalExpressions, newGlobalReactions, globalExpressions.Length);
newGlobalReactions[globalExpressions.Length] = c;
globalExpressions = newGlobalReactions;
}
return default;
@@ -575,11 +601,11 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
lock (_gexprWriteLock)
{
for (var i = 0; i < globalReactions.Length; i++)
for (var i = 0; i < globalExpressions.Length; i++)
{
if (globalReactions[i].Id == c.Id)
if (globalExpressions[i].Id == c.Id)
{
globalReactions[i] = c;
globalExpressions[i] = c;
return default;
}
}
@@ -596,8 +622,8 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
lock (_gexprWriteLock)
{
var newGlobalReactions = DeleteInternal(globalReactions, id, out _);
globalReactions = newGlobalReactions;
var newGlobalReactions = DeleteInternal(globalExpressions, id, out _);
globalExpressions = newGlobalReactions;
}
return default;
@@ -612,7 +638,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
private Task OnLeftGuild(SocketGuild arg)
{
newGuildReactions.TryRemove(arg.Id, out _);
newguildExpressions.TryRemove(arg.Id, out _);
return Task.CompletedTask;
}
@@ -622,7 +648,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
await using var uow = _db.GetDbContext();
var exprs = await uow.Expressions.AsNoTracking().Where(x => x.GuildId == gc.GuildId).ToArrayAsync();
newGuildReactions[gc.GuildId] = exprs;
newguildExpressions[gc.GuildId] = exprs;
}
#endregion
@@ -702,10 +728,25 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
public NadekoExpression[] GetExpressionsFor(ulong? maybeGuildId)
{
if (maybeGuildId is { } guildId)
return newGuildReactions.TryGetValue(guildId, out var exprs) ? exprs : Array.Empty<NadekoExpression>();
return newguildExpressions.TryGetValue(guildId, out var exprs) ? exprs : Array.Empty<NadekoExpression>();
return globalReactions;
return globalExpressions;
}
#endregion
public async Task<bool> ToggleGlobalExpressionsAsync(ulong guildId)
{
await using var ctx = _db.GetDbContext();
var gc = ctx.GuildConfigsForId(guildId, set => set);
var toReturn = gc.DisableGlobalExpressions = !gc.DisableGlobalExpressions;
await ctx.SaveChangesAsync();
if (toReturn)
_disabledGlobalExpressionGuilds.Add(guildId);
else
_disabledGlobalExpressionGuilds.TryRemove(guildId);
return toReturn;
}
}

View File

@@ -77,28 +77,24 @@ public partial class Gambling
{
if (await _bank.TakeAsync(userId, amount))
{
await ReplyErrorLocalizedAsync(strs.take_fail(N(amount),
_client.GetUser(userId)?.ToString()
?? userId.ToString(),
CurrencySign));
await ctx.OkAsync();
return;
}
await ctx.OkAsync();
await ReplyErrorLocalizedAsync(strs.take_fail(N(amount),
_client.GetUser(userId)?.ToString()
?? userId.ToString(),
CurrencySign));
}
private async Task BankAwardInternalAsync(long amount, ulong userId)
{
if (await _bank.AwardAsync(userId, amount))
{
await ReplyErrorLocalizedAsync(strs.take_fail(N(amount),
_client.GetUser(userId)?.ToString()
?? userId.ToString(),
CurrencySign));
await ctx.OkAsync();
return;
}
await ctx.OkAsync();
}
[Cmd]

View File

@@ -88,10 +88,6 @@ public class GameStatusEvent : ICurrencyEvent
await msg.ModifyAsync(m =>
{
m.Embed = GetEmbed(PotSize).Build();
},
new()
{
RetryMode = RetryMode.AlwaysRetry
});
}

View File

@@ -79,10 +79,6 @@ public class ReactionEvent : ICurrencyEvent
await msg.ModifyAsync(m =>
{
m.Embed = GetEmbed(PotSize).Build();
},
new()
{
RetryMode = RetryMode.AlwaysRetry
});
}

View File

@@ -239,7 +239,7 @@ public partial class Gambling : GamblingModule<GamblingService>
var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)];
await SendConfirmAsync("🎟 " + GetText(strs.raffled_user),
$"**{usr.Username}#{usr.Discriminator}**",
$"**{usr.Username}**",
footer: $"ID: {usr.Id}");
}
@@ -258,7 +258,7 @@ public partial class Gambling : GamblingModule<GamblingService>
var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)];
await SendConfirmAsync("🎟 " + GetText(strs.raffled_user),
$"**{usr.Username}#{usr.Discriminator}**",
$"**{usr.Username}**",
footer: $"ID: {usr.Id}");
}
@@ -840,7 +840,7 @@ public partial class Gambling : GamblingModule<GamblingService>
else if (result.Result == RpsResultType.Win)
{
if ((long)result.Won > 0)
embed.AddField(GetText(strs.won), N(amount.Value));
embed.AddField(GetText(strs.won), N((long)result.Won));
msg = GetText(strs.rps_win(ctx.User.Mention,
GetRpsPick(pick),

View File

@@ -244,20 +244,33 @@ public partial class Gambling
var waifuItems = _service.GetWaifuItems().ToDictionary(x => x.ItemEmoji, x => x);
var nobody = GetText(strs.nobody);
var itemsStr = !wi.Items.Any()
var itemList = await _service.GetItems(wi.WaifuId);
var itemsStr = !itemList.Any()
? "-"
: string.Join("\n",
wi.Items.Where(x => waifuItems.TryGetValue(x.ItemEmoji, out _))
.OrderBy(x => waifuItems[x.ItemEmoji].Price)
.GroupBy(x => x.ItemEmoji)
.Select(x => $"{x.Key} x{x.Count(),-3}")
.Chunk(2)
.Select(x => string.Join(" ", x)));
itemList.Where(x => waifuItems.TryGetValue(x.ItemEmoji, out _))
.OrderByDescending(x => waifuItems[x.ItemEmoji].Price)
.GroupBy(x => x.ItemEmoji)
.Take(60)
.Select(x => $"{x.Key} x{x.Count(),-3}")
.Chunk(2)
.Select(x => string.Join(" ", x)));
var fansStr = wi.Fans.Shuffle().Take(30).Select(x => wi.Claims.Contains(x) ? $"{x} 💞" : x).Join('\n');
var claimsNames = (await _service.GetClaimNames(wi.WaifuId));
var claimsStr = claimsNames
.Shuffle()
.Take(30)
.Join('\n');
var fansList = await _service.GetFansNames(wi.WaifuId);
var fansStr = fansList
.Shuffle()
.Take(30)
.Select((x) => claimsNames.Contains(x) ? $"{x} 💞" : x)
.Join('\n');
if (string.IsNullOrWhiteSpace(fansStr))
fansStr = "-";
@@ -275,9 +288,9 @@ public partial class Gambling
.AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true)
.AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
.AddField("\u200B", "\u200B", true)
.AddField(GetText(strs.fans(wi.Fans.Count)), fansStr, true)
.AddField(GetText(strs.fans(fansList.Count)), fansStr, true)
.AddField($"Waifus ({wi.ClaimCount})",
wi.ClaimCount == 0 ? nobody : string.Join("\n", wi.Claims.Shuffle().Take(30)),
wi.ClaimCount == 0 ? nobody : claimsStr,
true)
.AddField(GetText(strs.gifts), itemsStr, true);
@@ -341,4 +354,4 @@ public partial class Gambling
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
}
}
}

View File

@@ -414,11 +414,8 @@ public class WaifuService : INService, IReadyExecutor
AffinityName = null,
ClaimCount = 0,
ClaimerName = null,
Claims = new(),
Fans = new(),
DivorceCount = 0,
FullName = null,
Items = new(),
Price = 1
};
}
@@ -426,14 +423,6 @@ public class WaifuService : INService, IReadyExecutor
return wi;
}
public async Task<WaifuInfoStats> GetFullWaifuInfoAsync(IGuildUser target)
{
await using var uow = _db.GetDbContext();
_ = uow.GetOrCreateUser(target);
return await GetFullWaifuInfoAsync(target.Id);
}
public string GetClaimTitle(int count)
{
ClaimTitle title;
@@ -557,4 +546,38 @@ public class WaifuService : INService, IReadyExecutor
}
}
}
public async Task<IReadOnlyCollection<string>> GetClaimNames(int waifuId)
{
await using var ctx = _db.GetDbContext();
return await ctx.GetTable<DiscordUser>()
.Where(x => ctx.GetTable<WaifuInfo>()
.Where(wi => wi.ClaimerId == waifuId)
.Select(wi => wi.WaifuId)
.Contains(x.Id))
.Select(x => $"{x.Username}#{x.Discriminator}")
.ToListAsyncEF();
}
public async Task<IReadOnlyCollection<string>> GetFansNames(int waifuId)
{
await using var ctx = _db.GetDbContext();
return await ctx.GetTable<DiscordUser>()
.Where(x => ctx.GetTable<WaifuInfo>()
.Where(wi => wi.AffinityId == waifuId)
.Select(wi => wi.WaifuId)
.Contains(x.Id))
.Select(x => $"{x.Username}#{x.Discriminator}")
.ToListAsyncEF();
}
public async Task<IReadOnlyCollection<WaifuItem>> GetItems(int waifuId)
{
await using var ctx = _db.GetDbContext();
return await ctx.GetTable<WaifuItem>()
.Where(x => x.WaifuInfoId == ctx.GetTable<WaifuInfo>()
.Where(x => x.WaifuId == waifuId)
.Select(x => x.Id)
.FirstOrDefault())
.ToListAsyncEF();
}
}

View File

@@ -1,6 +1,7 @@
#nullable disable
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db.Models;
using NadekoBot.Modules.Games.Common;
using NadekoBot.Modules.Games.Common.ChatterBot;
using NadekoBot.Modules.Permissions;
using NadekoBot.Modules.Permissions.Common;
@@ -27,6 +28,7 @@ public class ChatterBotService : IExecOnMessage
private readonly IHttpClientFactory _httpFactory;
private readonly IPatronageService _ps;
private readonly CmdCdService _ccs;
private readonly GamesConfigService _gcs;
public ChatterBotService(
DiscordSocketClient client,
@@ -38,7 +40,8 @@ public class ChatterBotService : IExecOnMessage
IBotCredentials creds,
IEmbedBuilderService eb,
IPatronageService ps,
CmdCdService cmdCdService)
CmdCdService cmdCdService,
GamesConfigService gcs)
{
_client = client;
_perms = perms;
@@ -49,6 +52,7 @@ public class ChatterBotService : IExecOnMessage
_httpFactory = factory;
_ps = ps;
_ccs = cmdCdService;
_gcs = gcs;
_flKey = new FeatureLimitKey()
{
@@ -64,11 +68,30 @@ public class ChatterBotService : IExecOnMessage
public IChatterBotSession CreateSession()
{
if (!string.IsNullOrWhiteSpace(_creds.CleverbotApiKey))
return new OfficialCleverbotSession(_creds.CleverbotApiKey, _httpFactory);
switch (_gcs.Data.ChatBot)
{
case ChatBotImplementation.Cleverbot:
if (!string.IsNullOrWhiteSpace(_creds.CleverbotApiKey))
return new OfficialCleverbotSession(_creds.CleverbotApiKey, _httpFactory);
Log.Information("Cleverbot will not work as the api key is missing.");
return null;
Log.Information("Cleverbot will not work as the api key is missing.");
return null;
case ChatBotImplementation.Gpt3:
if (!string.IsNullOrWhiteSpace(_creds.Gpt3ApiKey))
return new OfficialGpt3Session(_creds.Gpt3ApiKey,
_gcs.Data.ChatGpt.ModelName,
_gcs.Data.ChatGpt.ChatHistory,
_gcs.Data.ChatGpt.MaxTokens,
_gcs.Data.ChatGpt.MinTokens,
_gcs.Data.ChatGpt.PersonalityPrompt,
_client.CurrentUser.Username,
_httpFactory);
Log.Information("Gpt3 will not work as the api key is missing.");
return null;
default:
return null;
}
}
public string PrepareMessage(IUserMessage msg, out IChatterBotSession cleverbot)
@@ -102,7 +125,7 @@ public class ChatterBotService : IExecOnMessage
{
if (guild is not SocketGuild sg)
return false;
try
{
var message = PrepareMessage(usrMsg, out var cbs);
@@ -126,7 +149,7 @@ public class ChatterBotService : IExecOnMessage
Log.Information("{PermissionMessage}", returnMsg);
}
return true;
return false;
}
if (await _ccs.TryBlock(sg, usrMsg.Author, CleverBotResponseStr.CLEVERBOT_RESPONSE))
@@ -147,7 +170,7 @@ public class ChatterBotService : IExecOnMessage
uint? monthly = quota.Quota is int mVal and >= 0
? (uint)mVal
: null;
var maybeLimit = await _ps.TryIncrementQuotaCounterAsync(sg.OwnerId,
sg.OwnerId == usrMsg.Author.Id,
FeatureType.Limit,
@@ -155,7 +178,7 @@ public class ChatterBotService : IExecOnMessage
null,
daily,
monthly);
if (maybeLimit.TryPickT1(out var ql, out var counters))
{
if (ql.Quota == 0)
@@ -166,7 +189,7 @@ public class ChatterBotService : IExecOnMessage
"In order to use the cleverbot feature, the owner of this server should be [Patron Tier X](https://patreon.com/join/nadekobot) on patreon.",
footer:
"You may disable the cleverbot feature, and this message via '.cleverbot' command");
return true;
}
@@ -174,18 +197,18 @@ public class ChatterBotService : IExecOnMessage
null!,
$"You've reached your quota limit of **{ql.Quota}** responses {ql.QuotaPeriod.ToFullName()} for the cleverbot feature.",
footer: "You may wait for the quota reset or .");
return true;
}
}
_ = channel.TriggerTypingAsync();
var response = await cbs.Think(message);
var response = await cbs.Think(message, usrMsg.Author.ToString());
await channel.SendConfirmAsync(_eb,
title: null,
response.SanitizeMentions(true)
// , footer: counter > 0 ? counter.ToString() : null
);
);
Log.Information(@"CleverBot Executed
Server: {GuildName} [{GuildId}]

View File

@@ -1,8 +0,0 @@
#nullable disable
namespace NadekoBot.Modules.Games.Common.ChatterBot;
public class ChatterBotResponse
{
public string Convo_id { get; set; }
public string BotSay { get; set; }
}

View File

@@ -0,0 +1,46 @@
#nullable disable
using System.Text.Json.Serialization;
namespace NadekoBot.Modules.Games.Common.ChatterBot;
public class Gpt3Response
{
[JsonPropertyName("choices")]
public Choice[] Choices { get; set; }
}
public class Choice
{
[JsonPropertyName("message")]
public Message Message { get; init; }
}
public class Message {
[JsonPropertyName("content")]
public string Content { get; init; }
}
public class Gpt3ApiRequest
{
[JsonPropertyName("model")]
public string Model { get; init; }
[JsonPropertyName("messages")]
public List<GPTMessage> Messages { get; init; }
[JsonPropertyName("temperature")]
public int Temperature { get; init; }
[JsonPropertyName("max_tokens")]
public int MaxTokens { get; init; }
}
public class GPTMessage
{
[JsonPropertyName("role")]
public string Role {get; init;}
[JsonPropertyName("content")]
public string Content {get; init;}
[JsonPropertyName("name")]
public string Name {get; init;}
}

View File

@@ -3,5 +3,5 @@ namespace NadekoBot.Modules.Games.Common.ChatterBot;
public interface IChatterBotSession
{
Task<string> Think(string input);
Task<string> Think(string input, string username);
}

View File

@@ -18,7 +18,7 @@ public class OfficialCleverbotSession : IChatterBotSession
_httpFactory = factory;
}
public async Task<string> Think(string input)
public async Task<string> Think(string input, string username)
{
using var http = _httpFactory.CreateClient();
var dataString = await http.GetStringAsync(string.Format(QueryString, input, cs ?? ""));

View File

@@ -0,0 +1,107 @@
#nullable disable
using Newtonsoft.Json;
using System.Net.Http.Json;
using SharpToken;
using Antlr.Runtime;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace NadekoBot.Modules.Games.Common.ChatterBot;
public class OfficialGpt3Session : IChatterBotSession
{
private string Uri
=> $"https://api.openai.com/v1/chat/completions";
private readonly string _apiKey;
private readonly string _model;
private readonly int _maxHistory;
private readonly int _maxTokens;
private readonly int _minTokens;
private readonly string _nadekoUsername;
private readonly GptEncoding _encoding;
private List<GPTMessage> messages = new();
private readonly IHttpClientFactory _httpFactory;
public OfficialGpt3Session(
string apiKey,
ChatGptModel model,
int chatHistory,
int maxTokens,
int minTokens,
string personality,
string nadekoUsername,
IHttpClientFactory factory)
{
_apiKey = apiKey;
_httpFactory = factory;
switch (model)
{
case ChatGptModel.Gpt35Turbo:
_model = "gpt-3.5-turbo";
break;
case ChatGptModel.Gpt4:
_model = "gpt-4";
break;
case ChatGptModel.Gpt432k:
_model = "gpt-4-32k";
break;
}
_maxHistory = chatHistory;
_maxTokens = maxTokens;
_minTokens = minTokens;
_nadekoUsername = nadekoUsername;
_encoding = GptEncoding.GetEncodingForModel(_model);
messages.Add(new GPTMessage(){Role = "user", Content = personality, Name = _nadekoUsername});
}
public async Task<string> Think(string input, string username)
{
messages.Add(new GPTMessage(){Role = "user", Content = input, Name = username});
while(messages.Count > _maxHistory + 2){
messages.RemoveAt(1);
}
int tokensUsed = 0;
foreach(GPTMessage message in messages){
tokensUsed += _encoding.Encode(message.Content).Count;
}
tokensUsed *= 2; //Unsure why this is the case, but the token count chatgpt reports back is double what I calculate.
//check if we have the minimum number of tokens available to use. Remove messages until we have enough, otherwise exit out and inform the user why.
while(_maxTokens - tokensUsed <= _minTokens){
if(messages.Count > 2){
int tokens = _encoding.Encode(messages[1].Content).Count * 2;
tokensUsed -= tokens;
messages.RemoveAt(1);
}
else{
return "Token count exceeded, please increase the number of tokens in the bot config and restart.";
}
}
using var http = _httpFactory.CreateClient();
http.DefaultRequestHeaders.Authorization = new("Bearer", _apiKey);
var data = await http.PostAsJsonAsync(Uri, new Gpt3ApiRequest()
{
Model = _model,
Messages = messages,
MaxTokens = _maxTokens - tokensUsed,
Temperature = 1,
});
var dataString = await data.Content.ReadAsStringAsync();
try
{
var response = JsonConvert.DeserializeObject<Gpt3Response>(dataString);
string message = response?.Choices[0]?.Message?.Content;
//Can't rely on the return to except, now that we need to add it to the messages list.
_ = message ?? throw new ArgumentNullException(nameof(message));
messages.Add(new GPTMessage(){Role = "assistant", Content = message, Name = _nadekoUsername});
return message;
}
catch
{
Log.Warning("Unexpected GPT-3 response received: {ResponseString}", dataString);
return null;
}
}
}

View File

@@ -8,7 +8,7 @@ namespace NadekoBot.Modules.Games.Common;
public sealed partial class GamesConfig : ICloneable<GamesConfig>
{
[Comment("DO NOT CHANGE")]
public int Version { get; set; }
public int Version { get; set; } = 3;
[Comment("Hangman related settings (.hangman command)")]
public HangmanConfig Hangman { get; set; } = new()
@@ -95,6 +95,35 @@ public sealed partial class GamesConfig : ICloneable<GamesConfig>
Name = "Unicorn"
}
};
[Comment(@"Which chatbot API should bot use.
'cleverbot' - bot will use Cleverbot API.
'gpt3' - bot will use GPT-3 API")]
public ChatBotImplementation ChatBot { get; set; } = ChatBotImplementation.Gpt3;
public ChatGptConfig ChatGpt { get; set; } = new();
}
[Cloneable]
public sealed partial class ChatGptConfig
{
[Comment(@"Which GPT-3 Model should bot use.
gpt35turbo - cheapest
gpt4 - 30x more expensive, higher quality
gp432k - same model as above, but with a 32k token limit")]
public ChatGptModel ModelName { get; set; } = ChatGptModel.Gpt35Turbo;
[Comment(@"How should the chat bot behave, what's its personality? (Usage of this counts towards the max tokens)")]
public string PersonalityPrompt { get; set; } = "You are a chat bot willing to have a conversation with anyone about anything.";
[Comment(@"The maximum number of messages in a conversation that can be remembered. (This will increase the number of tokens used)")]
public int ChatHistory { get; set; } = 5;
[Comment(@"The maximum number of tokens to use per GPT-3 API call")]
public int MaxTokens { get; set; } = 100;
[Comment(@"The minimum number of tokens to use per GPT-3 API call, such that chat history is removed to make room.")]
public int MinTokens { get; set; } = 30;
}
[Cloneable]
@@ -120,4 +149,17 @@ public sealed partial class RaceAnimal
{
public string Icon { get; set; }
public string Name { get; set; }
}
public enum ChatBotImplementation
{
Cleverbot,
Gpt3
}
public enum ChatGptModel
{
Gpt35Turbo,
Gpt4,
Gpt432k
}

View File

@@ -28,6 +28,33 @@ public sealed class GamesConfigService : ConfigServiceBase<GamesConfig>
long.TryParse,
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("chatbot",
gs => gs.ChatBot,
ConfigParsers.InsensitiveEnum,
ConfigPrinters.ToString);
AddParsedProp("gpt.modelName",
gs => gs.ChatGpt.ModelName,
ConfigParsers.InsensitiveEnum,
ConfigPrinters.ToString);
AddParsedProp("gpt.personality",
gs => gs.ChatGpt.PersonalityPrompt,
ConfigParsers.String,
ConfigPrinters.ToString);
AddParsedProp("gpt.chathistory",
gs => gs.ChatGpt.ChatHistory,
int.TryParse,
ConfigPrinters.ToString,
val => val > 0);
AddParsedProp("gpt.max_tokens",
gs => gs.ChatGpt.MaxTokens,
int.TryParse,
ConfigPrinters.ToString,
val => val > 0);
AddParsedProp("gpt.min_tokens",
gs => gs.ChatGpt.MinTokens,
int.TryParse,
ConfigPrinters.ToString,
val => val > 0);
Migrate();
}
@@ -45,5 +72,23 @@ public sealed class GamesConfigService : ConfigServiceBase<GamesConfig>
};
});
}
if (data.Version < 2)
{
ModifyConfig(c =>
{
c.Version = 2;
c.ChatBot = ChatBotImplementation.Cleverbot;
});
}
if (data.Version < 3)
{
ModifyConfig(c =>
{
c.Version = 3;
c.ChatGpt.ModelName = ChatGptModel.Gpt35Turbo;
});
}
}
}

Some files were not shown because too many files have changed in this diff Show More