mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 01:38:27 -04:00
Compare commits
276 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f82c4c7019 | ||
|
03367c5ec4 | ||
|
01cc6e52d5 | ||
|
c903bc9003 | ||
|
323699d103 | ||
|
63ced029ab | ||
|
0aa8c20b75 | ||
|
578b7fefb4 | ||
|
80800673b9 | ||
|
f5a706f57a | ||
|
18efdc004d | ||
|
0fa2b7d171 | ||
|
bb90cdc3c9 | ||
|
c89a5789e6 | ||
|
ad2c0033df | ||
|
0f9037b228 | ||
|
4fc377686b | ||
|
f61ff27ca3 | ||
|
06c90fee0b | ||
|
6fbfe30cc5 | ||
|
45a898b66d | ||
|
27cbb6be78 | ||
|
2105c86111 | ||
|
1929c6b338 | ||
|
e0e044278e | ||
|
60e0729988 | ||
|
8c1c75c246 | ||
|
4b722c815f | ||
|
e6e802b563 | ||
|
7ed1b13e85 | ||
|
6895c8a2a4 | ||
|
f250cac8d5 | ||
|
09800cb8a3 | ||
|
0c1ccfacf8 | ||
|
d853151053 | ||
|
94e594ae8b | ||
|
46453f5f08 | ||
|
e194155689 | ||
|
dd5bc0eeda | ||
|
1622eb05c9 | ||
|
0008eabdd2 | ||
|
22eabff276 | ||
|
d42036845a | ||
|
3d1f9b8b75 | ||
|
73555ff70e | ||
|
a8960e8769 | ||
|
eda38e64d1 | ||
|
f77f2f433f | ||
|
eecccc8100 | ||
|
15ee3dd638 | ||
|
41653a317b | ||
|
b4a493971a | ||
|
ffa2c3f119 | ||
|
b22cd5a81e | ||
|
7ee51332b0 | ||
|
d31cfcc5a8 | ||
|
2d90ecaa51 | ||
|
3e0bbd8ada | ||
|
0f36242597 | ||
|
cd812304f7 | ||
|
3910dc7499 | ||
|
81533b7f69 | ||
|
02e59bd5a5 | ||
|
a402f33a4c | ||
|
c6b0d75fd7 | ||
|
0c88dd84cf | ||
|
0ef2da6f10 | ||
|
80e3507841 | ||
|
df5a7ecc47 | ||
|
7a8fbb418b | ||
|
0620133c5e | ||
|
4ac7d329fd | ||
|
4c2f7dde5f | ||
|
a70b58332f | ||
|
fa41c5a319 | ||
|
b41571a7fd | ||
|
9d2d2fcca5 | ||
|
3c90dc239e | ||
|
1d27b4e7e8 | ||
|
8ace24f0fc | ||
|
f9fff5a27e | ||
|
63d1741534 | ||
|
9b10094ea0 | ||
|
9de75d9109 | ||
|
73901158ab | ||
|
b044eb2d9a | ||
|
c58ae9fc13 | ||
|
17cdd6f893 | ||
|
f07a855912 | ||
|
2ce3262d59 | ||
|
a6330119e8 | ||
|
73de20b615 | ||
|
352ec9a562 | ||
|
f1770cbb8f | ||
|
51a396ec9f | ||
|
ac6e0c2f84 | ||
|
7b84d6363c | ||
|
e1fca41a70 | ||
|
e9f42bf4df | ||
|
6c39044435 | ||
|
f13d7d2c80 | ||
|
3aa6a54b6e | ||
|
ef49030841 | ||
|
ade880a6e6 | ||
|
21bef1a98e | ||
|
df3e60b61f | ||
|
4e60ea4241 | ||
|
ca9fa1b0ac | ||
|
2edda76218 | ||
|
6322e0e077 | ||
|
25f249ab5e | ||
|
3428073208 | ||
|
4b6af0e4ef | ||
|
9c590668df | ||
|
9b4eb21321 | ||
|
f81f9fadd3 | ||
|
8c6fcd2ce6 | ||
|
25eeffa163 | ||
|
6eee161b6b | ||
|
82000c97a4 | ||
|
723447c7d4 | ||
|
d093f7eed7 | ||
|
0acd2931eb | ||
|
7b6539632c | ||
|
44104bb0e4 | ||
|
59f5056035 | ||
|
0634470a8a | ||
|
89c2cda9ec | ||
|
1b0392dfab | ||
|
9ae030a5c5 | ||
|
d5fd6aae8e | ||
|
b85ba177cd | ||
|
d18f9429c6 | ||
|
68741ec484 | ||
|
77bbc5ef7a | ||
|
26ee6ce4d3 | ||
|
594a3b1f97 | ||
|
856dcd048a | ||
|
643dc1824f | ||
|
da849f7c7b | ||
|
93b8bca018 | ||
|
f78e4d457c | ||
|
0b8c3b3f2b | ||
|
c66e491ce9 | ||
|
52b2c0910c | ||
|
9a4bb7bff9 | ||
|
ab5450a125 | ||
|
bcce32423c | ||
|
c42d529016 | ||
|
cbea5077be | ||
|
9223d78849 | ||
|
edd60ae656 | ||
|
da2ee0c158 | ||
|
1b2017024c | ||
|
345a9e9524 | ||
|
cd379fd308 | ||
|
ee33313519 | ||
|
bc31dae965 | ||
|
cdc2cc1439 | ||
|
87819f21bf | ||
|
d1be56fbc1 | ||
|
14016a761d | ||
|
12a64c4c4d | ||
|
d922120f58 | ||
|
8e8e349e65 | ||
|
ccdf0fc077 | ||
|
8c66bcb1e1 | ||
|
77fb47183f | ||
|
d275dc36b2 | ||
|
7bff20cc70 | ||
|
29f5dcc359 | ||
|
14f2851072 | ||
|
a2b25f8246 | ||
|
a38951b5ad | ||
|
4c1b911cb7 | ||
|
6c9f231453 | ||
|
83daf3c30f | ||
|
9be8140d4d | ||
|
96c9b699aa | ||
|
3c0768a372 | ||
|
58b22e3d9e | ||
|
0474551e2f | ||
|
c85bdec396 | ||
|
5fa39eaa9f | ||
|
8eaaa35c7a | ||
|
1c24f95efa | ||
|
fcc49dbbdb | ||
|
3a317590b4 | ||
|
36c013fbb5 | ||
|
a9ec8049f6 | ||
|
ced0d97e3a | ||
|
24d0f57dc3 | ||
|
514ecd6be8 | ||
|
02eb6e172b | ||
|
d22c579875 | ||
|
f70e49fc6a | ||
|
8b410561f9 | ||
|
3c79fd1d6f | ||
|
e9f1a9b1dd | ||
|
3c293ae6db | ||
|
a5d9e7de66 | ||
|
4d9e48cd41 | ||
|
b7ead22e09 | ||
|
9f219cddbb | ||
|
cf9792f24a | ||
|
0187dd57ac | ||
|
2d2e54e31e | ||
|
cb7c5e48fd | ||
|
771c2745dc | ||
|
59c0f2f4b3 | ||
|
219ca39cd1 | ||
|
1e6d0806d7 | ||
|
71f1e43272 | ||
|
8499e1da70 | ||
|
a2ea806bed | ||
|
732b5dfeed | ||
|
d4dcdc761a | ||
|
57996ba290 | ||
|
4b29b3a239 | ||
|
54ac955395 | ||
|
f4fa298866 | ||
|
b2fafc964f | ||
|
22b452e449 | ||
|
fda385a5e4 | ||
|
c28f7cfa07 | ||
|
0a029a7847 | ||
|
c050ce2123 | ||
|
27613410dd | ||
|
1513008b4b | ||
|
bf97cffd84 | ||
|
e37d1c46db | ||
|
06c20c6fa4 | ||
|
aa518d60a5 | ||
|
d55ce7accc | ||
|
502c5cec07 | ||
|
ee5c13607b | ||
|
5a681a5194 | ||
|
68395372f0 | ||
|
c8e01bd158 | ||
|
1d57191700 | ||
|
02c7ded457 | ||
|
12c483d222 | ||
|
c80898a7bf | ||
|
aae2805785 | ||
|
fc3695d090 | ||
|
428429ff44 | ||
|
dc344caec6 | ||
|
2a4d55f81d | ||
|
d090aa23ee | ||
|
65062306c6 | ||
|
9ae3b66fc2 | ||
|
c4ba43ec6d | ||
|
1141791ce5 | ||
|
49f1ef7db0 | ||
|
a70c35e101 | ||
|
717543f6c2 | ||
|
b61b1dbfaa | ||
|
92365fd22d | ||
|
24a4745193 | ||
|
1af75fd813 | ||
|
18160164eb | ||
|
2fd7d97025 | ||
|
6ada15049d | ||
|
0ebc40b95c | ||
|
02de25a931 | ||
|
0b395e9176 | ||
|
4532f992cd | ||
|
34201f0558 | ||
|
d2f4d63183 | ||
|
b41c014869 | ||
|
d348347762 | ||
|
db7cf3d757 | ||
|
83ea046d5f | ||
|
d5c94424e9 | ||
|
ff95b3d00f | ||
|
4b5fa3bb04 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,7 +8,7 @@ src/NadekoBot/creds.yml
|
|||||||
src/NadekoBot/Command Errors*.txt
|
src/NadekoBot/Command Errors*.txt
|
||||||
|
|
||||||
src/NadekoBot/creds.yml
|
src/NadekoBot/creds.yml
|
||||||
# credentials file before and after migrations
|
# credentials file before and after v3
|
||||||
src/NadekoBot/credentials.json
|
src/NadekoBot/credentials.json
|
||||||
src/NadekoBot/old_credentials.json
|
src/NadekoBot/old_credentials.json
|
||||||
src/NadekoBot/credentials.json.bak
|
src/NadekoBot/credentials.json.bak
|
||||||
@@ -256,7 +256,7 @@ PublishScripts/
|
|||||||
!**/packages/build/
|
!**/packages/build/
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
#!**/packages/repositories.config
|
#!**/packages/repositories.config
|
||||||
# NuGet v3's project.json files produces more ignoreable files
|
# NuGet v4's project.json files produces more ignoreable files
|
||||||
*.nuget.props
|
*.nuget.props
|
||||||
*.nuget.targets
|
*.nuget.targets
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
image: mcr.microsoft.com/dotnet/sdk:5.0
|
image: mcr.microsoft.com/dotnet/sdk:6.0
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
@@ -50,7 +50,7 @@ release:
|
|||||||
- if: $CI_COMMIT_TAG
|
- if: $CI_COMMIT_TAG
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
release-cli create --name "NadekoBot v$CI_COMMIT_TAG" --description "## [Changelog](https://gitlab.com/Kwoth/nadekobot/-/blob/v3/CHANGELOG.md#$(echo "$CI_COMMIT_TAG" | sed "s/\.//g")-$(date +%d%m%Y))" --tag-name $CI_COMMIT_TAG \
|
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\":\"${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\":\"${WIN_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${WIN_X64_RELEASE}\"}"
|
||||||
|
|
||||||
@@ -68,8 +68,8 @@ publish-windows:
|
|||||||
- if: '$CI_COMMIT_TAG'
|
- if: '$CI_COMMIT_TAG'
|
||||||
image: scottyhardy/docker-wine
|
image: scottyhardy/docker-wine
|
||||||
before_script:
|
before_script:
|
||||||
- choco install dotnet-5.0-runtime -y
|
- choco install dotnet-6.0-runtime -y
|
||||||
- choco install dotnet-5.0-sdk -y
|
- choco install dotnet-6.0-sdk -y
|
||||||
- choco install innosetup -y
|
- choco install innosetup -y
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
@@ -97,29 +97,29 @@ upload-windows-updater-release:
|
|||||||
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/$INSTALLER_FILE_NAME" --acl public-read --body "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/$INSTALLER_FILE_NAME" --acl public-read --body "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
||||||
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/releases-v3.json" --acl public-read --body "releases-v3.json"
|
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/releases-v3.json" --acl public-read --body "releases-v3.json"
|
||||||
|
|
||||||
docker-build:
|
# docker-build:
|
||||||
# Use the official docker image.
|
# # Use the official docker image.
|
||||||
image: docker:latest
|
# image: docker:latest
|
||||||
stage: build
|
# stage: build
|
||||||
services:
|
# services:
|
||||||
- docker:dind
|
# - docker:dind
|
||||||
before_script:
|
# before_script:
|
||||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
# - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||||
# Default branch leaves tag empty (= latest tag)
|
# # Default branch leaves tag empty (= latest tag)
|
||||||
# All other branches are tagged with the escaped branch name (commit ref slug)
|
# # All other branches are tagged with the escaped branch name (commit ref slug)
|
||||||
script:
|
# script:
|
||||||
- |
|
# - |
|
||||||
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
|
# if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
|
||||||
tag=""
|
# tag=""
|
||||||
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
# echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
||||||
else
|
# else
|
||||||
tag=":$CI_COMMIT_REF_SLUG"
|
# tag=":$CI_COMMIT_REF_SLUG"
|
||||||
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
# echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
||||||
fi
|
# fi
|
||||||
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
|
# - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
|
||||||
- docker push "$CI_REGISTRY_IMAGE${tag}"
|
# - docker push "$CI_REGISTRY_IMAGE${tag}"
|
||||||
# Run this job in a branch where a Dockerfile exists
|
# # Run this job in a branch where a Dockerfile exists
|
||||||
rules:
|
# rules:
|
||||||
- if: $CI_COMMIT_BRANCH
|
# - if: $CI_COMMIT_BRANCH
|
||||||
exists:
|
# exists:
|
||||||
- Dockerfile
|
# - Dockerfile
|
||||||
|
168
CHANGELOG.md
168
CHANGELOG.md
@@ -1,9 +1,177 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||||
|
#todo .trans fix
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- More cool stuff coming soon
|
||||||
|
|
||||||
|
## [4.0.1] - 03.03.2022
|
||||||
|
|
||||||
|
- Added `usePrivilegedIntents` to creds.yml if you don't have or don't want (?) to use them
|
||||||
|
- Added a human-readable, detailed error message if logging in fails due to missing privileged intents
|
||||||
|
|
||||||
|
## [4.0.0] - 02.03.2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added `.deleteemptyservers` command
|
||||||
|
- Added `.curtr <id>` which lets you see full information about one of your own transactions with the specified id
|
||||||
|
- Added trovo.live support for stream notifications (`.stadd`)
|
||||||
|
- Added unclaimed waifu decay functionality
|
||||||
|
- Added 3 new settings to `data/gambling.yml` to control it:
|
||||||
|
- waifu.decay.percent - How much % to subtract from unclaimed waifu
|
||||||
|
- waifu.decay.hourInterval - How often to decay the price
|
||||||
|
- waifu.decay.minPrice - Unclaimed waifus with price lower than the one specified here will not be affected by the decay
|
||||||
|
- Added `currency.transactionsLifetime` to `data/gambling.yml` Any transaction older than the number of days specified will be automatically deleted
|
||||||
|
- Added `.stock` command to check stock prices and charts
|
||||||
|
- Re-added `.qap / .queueautoplay`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- CustomReactions module (and customreactions db table) has been renamed to Expressions.
|
||||||
|
- This was done to remove confusion about how it relates to discord Reactions (it doesn't, it was created and named before discord reactions existed)
|
||||||
|
- Expression command now start with ex/expr and end with the name of the action or setting.
|
||||||
|
- For example `.exd` (`.dcr`) is expression delete, `.exa` (`.acr`)
|
||||||
|
- Permissions (`.lp`) be automatically updated with "ACTUALEXPRESSIONS", "EXPRESSIONS" instead of "ACTUALCUSTOMREACTIONS" and "CUSTOMREACTIONS"
|
||||||
|
- Permissions for `.ecr` (now `.exe`), `.scr` (now `.exs`), `.dcr` (now `.exd`), `.acr` (now `.exa`), `.lcr` (now `.exl`) will be automatically updated
|
||||||
|
- If you have custom permissions for other CustomReaction commands
|
||||||
|
- Some of the old aliases like `.acr` `.dcr` `.lcr` and a few others have been kept
|
||||||
|
- Currency output format improvement (will use guild locale now for some commands)
|
||||||
|
- `.crypto` will now also show CoinMarketCap rank
|
||||||
|
- Waifus can now be claimed for much higher prices (int -> long)
|
||||||
|
- Several strings and commands related to music have been changed
|
||||||
|
- Changed `.ms / .movesong` to `.tm / .trackmove` but kept old aliases
|
||||||
|
- Changed ~~song~~ -> `track` throughout music module strings
|
||||||
|
- Improved .curtrs (It will now have a lot more useful data in the database, show Tx ids, and be partially localized)
|
||||||
|
- [dev] Reason renamed to Note
|
||||||
|
- [dev] Added Type, Extra, OtherId fields to the database
|
||||||
|
- [dev] CommandStrings will now use methodname as the key, and **not** the command name (first entry in aliases.yml)
|
||||||
|
- In other words aliases.yml and commands.en-US.yml will use the same keys (once again)
|
||||||
|
- [dev] Reorganized module and submodule folders
|
||||||
|
- [dev] Permissionv2 db table renamed to Permissions
|
||||||
|
- [dev] Moved FilterWordsChannelId to a separate table
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed an extra whitespace in usage part of command help if the command has no arguments
|
||||||
|
- Possible small fix for `.prune` ratelimiting
|
||||||
|
- `.gvc` should now properly trigger when a user is already in a gvc and changes his activity
|
||||||
|
- `.gvc` should now properly detect multiple activities
|
||||||
|
- Fixed reference to non-existent command in bot.yml
|
||||||
|
- Comment indentation in .yml files should now make more sense
|
||||||
|
- Fixed `.warn` punishments not being applied properly when using weighted warnings
|
||||||
|
- Fixed embed color when disabling `.antialt`
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removed `.bce` - use `.config` or `.config bot` specifically for bot config
|
||||||
|
- Removed obsolete placeholders: %users% %servers% %userfull% %username% %userdiscrim% %useravatar% %id% %uid% %chname% %cid% %sid% %members% %server_time% %shardid% %time% %mention%
|
||||||
|
- Removed some obsolete commands and strings
|
||||||
|
- Removed code which migrated 2.x to v3 credentials, settings, etc...
|
||||||
|
|
||||||
|
## [3.0.13] - 14.01.2022
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `.greetdm` causing ratelimits during raids
|
||||||
|
- Fixed `.gelbooru`
|
||||||
|
|
||||||
|
## [3.0.12] - 06.01.2022
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `.smch` Fixed
|
||||||
|
- `.trans` command will now work properly with capitilized language names
|
||||||
|
- Ban message color with plain text fixed
|
||||||
|
- Fixed some grpc coordinator bugs
|
||||||
|
- Fixed a string in `.xpex`
|
||||||
|
- Google version of .img will now have safe search enabled
|
||||||
|
- Fixed a small bug in `.hangman`
|
||||||
|
|
||||||
|
## [3.0.11] - 17.12.2021
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `.remindl` and `.remindrm` commands now supports optional 'server' parameter for Administrators which allows them to delete any reminder created on the server
|
||||||
|
- Added slots.currencyFontColor to gambling.yml
|
||||||
|
- Added `.qexport` and `.qimport` commands which allow you to export and import quotes just like `.crsexport`
|
||||||
|
- Added `.showembed <msgid>` and `.showembed #channel <msgid>` which will show you embed json from the specified message
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `.at` and `.atl` commands reworked
|
||||||
|
- Persist restarts
|
||||||
|
- Will now only translate non-commands
|
||||||
|
- You can switch between `.at del` and `.at` without clearing the user language registrations
|
||||||
|
- Disabling `.at` will clear all user language registrations on that channel
|
||||||
|
- Users can't register languages if the `.at` is not enabled
|
||||||
|
- Looks much nicer
|
||||||
|
- Bot will now reply to user messages with a translation if `del` is disabled
|
||||||
|
- Bot will make an embed with original and translated text with user avatar and name if `del` is enabled
|
||||||
|
- If the bot is unable to delete messages while having `del` enabled, it will reset back to the no-del behavior for the current session
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `.crypto` now supports top 5000 coins
|
||||||
|
|
||||||
|
## [3.0.10] - 01.12.2021
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `.warn` now supports weighted warnings
|
||||||
|
- `.warnlog` will now show current amount and total amount of warnings
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `.xprewsreset` now has correct permissions
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removed slot.numbers from `images.yml` as they're no longer used
|
||||||
|
|
||||||
|
## [3.0.9] - 21.11.2021
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `.ea` will now use an image attachments if you omit imageUrl
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added `.emojiadd` with 3 overloads
|
||||||
|
- `.ea :customEmoji:` which copies another server's emoji
|
||||||
|
- `.ea newName :customEmoji:` which copies emoji under a different name
|
||||||
|
- `.ea emojiName <imagelink.png>` which creates a new emoji from the specified image
|
||||||
|
- Patreon Access and Refresh Tokens should now be automatically updated once a month as long as the user has provided the necessary credentials in creds.yml file:
|
||||||
|
- `Patreon.ClientId`
|
||||||
|
- `Patreon.RefreshToken` (will also get updated once a month but needs an initial value)
|
||||||
|
- `Patreon.ClientSecret`
|
||||||
|
- `Patreon.CampaignId`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed an error that would show up in the console when a club image couldn't be drawn in certain circumstances
|
||||||
|
|
||||||
|
## [3.0.8] - 03.11.2021
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Created VotesApi project nad re-worked vote rewards handling
|
||||||
|
- Updated votes entries in creds.yml with explanations on how to set up vote links
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed adding currency to users who don't exist in the database
|
||||||
|
- Memory used by the bot is now correct (thanks to kotz)
|
||||||
|
- Ban/kick will no longer fail due to too long reasons
|
||||||
|
- Fixed some fields not preserving inline after string replacements
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `images.json` moved to `images.yml`
|
||||||
|
- Links will use the new cdn url
|
||||||
|
- Heads and Tails images will be updated if you haven't changed them already
|
||||||
|
- `.slot` redesigned (and updated entries in `images.yml`)
|
||||||
|
- Reduced required permissions for .qdel (thanks to tbodt)
|
||||||
|
|
||||||
|
## [3.0.7] - 05.10.2021
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `.streamsclear` re-added. It will remove all followed streams on the server.
|
||||||
|
- `.gifts` now have 3 new ✂️ Haircut 🧻 ToiletPaper and 🥀 WiltedRose which **reduce** waifu's value
|
||||||
|
- They are called negative gifts
|
||||||
|
- They show up at the end of the `.gifts` page and are marked with a broken heart
|
||||||
|
- They have a separate multiplier (`waifu.multi.negative_gift_effect` default 0.5, changeable via `.config gambling` or `data/gambling.yml`)
|
||||||
|
- When gifted, the waifu's price will be reduced by the `price * multiplier`
|
||||||
|
- Negative gifts don't show up in `.waifuinfo` nor is the record of them kept in the database
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed `%users%` and `%shard.usercount%` placeholders not showing correct values
|
||||||
|
|
||||||
## [3.0.6] - 27.09.2021
|
## [3.0.6] - 27.09.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
WORKDIR /source
|
WORKDIR /source
|
||||||
|
|
||||||
COPY src/NadekoBot/*.csproj src/NadekoBot/
|
COPY src/NadekoBot/*.csproj src/NadekoBot/
|
||||||
@@ -18,16 +18,17 @@ RUN set -xe; \
|
|||||||
chmod +x /app/NadekoBot
|
chmod +x /app/NadekoBot
|
||||||
|
|
||||||
# final stage/image
|
# final stage/image
|
||||||
FROM mcr.microsoft.com/dotnet/runtime:5.0-buster-slim
|
FROM mcr.microsoft.com/dotnet/runtime:6.0
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN set -xe; \
|
RUN set -xe; \
|
||||||
useradd -m nadeko; \
|
useradd -m nadeko; \
|
||||||
apt-get update; \
|
apt-get update; \
|
||||||
apt-get install -y libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 sudo; \
|
apt-get install -y libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 python3-pip sudo; \
|
||||||
update-alternatives --install /usr/bin/python python /usr/bin/python3.7 1; \
|
update-alternatives --install /usr/bin/python python /usr/bin/python3.7 1; \
|
||||||
echo 'Defaults>nadeko env_keep+="ASPNETCORE_* DOTNET_* NadekoBot_* shard_id total_shards TZ"' > /etc/sudoers.d/nadeko; \
|
echo 'Defaults>nadeko env_keep+="ASPNETCORE_* DOTNET_* NadekoBot_* shard_id total_shards TZ"' > /etc/sudoers.d/nadeko; \
|
||||||
curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl; \
|
pip3 install --upgrade youtube-dl; \
|
||||||
|
apt-get remove -y python3-pip; \
|
||||||
chmod +x /usr/local/bin/youtube-dl
|
chmod +x /usr/local/bin/youtube-dl
|
||||||
|
|
||||||
COPY --from=build /app ./
|
COPY --from=build /app ./
|
||||||
|
@@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Coordinator", "sr
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Generators", "src\NadekoBot.Generators\NadekoBot.Generators.csproj", "{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Generators", "src\NadekoBot.Generators\NadekoBot.Generators.csproj", "{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.VotesApi", "src\NadekoBot.VotesApi\NadekoBot.VotesApi.csproj", "{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -62,6 +64,12 @@ Global
|
|||||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -73,6 +81,7 @@ Global
|
|||||||
{DB448DD4-C97F-40E9-8BD3-F605FF1FF833} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{DB448DD4-C97F-40E9-8BD3-F605FF1FF833} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
{AE9B7F8C-81D7-4401-83A3-643B38258374} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{AE9B7F8C-81D7-4401-83A3-643B38258374} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
|
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<packageSources>
|
|
||||||
<add key="Discord.Net" value="https://www.myget.org/F/discord-net/api/v3/index.json" />
|
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
|
||||||
<add key="Kwoth-myget" value="https://www.myget.org/F/kwoth/api/v3/index.json" />
|
|
||||||
</packageSources>
|
|
||||||
</configuration>
|
|
@@ -1,5 +1,5 @@
|
|||||||
[](https://discord.gg/nadekobot)
|
[](https://discord.gg/nadekobot)
|
||||||
[](http://nadekobot.readthedocs.io/en/v3/?badge=v3)
|
[](http://nadekobot.readthedocs.io/en/v4/?badge=v4)
|
||||||
[](https://top.gg/bot/116275390695079945)
|
[](https://top.gg/bot/116275390695079945)
|
||||||
|
|
||||||
|
|
||||||
@@ -10,6 +10,5 @@
|
|||||||
[](https://nadeko.bot/commands)
|
[](https://nadeko.bot/commands)
|
||||||
|
|
||||||
### Useful links
|
### Useful links
|
||||||
- ❗ [2.x to v3 migration guide](https://nadekobot.readthedocs.io/en/v3/guides/migration-guide/)
|
- [Self hosting Guides and Docs](https://nadekobot.readthedocs.io/en/v4)
|
||||||
- [Self hosting Guides and Docs](https://nadekobot.readthedocs.io/en/v3)
|
|
||||||
- [Discord support server](https://discord.nadeko.bot)
|
- [Discord support server](https://discord.nadeko.bot)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# How to contribute
|
# How to contribute
|
||||||
|
|
||||||
1. Make Merge Requests to the [**v3 branch**](https://gitlab.com/Kwoth/nadekobot/tree/v3)
|
1. Make Merge Requests to the [**v4 branch**](https://gitlab.com/Kwoth/nadekobot/tree/v4)
|
||||||
2. Keep a single Merge Request to a single feature
|
2. Keep a single Merge Request to a single feature
|
||||||
3. Fill out the MR template
|
3. Fill out the MR template
|
||||||
|
|
||||||
|
@@ -13,8 +13,13 @@ This document aims to guide you through the process of creating a Discord accoun
|
|||||||
- Click on the `Add a Bot` button and confirm that you do want to add a bot to this app.
|
- Click on the `Add a Bot` button and confirm that you do want to add a bot to this app.
|
||||||
- **Optional:** Add bot's avatar and description.
|
- **Optional:** Add bot's avatar and description.
|
||||||
- Copy your Token to `creds.yml` as shown above.
|
- Copy your Token to `creds.yml` as shown above.
|
||||||
- Scroll down to the `Privileged Gateway Intents` section and enable both intents.
|
- Scroll down to the **`Privileged Gateway Intents`** section
|
||||||
These are required for a number of features to function properly, and should both be on.
|
- **Enable the following:**
|
||||||
|
- **PRESENCE INTENT**
|
||||||
|
- **SERVER MEMBERS INTENT**
|
||||||
|
- **MESSAGE CONTENT INTENT**
|
||||||
|
|
||||||
|
These are required for a number of features to function properly, and all should be on.
|
||||||
|
|
||||||
##### Getting Owner ID*(s)*:
|
##### Getting Owner ID*(s)*:
|
||||||
|
|
||||||
@@ -32,7 +37,7 @@ For a single owner, it should look like this:
|
|||||||
- 105635576866156544
|
- 105635576866156544
|
||||||
```
|
```
|
||||||
|
|
||||||
For multiple owners, it should look like this (pay attention to the commas, the last ID should **never** have a comma next to it):
|
For multiple owners, it should look like this:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
OwnerIds:
|
OwnerIds:
|
||||||
|
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
# DO NOT USE YET - WORK IN PROGRESS
|
# DO NOT USE YET - WORK IN PROGRESS
|
||||||
|
|
||||||
Upgrade from 2.x to v3 does not work because the file is mount readonly
|
|
||||||
|
|
||||||
### Docker Compose
|
### Docker Compose
|
||||||
```yml
|
```yml
|
||||||
version: "3.7"
|
version: "3.7"
|
||||||
|
@@ -1,8 +1,28 @@
|
|||||||
## Migration from 2.x
|
# Setting up NadekoBot on Linux
|
||||||
|
|
||||||
##### ⚠ If you're already hosting NadekoBot, _You **MUST** update to latest version of 2.x and **run your bot at least once**_ before switching over to v3.
|
| Table of Contents |
|
||||||
|
| :-------------------------------------------------- |
|
||||||
|
| [Linux From Source] |
|
||||||
|
| [Source Update Instructions] |
|
||||||
|
| [Linux Release] |
|
||||||
|
| [Release Update Instructions] |
|
||||||
|
| [Tmux (Preferred Method)] |
|
||||||
|
| [Systemd] |
|
||||||
|
| [Systemd + Script] |
|
||||||
|
| [Setting up Nadeko on a VPS (Digital Ocean)] |
|
||||||
|
|
||||||
#### [Linux migration instructions](../migration-guide/#linux)
|
#### Operating System Compatibility
|
||||||
|
|
||||||
|
It is recommended that you use **Ubuntu 20.04**, as there have been nearly no problems with it. Also, **32-bit systems are incompatible**.
|
||||||
|
|
||||||
|
##### Compatible operating systems:
|
||||||
|
|
||||||
|
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10
|
||||||
|
- Mint: 19, 20
|
||||||
|
- Debian: 9, 10
|
||||||
|
- CentOS: 7
|
||||||
|
- openSUSE
|
||||||
|
- Fedora: 33, 34, 35
|
||||||
|
|
||||||
## Linux From Source
|
## Linux From Source
|
||||||
|
|
||||||
@@ -13,20 +33,20 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
|||||||
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
|
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
|
||||||
2. Install prerequisites (type `1` and press enter)
|
2. Install prerequisites (type `1` and press enter)
|
||||||
3. Download the bot (type `2` and press enter)
|
3. Download the bot (type `2` and press enter)
|
||||||
4. Exit the installer in order to set up your `creds.yml`
|
4. Exit the installer (type `5` and press enter)
|
||||||
5. Copy the creds.yml template `cp nadekobot/output/creds_example.yml nadekobot/output/creds.yml`
|
5. Copy the creds.yml template `cp nadekobot/output/creds_example.yml nadekobot/output/creds.yml`
|
||||||
6. Open `nadekobot/output/creds.yml` with your favorite text editor. We will use nano here
|
6. Open `nadekobot/output/creds.yml` with your favorite text editor. We will use nano here
|
||||||
- `nano nadekobot/output/creds.yml`
|
- `nano nadekobot/output/creds.yml`
|
||||||
7. [Enter your bot's token](../../creds-guide)
|
7. [Click here to follow creds guide](../../creds-guide)
|
||||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||||
- `CTRL` + `X`
|
- `CTRL` + `X`
|
||||||
- `Y`
|
- `Y`
|
||||||
- `Enter`
|
- `Enter`
|
||||||
8. Run the bot (type `3` and press enter)
|
8. Run the bot (type `3` and press enter)
|
||||||
|
|
||||||
##### Update Instructions
|
##### Source Update Instructions
|
||||||
|
|
||||||
1. ⚠ Stop the bot
|
1. ⚠ Stop the bot ⚠
|
||||||
2. Update and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
|
2. Update and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
|
||||||
3. Update the bot (type `2` and press enter)
|
3. Update the bot (type `2` and press enter)
|
||||||
4. Run the bot (type `3` and press enter)
|
4. Run the bot (type `3` and press enter)
|
||||||
@@ -34,6 +54,8 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
|||||||
|
|
||||||
## Linux Release
|
## Linux Release
|
||||||
|
|
||||||
|
**⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠**
|
||||||
|
|
||||||
##### Installation Instructions
|
##### Installation Instructions
|
||||||
|
|
||||||
1. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
1. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
||||||
@@ -49,7 +71,7 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
|||||||
- `cp creds_example.yml creds.yml`
|
- `cp creds_example.yml creds.yml`
|
||||||
6. Open `creds.yml` with your favorite text editor. We will use nano here
|
6. Open `creds.yml` with your favorite text editor. We will use nano here
|
||||||
- `nano nadekobot/output/creds.yml`
|
- `nano nadekobot/output/creds.yml`
|
||||||
8. [Enter your bot's token](#creds-guide)
|
8. [Click here to follow creds guide](../../creds-guide)
|
||||||
- After you're done, you can close nano (and save the file) by inputting, in order
|
- After you're done, you can close nano (and save the file) by inputting, in order
|
||||||
- `CTRL` + `X`
|
- `CTRL` + `X`
|
||||||
- `Y`
|
- `Y`
|
||||||
@@ -57,7 +79,7 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
|||||||
9. Run the bot
|
9. Run the bot
|
||||||
- `./NadekoBot`
|
- `./NadekoBot`
|
||||||
|
|
||||||
##### Update Instructions
|
##### Release Update Instructions
|
||||||
|
|
||||||
1. Stop the bot
|
1. Stop the bot
|
||||||
2. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
2. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
||||||
@@ -104,7 +126,7 @@ cd nadekobot && chmod +x NadekoBot
|
|||||||
|
|
||||||
While there are two run modes built into the installer, these options only run Nadeko within the current session. Below are 3 methods of running Nadeko as a background process.
|
While there are two run modes built into the installer, these options only run Nadeko within the current session. Below are 3 methods of running Nadeko as a background process.
|
||||||
|
|
||||||
### Tmux (Preferred Method)
|
### Tmux Method (Preferred)
|
||||||
|
|
||||||
Using `tmux` is the simplest method, and is therefore recommended for most users.
|
Using `tmux` is the simplest method, and is therefore recommended for most users.
|
||||||
|
|
||||||
@@ -134,6 +156,8 @@ Compared to using tmux, this method requires a little bit more work to set up, b
|
|||||||
echo "[Unit]
|
echo "[Unit]
|
||||||
Description=NadekoBot service
|
Description=NadekoBot service
|
||||||
After=network.target
|
After=network.target
|
||||||
|
StartLimitIntervalSec=60
|
||||||
|
StartLimitBurst=2
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
@@ -144,10 +168,11 @@ Compared to using tmux, this method requires a little bit more work to set up, b
|
|||||||
# source code.
|
# source code.
|
||||||
#ExecStartPre=/usr/bin/dotnet build ../src/NadekoBot/NadekoBot.csproj -c Release -o output/
|
#ExecStartPre=/usr/bin/dotnet build ../src/NadekoBot/NadekoBot.csproj -c Release -o output/
|
||||||
ExecStart=/usr/bin/dotnet NadekoBot.dll
|
ExecStart=/usr/bin/dotnet NadekoBot.dll
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
StandardOutput=syslog
|
StandardOutput=syslog
|
||||||
StandardError=syslog
|
StandardError=syslog
|
||||||
SyslogIdentifier=NadekoBot
|
SyslogIdentifier=NadekoBot
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/nadeko.service
|
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/nadeko.service
|
||||||
@@ -172,12 +197,16 @@ This method is similar to the one above, but requires one extra step, with the a
|
|||||||
echo "[Unit]
|
echo "[Unit]
|
||||||
Description=NadekoBot service
|
Description=NadekoBot service
|
||||||
After=network.target
|
After=network.target
|
||||||
|
StartLimitIntervalSec=60
|
||||||
|
StartLimitBurst=2
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=$USER
|
User=$USER
|
||||||
WorkingDirectory=$PWD
|
WorkingDirectory=$_WORKING_DIR
|
||||||
ExecStart=/bin/bash NadekoRun.sh
|
ExecStart=/bin/bash NadekoRun.sh
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
StandardOutput=syslog
|
StandardOutput=syslog
|
||||||
StandardError=syslog
|
StandardError=syslog
|
||||||
SyslogIdentifier=NadekoBot
|
SyslogIdentifier=NadekoBot
|
||||||
@@ -191,14 +220,14 @@ This method is similar to the one above, but requires one extra step, with the a
|
|||||||
4. Use the following command to create a script that will be used to start Nadeko:
|
4. Use the following command to create a script that will be used to start Nadeko:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo "#\!/bin/bash
|
{
|
||||||
|
echo '#!/bin/bash'
|
||||||
echo \"\"
|
echo ""
|
||||||
echo \"Running NadekoBot in the background with auto restart\"
|
echo "echo \"Running NadekoBot in the background with auto restart\"
|
||||||
youtube-dl -U
|
youtube-dl -U
|
||||||
|
|
||||||
# If you want Nadeko to be compiled prior to every startup, uncomment the lines
|
# If you want Nadeko to be compiled prior to every startup, uncomment the lines
|
||||||
# below. Note that it's not neccessary unless you are personally modifying the
|
# below. Note that it's not necessary unless you are personally modifying the
|
||||||
# source code.
|
# source code.
|
||||||
#echo \"Compiling NadekoBot...\"
|
#echo \"Compiling NadekoBot...\"
|
||||||
#cd \"$PWD\"/nadekobot
|
#cd \"$PWD\"/nadekobot
|
||||||
@@ -207,23 +236,73 @@ This method is similar to the one above, but requires one extra step, with the a
|
|||||||
echo \"Starting NadekoBot...\"
|
echo \"Starting NadekoBot...\"
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
{
|
if [[ -d $PWD/nadekobot/output ]]; then
|
||||||
cd \"$PWD\"/nadekobot/output
|
cd $PWD/nadekobot/output || {
|
||||||
dotnet NadekoBot.dll
|
echo \"Failed to change working directory to $PWD/nadekobot/output\" >&2
|
||||||
## If a non-zero exit code is produced, exit this script.
|
echo \"Ensure that the working directory inside of '/etc/systemd/system/nadeko.service' is correct\"
|
||||||
} || {
|
echo \"Exiting...\"
|
||||||
error_code=\"\$?\"
|
exit 1
|
||||||
|
}
|
||||||
|
else
|
||||||
|
echo \"$PWD/nadekobot/output doesn't exist\"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dotnet NadekoBot.dll || {
|
||||||
echo \"An error occurred when trying to start NadekBot\"
|
echo \"An error occurred when trying to start NadekBot\"
|
||||||
echo \"EXIT CODE: \$?\"
|
echo \"Exiting...\"
|
||||||
exit \"\$error_code\"
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echo \"Waiting for 5 seconds...\"
|
||||||
|
sleep 5
|
||||||
youtube-dl -U
|
youtube-dl -U
|
||||||
echo \"Restarting NadekoBot...\"
|
echo \"Restarting NadekoBot...\"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo \"Stopping NadekoBot...\"" > NadekoRun.sh
|
echo \"Stopping NadekoBot...\""
|
||||||
|
} > NadekoRun.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Start Nadeko:
|
5. Start Nadeko:
|
||||||
- `sudo systemctl start nadeko.service && sudo systemctl enable nadeko.service`
|
- `sudo systemctl start nadeko.service && sudo systemctl enable nadeko.service`
|
||||||
|
|
||||||
|
### Setting up Nadeko on a Linux VPS (Digital Ocean Droplet)
|
||||||
|
|
||||||
|
If you want Nadeko to play music for you 24/7 without having to hosting it on your PC and want to keep it cheap, reliable and convenient as possible, you can try Nadeko on Linux Digital Ocean Droplet using the link [DigitalOcean](http://m.do.co/c/46b4d3d44795/) (by using this link, you will get **$10 credit** and also support Nadeko)
|
||||||
|
|
||||||
|
**Setting up NadekoBot**
|
||||||
|
Assuming you have followed the link above to setup an account and a Droplet with a 64-bit operational system on Digital Ocean and got the `IP address and root password (in your e-mail)` to login, it's time to get started.
|
||||||
|
|
||||||
|
**This section is only relevant to those who want to host Nadeko on DigitalOcean. Go through this whole section before setting the bot up.**
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
- Download [PuTTY](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)
|
||||||
|
- Download [WinSCP](https://winscp.net/eng/download.php) *(optional)*
|
||||||
|
- [Create and invite the bot](../../creds-guide).
|
||||||
|
|
||||||
|
#### Starting up
|
||||||
|
|
||||||
|
- **Open PuTTY** and paste or enter your `IP address` and then click **Open**.
|
||||||
|
If you entered your Droplets IP address correctly, it should show **login as:** in a newly opened window.
|
||||||
|
- Now for **login as:**, type `root` and press enter.
|
||||||
|
- It should then ask for a password. Type the `root password` you have received in your e-mail address, then press Enter.
|
||||||
|
|
||||||
|
If you are running your droplet for the first time, it will most likely ask you to change your root password. To do that, copy the **password you've received by e-mail** and paste it on PuTTY.
|
||||||
|
|
||||||
|
- To paste, just right-click the window (it won't show any changes on the screen), then press Enter.
|
||||||
|
- Type a **new password** somewhere, copy and paste it on PuTTY. Press Enter then paste it again.
|
||||||
|
|
||||||
|
**Save the new password somewhere safe.**
|
||||||
|
|
||||||
|
After that, your droplet should be ready for use. [Follow the guide from the beginning](#linux-from-source) to set Nadeko up on your newly created VPS.
|
||||||
|
|
||||||
|
[Linux From Source]: #linux-from-source
|
||||||
|
[Source Update Instructions]: #source-update-instructions
|
||||||
|
[Linux Release]: #linux-release
|
||||||
|
[Release Update Instructions]: #release-update-instructions
|
||||||
|
[Tmux (Preferred Method)]: #tmux-preferred-method
|
||||||
|
[Systemd]: #systemd
|
||||||
|
[Systemd + Script]: #systemd-script
|
||||||
|
[Setting up Nadeko on a VPS (Digital Ocean)]: #setting-up-nadeko-on-a-linux-vps-digital-ocean-droplet
|
||||||
|
@@ -1,66 +0,0 @@
|
|||||||
# Migration instructions (2.x to v3)
|
|
||||||
|
|
||||||
## Windows
|
|
||||||
|
|
||||||
1. Run your NadekoBot Updater first, and **make sure your bot is updated to at least 2.46.5**
|
|
||||||
- **Run your 2.46.5 Bot** and make sure it works, and then **stop it**
|
|
||||||
- Close your old NadekoBot Updater
|
|
||||||
2. Get the new NadekoBot v3 Updater [here](https://dl.nadeko.bot/v3)
|
|
||||||
3. Click on the + icon to add a new bot
|
|
||||||
4. Next to the path, click on the folder icon and select the folder where your 2.46.5 bot is
|
|
||||||
- ℹ In case you're not sure where it's located, you can open your old updater and see it
|
|
||||||
5. If you've selected the correct path, you should have an **Update** button available, click it
|
|
||||||
6. You're done; you can now run your bot, and you can uninstall your old updater if you no longer have 2.x bots
|
|
||||||
7. 🎉
|
|
||||||
|
|
||||||
## Linux
|
|
||||||
|
|
||||||
1. In order to migrate a bot hosted on **Linux**, first update your current version to the latest 2.x version using the 2.x installer, run the bot, and make sure it works. Then:
|
|
||||||
- Run the **old** installer with `cd ~ && wget -N https://github.com/Kwoth/NadekoBot-BashScript/raw/1.9/linuxAIO.sh && bash linuxAIO.sh`
|
|
||||||
- Run option **1** again
|
|
||||||
- You **MUST** Run the bot now to ensure database is ready for migration
|
|
||||||
- Type `.stats` and ensure the version is `2.46.5` or later
|
|
||||||
- Stop the bot
|
|
||||||
2. Make sure your bot's folder is called `NadekoBot`
|
|
||||||
- Run `cd ~ && ls`
|
|
||||||
- Confirm there is a folder called NadekoBot (not nadekobot, in all lowercase)
|
|
||||||
3. Migrate your bot's data using the new installer:
|
|
||||||
- Run the **new** installer `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
|
|
||||||
- The installer should notify you that your data is ready for migration in a message above the menu.
|
|
||||||
- Install prerequisites (type `1` and press enter), and make sure it is successful
|
|
||||||
- Download NadekoBot v3 (type `2` and press enter)
|
|
||||||
- Run the bot (type `3` and press enter)
|
|
||||||
4. Make sure your permissions, custom reactions, credentials, and other data is preserved
|
|
||||||
- `.stats` to ensure owner id (credentials) is correct
|
|
||||||
- `.lcr` to see custom reactions
|
|
||||||
- `.lp` to list permissions
|
|
||||||
5. 🎉 Enjoy. If you want to learn how to update the bot, click [here](../linux-guide/#update-instructions)
|
|
||||||
|
|
||||||
## Manual
|
|
||||||
|
|
||||||
⚠ NOT RECOMMENDED
|
|
||||||
⚠ NadekoBot v3 requires [.net 5](https://dotnet.microsoft.com/download/dotnet/5.0)
|
|
||||||
|
|
||||||
1. In order to migrate a bot hosted **on Linux or from source on Windows**
|
|
||||||
- First update your current version to the latest 2.x version using the 2.x installer
|
|
||||||
- Then you **must** run the bot to prepare the database for the migration, and make sure the bot works prior to upgrade.
|
|
||||||
Then:
|
|
||||||
2. Rename your old nadeko bot folder to `nadekobot_2x`
|
|
||||||
- `mv NadekoBot nadekobot_2x`
|
|
||||||
3. Build the new version and move old data to the output folder
|
|
||||||
1. Clone the v3 branch to a separate folder
|
|
||||||
- `git clone https://gitlab.com/kwoth/nadekobot -b v3 --depth 1`
|
|
||||||
2. Build the bot
|
|
||||||
- `dotnet publish -c Release -o output/ src/NadekoBot/`
|
|
||||||
3. Copy old data
|
|
||||||
- ⚠ Be sure you copy the correct command for your system!
|
|
||||||
- **Windows:** `cp -r -fo nadekobot_2x/src/NadekoBot/data nadekobot/src/NadekoBot/data`
|
|
||||||
- **Linux:** `cp -rf nadekobot_2x/src/NadekoBot/data nadekobot/src/NadekoBot/data`
|
|
||||||
4. Copy the database
|
|
||||||
- `cp nadekobot_2x/src/NadekoBot/bin/Release/netcoreapp2.1/data/NadekoBot.db nadekobot/output/data`
|
|
||||||
5. Copy your credentials
|
|
||||||
- `cp nadekobot_2x/src/NadekoBot/credentials.json nadekobot/output/`
|
|
||||||
4. Run the bot
|
|
||||||
- `cd nadekobot/output`
|
|
||||||
- `dotnet NadekoBot.dll`
|
|
||||||
5. That's it. Just make sure that when you're updating the bot, you're properly backing up your old data.
|
|
@@ -2,14 +2,33 @@
|
|||||||
|
|
||||||
Open Terminal (if you don't know how to, click on the magnifying glass on the top right corner of your screen and type **Terminal** on the window that pops up) and navigate to the location where you want to install the bot (for example `cd ~`)
|
Open Terminal (if you don't know how to, click on the magnifying glass on the top right corner of your screen and type **Terminal** on the window that pops up) and navigate to the location where you want to install the bot (for example `cd ~`)
|
||||||
|
|
||||||
##### Installing Homebrew and wget
|
##### Installing Homebrew, wget and dotnet
|
||||||
|
|
||||||
|
###### Homebrew/wget
|
||||||
*Skip this step if you already have homebrew installed*
|
*Skip this step if you already have homebrew installed*
|
||||||
- Copy and paste this command, then press Enter:
|
- Copy and paste this command, then press Enter:
|
||||||
- `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
|
- `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
|
||||||
- Install wget
|
- Install wget
|
||||||
- `brew install wget`
|
- `brew install wget`
|
||||||
|
|
||||||
|
###### Dotnet
|
||||||
|
- Download [.net6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0)
|
||||||
|
- Open the `.pkg` file you've downloaded and install it.
|
||||||
|
- Run this command in Terminal. There might be output. If there is, disregard it. (copy-paste the entire block)
|
||||||
|
```bash
|
||||||
|
sudo mkdir /usr/local/bin
|
||||||
|
|
||||||
|
sudo mkdir /usr/local/lib
|
||||||
|
```
|
||||||
|
- Run this command in Terminal. There won't be any output. (copy-paste the entire block):
|
||||||
|
```bash
|
||||||
|
sudo ln -s /usr/local/share/dotnet/dotnet /usr/local/bin
|
||||||
|
|
||||||
|
sudo ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/
|
||||||
|
|
||||||
|
sudo ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/
|
||||||
|
```
|
||||||
|
|
||||||
##### Installation Instructions
|
##### Installation Instructions
|
||||||
|
|
||||||
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
|
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
|
||||||
@@ -37,6 +56,8 @@ Open Terminal (if you don't know how to, click on the magnifying glass on the to
|
|||||||
|
|
||||||
## MacOS Manual Release installation instructions
|
## MacOS Manual Release installation instructions
|
||||||
|
|
||||||
|
⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠
|
||||||
|
|
||||||
##### Installation Instructions
|
##### Installation Instructions
|
||||||
|
|
||||||
1. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
1. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
||||||
|
@@ -1,9 +1,3 @@
|
|||||||
## Migration from 2.x
|
|
||||||
|
|
||||||
⚠ If you're already hosting NadekoBot, You **MUST** update to latest version of 2.x and **run your bot at least once** before switching over to v3.
|
|
||||||
|
|
||||||
#### [Windows migration instructions](../migration-guide#windows)
|
|
||||||
|
|
||||||
## Setting Up NadekoBot on Windows With the Updater
|
## Setting Up NadekoBot on Windows With the Updater
|
||||||
|
|
||||||
| Table of Contents|
|
| Table of Contents|
|
||||||
@@ -12,16 +6,16 @@
|
|||||||
| [Setup](#setup) |
|
| [Setup](#setup) |
|
||||||
| [Starting the Bot](#starting-the-bot) |
|
| [Starting the Bot](#starting-the-bot) |
|
||||||
| [Updating Nadeko](#updating-nadeko) |
|
| [Updating Nadeko](#updating-nadeko) |
|
||||||
| [Manually Installing the Prerequisites from the Updater](#if-the-updater-fails-to-install-the-prerequisites-for-any-reason) |
|
| [Manually Installing the Prerequisites from the Updater](#music-prerequisites) |
|
||||||
|
|
||||||
*Note: If you want to make changes to Nadeko's source code, please follow the [From Source][SourceGuide] guide instead.*
|
*Note: If you want to make changes to Nadeko's source code, please follow the [From Source](#windows-from-source) guide instead.*
|
||||||
|
|
||||||
*If you have Windows 7 or a 32-bit system, please refer to the [From Source][SourceGuide] guide.*
|
*If you have Windows 7 or a 32-bit system, please refer to the [From Source](#windows-from-source)) guide.*
|
||||||
|
|
||||||
#### Prerequisites
|
#### Prerequisites
|
||||||
|
|
||||||
- Windows 8 or later (64-bit)
|
- Windows 8 or later (64-bit)
|
||||||
- [Create a Discord Bot application and invite the bot to your server](../../creds-guide.md)
|
- [Create a Discord Bot application and invite the bot to your server](../creds-guide.md)
|
||||||
|
|
||||||
**Optional**
|
**Optional**
|
||||||
|
|
||||||
@@ -32,12 +26,13 @@
|
|||||||
|
|
||||||
- Download and run the [NadekoBot v3 Updater][Updater].
|
- Download and run the [NadekoBot v3 Updater][Updater].
|
||||||
- Click on the + at the top left to create a new bot.
|
- Click on the + at the top left to create a new bot.
|
||||||

|

|
||||||
- Give your bot a name and then click **`Go to setup`** at the lower right.
|
- Give your bot a name and then click **`Go to setup`** at the lower right.
|
||||||

|

|
||||||
- Click on **`DOWNLOAD`** at the lower right
|
- Click on **`DOWNLOAD`** at the lower right
|
||||||

|

|
||||||
- Click on **`Install`** next to **`Redis`**.
|
- Click on **`Install`** next to **`Redis`**.
|
||||||
|
- Note: If Redis fails to install, install Redis manually here: [Redis Installer](https://github.com/MicrosoftArchive/redis/releases/tag/win-3.0.504) Download and run the **`.msi`** file.
|
||||||
- If you will use the music module, click on **`Install`** next to **`FFMPEG`** and **`Youtube-DL`**.
|
- If you will use the music module, click on **`Install`** next to **`FFMPEG`** and **`Youtube-DL`**.
|
||||||
- If any dependencies fail to install, you can temporarily disable your Windows Defender/AV until you install them. If you don't want to, then read [the last section of this guide](#Manual-Prerequisite-Installation).
|
- If any dependencies fail to install, you can temporarily disable your Windows Defender/AV until you install them. If you don't want to, then read [the last section of this guide](#Manual-Prerequisite-Installation).
|
||||||
- When installation is finished, click on **`CREDS`** to the left of **`RUN`** at the lower right.
|
- When installation is finished, click on **`CREDS`** to the left of **`RUN`** at the lower right.
|
||||||
@@ -47,6 +42,8 @@
|
|||||||
|
|
||||||
- Either click on **`RUN`** button in the updater or run the bot via its desktop shortcut.
|
- Either click on **`RUN`** button in the updater or run the bot via its desktop shortcut.
|
||||||
|
|
||||||
|
### If you get a "No owner channels created..." message. Please follow the creds guide again [**HERE**](../../creds-guide).
|
||||||
|
|
||||||
#### Updating Nadeko
|
#### Updating Nadeko
|
||||||
|
|
||||||
- Make sure Nadeko is closed and not running
|
- Make sure Nadeko is closed and not running
|
||||||
@@ -68,10 +65,12 @@ You can still install them manually:
|
|||||||
|
|
||||||
### Windows From Source
|
### Windows From Source
|
||||||
|
|
||||||
|
⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠
|
||||||
|
|
||||||
##### Prerequisites
|
##### Prerequisites
|
||||||
|
|
||||||
**Install these before proceeding or your bot will not work!**
|
**Install these before proceeding or your bot will not work!**
|
||||||
- [.net 5](https://dotnet.microsoft.com/download/dotnet/5.0) - needed to compile and run the bot
|
- [.net 6](https://dotnet.microsoft.com/download/dotnet/6.0) - needed to compile and run the bot
|
||||||
- [git](https://git-scm.com/downloads) - needed to clone the repository (you can also download the zip manually and extract it, but this guide assumes you're using git)
|
- [git](https://git-scm.com/downloads) - needed to clone the repository (you can also download the zip manually and extract it, but this guide assumes you're using git)
|
||||||
- [redis](https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.msi) - to cache things needed by some features and persist through restarts
|
- [redis](https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.msi) - to cache things needed by some features and persist through restarts
|
||||||
|
|
||||||
@@ -79,14 +78,15 @@ You can still install them manually:
|
|||||||
|
|
||||||
Open PowerShell (press windows button on your keyboard and type powershell, it should show up; alternatively, right click the start menu and select Windows PowerShell), and navigate to the location where you want to install the bot (for example `cd ~/Desktop/`)
|
Open PowerShell (press windows button on your keyboard and type powershell, it should show up; alternatively, right click the start menu and select Windows PowerShell), and navigate to the location where you want to install the bot (for example `cd ~/Desktop/`)
|
||||||
|
|
||||||
1. `git clone https://gitlab.com/kwoth/nadekobot -b v3 --depth 1`
|
1. `git clone https://gitlab.com/kwoth/nadekobot -b v4 --depth 1`
|
||||||
2. `cd nadekobot`
|
2. `cd nadekobot`
|
||||||
3. `dotnet publish -c Release -o output/ src/NadekoBot/`
|
3. `dotnet publish -c Release -o output/ src/NadekoBot/`
|
||||||
4. `cd output && cp creds_example.yml creds.yml`
|
4. `cd output`
|
||||||
5. Open `creds.yml` with your favorite text editor (Please don't use Notepad or WordPad. You can use Notepad++, VSCode, Atom, Sublime, or something similar)
|
5. `cp creds_example.yml creds.yml`
|
||||||
6. [Enter your bot's token](#creds-guide)
|
6. Open `creds.yml` with your favorite text editor (Please don't use Notepad or WordPad. You can use Notepad++, VSCode, Atom, Sublime, or something similar)
|
||||||
7. Run the bot `dotnet NadekoBot.dll`
|
7. [Enter your bot's token](#creds-guide)
|
||||||
8. 🎉
|
8. Run the bot `dotnet NadekoBot.dll`
|
||||||
|
9. 🎉
|
||||||
|
|
||||||
##### Update Instructions
|
##### Update Instructions
|
||||||
|
|
||||||
@@ -122,14 +122,12 @@ 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`.
|
- [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`.
|
- [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/v3
|
[Updater]: https://dl.nadeko.bot/
|
||||||
[Notepad++]: https://notepad-plus-plus.org/
|
[Notepad++]: https://notepad-plus-plus.org/
|
||||||
[.net]: https://dotnet.microsoft.com/download/dotnet/5.0
|
[.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
|
[Redis]: https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.msi
|
||||||
[Visual C++ 2010 (x86)]: https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe
|
[Visual C++ 2010 (x86)]: https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe
|
||||||
[Visual C++ 2017 (x64)]: https://aka.ms/vs/15/release/vc_redist.x64.exe
|
[Visual C++ 2017 (x64)]: https://aka.ms/vs/15/release/vc_redist.x64.exe
|
||||||
[SourceGuide]: ../from-source
|
|
||||||
[ffmpeg-32bit]: https://cdn.nadeko.bot/dl/ffmpeg-32.zip
|
[ffmpeg-32bit]: https://cdn.nadeko.bot/dl/ffmpeg-32.zip
|
||||||
[ffmpeg-64bit]: https://cdn.nadeko.bot/dl/ffmpeg-64.zip
|
[ffmpeg-64bit]: https://cdn.nadeko.bot/dl/ffmpeg-64.zip
|
||||||
[youtube-dl]: https://yt-dl.org/downloads/latest/youtube-dl.exe
|
[youtube-dl]: https://yt-dl.org/downloads/latest/youtube-dl.exe
|
||||||
|
|
||||||
|
@@ -8,8 +8,14 @@ This part is completely optional, **however it's necessary for music and a few o
|
|||||||
- Go to [Google Console][Google Console] and log in.
|
- Go to [Google Console][Google Console] and log in.
|
||||||
- Create a new project (name does not matter).
|
- Create a new project (name does not matter).
|
||||||
- Once the project is created, go into `Library`
|
- Once the project is created, go into `Library`
|
||||||
- Under the `YouTube APIs` section, enable `YouTube Data API`
|
- Under the `YouTube APIs` section
|
||||||
- On the left tab, access `Credentials`,
|
- Select `YouTube Data API v3`,
|
||||||
|
- Click enable.
|
||||||
|
- Search for `Custom Search API`
|
||||||
|
- Select `Custom Search API`,
|
||||||
|
- Click enable.
|
||||||
|
- Open up the `Navigation menu` on the top right with the three lines.
|
||||||
|
- select `APIs & Services`, then select `Credentials`,
|
||||||
- Click `Create Credentials` button,
|
- Click `Create Credentials` button,
|
||||||
- Click on `API Key`
|
- Click on `API Key`
|
||||||
- A new window will appear with your `Google API key`
|
- A new window will appear with your `Google API key`
|
||||||
@@ -18,7 +24,7 @@ This part is completely optional, **however it's necessary for music and a few o
|
|||||||
- Open up `creds.yml` and look for `GoogleAPIKey`, paste your API key after the `:`.
|
- Open up `creds.yml` and look for `GoogleAPIKey`, paste your API key after the `:`.
|
||||||
- It should look like this:
|
- It should look like this:
|
||||||
```yml
|
```yml
|
||||||
GoogleApiKey: "AIzaSyDSci1sdlWQOWNVj1vlXxxxxxbk0oWMEzM"
|
GoogleApiKey: AIzaSyDSci1sdlWQOWNVj1vlXxxxxxbk0oWMEzM
|
||||||
```
|
```
|
||||||
- **MashapeKey**
|
- **MashapeKey**
|
||||||
- Required for Hearthstone cards.
|
- Required for Hearthstone cards.
|
||||||
@@ -69,6 +75,7 @@ For Windows (Updater), add this to your `creds.yml`
|
|||||||
```yml
|
```yml
|
||||||
RestartCommand:
|
RestartCommand:
|
||||||
Cmd: "NadekoBot.exe"
|
Cmd: "NadekoBot.exe"
|
||||||
|
args: "{0}"
|
||||||
```
|
```
|
||||||
|
|
||||||
For Windows (Source), Linux or OSX, add this to your `creds.yml`
|
For Windows (Source), Linux or OSX, add this to your `creds.yml`
|
||||||
@@ -92,11 +99,10 @@ version: 1
|
|||||||
token: 'MTE5Nzc3MDIxMzE5NTc3NjEw.VlhNCw.BuqJFyzdIUAK1PRf1eK1Cu89Jew'
|
token: 'MTE5Nzc3MDIxMzE5NTc3NjEw.VlhNCw.BuqJFyzdIUAK1PRf1eK1Cu89Jew'
|
||||||
# List of Ids of the users who have bot owner permissions
|
# List of Ids of the users who have bot owner permissions
|
||||||
# **DO NOT ADD PEOPLE YOU DON'T TRUST**
|
# **DO NOT ADD PEOPLE YOU DON'T TRUST**
|
||||||
ownerIds: [
|
ownerIds:
|
||||||
105635123466156544,
|
- 105635123466156544
|
||||||
145521851676884992,
|
- 145521851676884992
|
||||||
341420590009417729
|
- 341420590009417729
|
||||||
]
|
|
||||||
# The number of shards that the bot will running on.
|
# The number of shards that the bot will running on.
|
||||||
# Leave at 1 if you don't know what you're doing.
|
# Leave at 1 if you don't know what you're doing.
|
||||||
totalShards: 1
|
totalShards: 1
|
||||||
@@ -156,8 +162,8 @@ osuApiKey: 4c8c8fdffdsfdsfsdfsfa33f3f3140a7d93320d6
|
|||||||
# cmd: dotnet
|
# cmd: dotnet
|
||||||
# args: "NadekoBot.dll -- {0}"
|
# args: "NadekoBot.dll -- {0}"
|
||||||
# Windows default
|
# Windows default
|
||||||
# cmd: NadekoBot.exe
|
# cmd: "NadekoBot.exe"
|
||||||
# args: {0}
|
# args: "{0}"
|
||||||
restartCommand:
|
restartCommand:
|
||||||
cmd:
|
cmd:
|
||||||
args:
|
args:
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#define sysfolder "system"
|
#define sysfolder "system"
|
||||||
#define version GetEnv("NADEKOBOT_INSTALL_VERSION")
|
#define version GetEnv("NADEKOBOT_INSTALL_VERSION")
|
||||||
#define target "win7-x64"
|
#define target "win7-x64"
|
||||||
#define platform "net5.0"
|
#define platform "net6.0"
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
AppName = {param:botname|NadekoBot}
|
AppName = {param:botname|NadekoBot}
|
||||||
|
@@ -72,7 +72,6 @@ markdown_extensions:
|
|||||||
nav:
|
nav:
|
||||||
- Home: index.md
|
- Home: index.md
|
||||||
- Guides:
|
- Guides:
|
||||||
- ❗ Migration Guide: guides/migration-guide.md
|
|
||||||
- Windows Guide: guides/windows-guide.md
|
- Windows Guide: guides/windows-guide.md
|
||||||
- Linux Guide: guides/linux-guide.md
|
- Linux Guide: guides/linux-guide.md
|
||||||
- OSX Guide: guides/osx-guide.md
|
- OSX Guide: guides/osx-guide.md
|
||||||
|
@@ -12,9 +12,7 @@ namespace NadekoBot.Coordinator
|
|||||||
public IConfiguration Configuration { get; }
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
public CoordStartup(IConfiguration config)
|
public CoordStartup(IConfiguration config)
|
||||||
{
|
=> Configuration = config;
|
||||||
Configuration = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
@@ -21,7 +21,7 @@ namespace NadekoBot.Services
|
|||||||
.Enrich.WithProperty("LogSource", source)
|
.Enrich.WithProperty("LogSource", source)
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
System.Console.OutputEncoding = Encoding.UTF8;
|
Console.OutputEncoding = Encoding.UTF8;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConsoleTheme GetTheme()
|
private static ConsoleTheme GetTheme()
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -9,9 +9,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.AspNetCore" Version="2.38.0" />
|
<PackageReference Include="Grpc.AspNetCore" Version="2.42.0" />
|
||||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@@ -6,7 +6,6 @@ using System.Linq;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
@@ -30,7 +29,7 @@ namespace NadekoBot.Coordinator
|
|||||||
private readonly Random _rng;
|
private readonly Random _rng;
|
||||||
private bool _gracefulImminent;
|
private bool _gracefulImminent;
|
||||||
|
|
||||||
public CoordinatorRunner(IConfiguration configuration)
|
public CoordinatorRunner()
|
||||||
{
|
{
|
||||||
_serializer = new();
|
_serializer = new();
|
||||||
_deserializer = new();
|
_deserializer = new();
|
||||||
@@ -91,7 +90,7 @@ namespace NadekoBot.Coordinator
|
|||||||
var shardIds = Enumerable.Range(0, 1) // shard 0 is always first
|
var shardIds = Enumerable.Range(0, 1) // shard 0 is always first
|
||||||
.Append((int)((117523346618318850 >> 22) % _config.TotalShards)) // then nadeko server shard
|
.Append((int)((117523346618318850 >> 22) % _config.TotalShards)) // then nadeko server shard
|
||||||
.Concat(Enumerable.Range(1, _config.TotalShards - 1)
|
.Concat(Enumerable.Range(1, _config.TotalShards - 1)
|
||||||
.OrderBy(x => _rng.Next())) // then all other shards in a random order
|
.OrderBy(_ => _rng.Next())) // then all other shards in a random order
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@@ -160,7 +159,9 @@ namespace NadekoBot.Coordinator
|
|||||||
private void StartShard(int shardId)
|
private void StartShard(int shardId)
|
||||||
{
|
{
|
||||||
var status = _shardStatuses[shardId];
|
var status = _shardStatuses[shardId];
|
||||||
if (status.Process is {HasExited: false} p)
|
try
|
||||||
|
{
|
||||||
|
if (status.Process is { HasExited: false } p)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -172,6 +173,10 @@ namespace NadekoBot.Coordinator
|
|||||||
}
|
}
|
||||||
|
|
||||||
status.Process?.Dispose();
|
status.Process?.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
var proc = StartShardProcess(shardId);
|
var proc = StartShardProcess(shardId);
|
||||||
_shardStatuses[shardId] = status with
|
_shardStatuses[shardId] = status with
|
||||||
@@ -185,8 +190,7 @@ namespace NadekoBot.Coordinator
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Process StartShardProcess(int shardId)
|
private Process StartShardProcess(int shardId)
|
||||||
{
|
=> Process.Start(new ProcessStartInfo()
|
||||||
return Process.Start(new ProcessStartInfo()
|
|
||||||
{
|
{
|
||||||
FileName = _config.ShardStartCommand,
|
FileName = _config.ShardStartCommand,
|
||||||
Arguments = string.Format(_config.ShardStartArgs,
|
Arguments = string.Format(_config.ShardStartArgs,
|
||||||
@@ -199,7 +203,6 @@ namespace NadekoBot.Coordinator
|
|||||||
// CreateNoWindow = true,
|
// CreateNoWindow = true,
|
||||||
// UseShellExecute = false,
|
// UseShellExecute = false,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
public bool Heartbeat(int shardId, int guildCount, ConnState state)
|
public bool Heartbeat(int shardId, int guildCount, ConnState state)
|
||||||
{
|
{
|
||||||
@@ -233,7 +236,6 @@ namespace NadekoBot.Coordinator
|
|||||||
{
|
{
|
||||||
lock (locker)
|
lock (locker)
|
||||||
{
|
{
|
||||||
ref var toSave = ref _config;
|
|
||||||
SaveConfig(new Config(
|
SaveConfig(new Config(
|
||||||
totalShards,
|
totalShards,
|
||||||
_config.RecheckIntervalMs,
|
_config.RecheckIntervalMs,
|
||||||
@@ -278,7 +280,7 @@ namespace NadekoBot.Coordinator
|
|||||||
for (var shardId = 0; shardId < _shardStatuses.Length; shardId++)
|
for (var shardId = 0; shardId < _shardStatuses.Length; shardId++)
|
||||||
{
|
{
|
||||||
var status = _shardStatuses[shardId];
|
var status = _shardStatuses[shardId];
|
||||||
if (status.Process is Process p)
|
if (status.Process is { } p)
|
||||||
{
|
{
|
||||||
p.Kill();
|
p.Kill();
|
||||||
p.Dispose();
|
p.Dispose();
|
||||||
@@ -308,7 +310,7 @@ namespace NadekoBot.Coordinator
|
|||||||
})
|
})
|
||||||
.ToList()
|
.ToList()
|
||||||
};
|
};
|
||||||
var jsonState = JsonSerializer.Serialize(coordState, new ()
|
var jsonState = JsonSerializer.Serialize(coordState, new JsonSerializerOptions()
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
});
|
});
|
||||||
@@ -340,7 +342,7 @@ namespace NadekoBot.Coordinator
|
|||||||
|
|
||||||
if (savedState.StatusObjects.Count != _config.TotalShards)
|
if (savedState.StatusObjects.Count != _config.TotalShards)
|
||||||
{
|
{
|
||||||
Log.Error("Unable to restore old state because shard count doesn't match.");
|
Log.Error("Unable to restore old state because shard count doesn't match");
|
||||||
File.Move(GRACEFUL_STATE_PATH, GRACEFUL_STATE_BACKUP_PATH, overwrite: true);
|
File.Move(GRACEFUL_STATE_PATH, GRACEFUL_STATE_BACKUP_PATH, overwrite: true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -351,7 +353,7 @@ namespace NadekoBot.Coordinator
|
|||||||
{
|
{
|
||||||
var statusObj = savedState.StatusObjects[shardId];
|
var statusObj = savedState.StatusObjects[shardId];
|
||||||
Process p = null;
|
Process p = null;
|
||||||
if (statusObj.Pid is int pid)
|
if (statusObj.Pid is { } pid)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -359,7 +361,7 @@ namespace NadekoBot.Coordinator
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, $"Process for shard {shardId} is not runnning.");
|
Log.Warning(ex, "Process for shard {ShardId} is not runnning", shardId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,9 +437,7 @@ namespace NadekoBot.Coordinator
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string GetConfigText()
|
public string GetConfigText()
|
||||||
{
|
=> File.ReadAllText(CONFIG_PATH);
|
||||||
return File.ReadAllText(CONFIG_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetConfigText(string text)
|
public void SetConfigText(string text)
|
||||||
{
|
{
|
||||||
|
@@ -5,14 +5,12 @@ using Grpc.Core;
|
|||||||
|
|
||||||
namespace NadekoBot.Coordinator
|
namespace NadekoBot.Coordinator
|
||||||
{
|
{
|
||||||
public sealed class CoordinatorService : NadekoBot.Coordinator.Coordinator.CoordinatorBase
|
public sealed class CoordinatorService : Coordinator.CoordinatorBase
|
||||||
{
|
{
|
||||||
private readonly CoordinatorRunner _runner;
|
private readonly CoordinatorRunner _runner;
|
||||||
|
|
||||||
public CoordinatorService(CoordinatorRunner runner)
|
public CoordinatorService(CoordinatorRunner runner)
|
||||||
{
|
=> _runner = runner;
|
||||||
_runner = runner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<HeartbeatReply> Heartbeat(HeartbeatRequest request, ServerCallContext context)
|
public override Task<HeartbeatReply> Heartbeat(HeartbeatRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
@@ -113,11 +111,10 @@ namespace NadekoBot.Coordinator
|
|||||||
return new DieReply();
|
return new DieReply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<SetConfigTextReply> SetConfigText(SetConfigTextRequest request, ServerCallContext context)
|
public override Task<SetConfigTextReply> SetConfigText(SetConfigTextRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
var error = string.Empty;
|
||||||
string error = string.Empty;
|
var success = true;
|
||||||
bool success = true;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_runner.SetConfigText(request.ConfigYml);
|
_runner.SetConfigText(request.ConfigYml);
|
||||||
@@ -128,11 +125,11 @@ namespace NadekoBot.Coordinator
|
|||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(new()
|
return Task.FromResult<SetConfigTextReply>(new(new()
|
||||||
{
|
{
|
||||||
Success = success,
|
Success = success,
|
||||||
Error = error
|
Error = error
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<GetConfigTextReply> GetConfigText(GetConfigTextRequest request, ServerCallContext context)
|
public override Task<GetConfigTextReply> GetConfigText(GetConfigTextRequest request, ServerCallContext context)
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
namespace NadekoBot.Coordinator
|
||||||
|
|
||||||
namespace NadekoBot.Coordinator
|
|
||||||
{
|
{
|
||||||
public class JsonStatusObject
|
public class JsonStatusObject
|
||||||
{
|
{
|
||||||
|
254
src/NadekoBot.Generators/Cloneable/CloneableGenerator.cs
Normal file
254
src/NadekoBot.Generators/Cloneable/CloneableGenerator.cs
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
// Code temporarily yeeted from
|
||||||
|
// https://github.com/mostmand/Cloneable/blob/master/Cloneable/CloneableGenerator.cs
|
||||||
|
// because of NRT issue
|
||||||
|
#nullable enable
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Cloneable
|
||||||
|
{
|
||||||
|
[Generator]
|
||||||
|
public class CloneableGenerator : ISourceGenerator
|
||||||
|
{
|
||||||
|
private const string PREVENT_DEEP_COPY_KEY_STRING = "PreventDeepCopy";
|
||||||
|
private const string EXPLICIT_DECLARATION_KEY_STRING = "ExplicitDeclaration";
|
||||||
|
|
||||||
|
private const string CLONEABLE_NAMESPACE = "Cloneable";
|
||||||
|
private const string CLONEABLE_ATTRIBUTE_STRING = "CloneableAttribute";
|
||||||
|
private const string CLONE_ATTRIBUTE_STRING = "CloneAttribute";
|
||||||
|
private const string IGNORE_CLONE_ATTRIBUTE_STRING = "IgnoreCloneAttribute";
|
||||||
|
|
||||||
|
private const string CLONEABLE_ATTRIBUTE_TEXT = @"// <AutoGenerated/>
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace " + CLONEABLE_NAMESPACE + @"
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = false)]
|
||||||
|
public sealed class " + CLONEABLE_ATTRIBUTE_STRING + @" : Attribute
|
||||||
|
{
|
||||||
|
public " + CLONEABLE_ATTRIBUTE_STRING + @"()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool " + EXPLICIT_DECLARATION_KEY_STRING + @" { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
private const string CLONE_PROPERTY_ATTRIBUTE_TEXT = @"// <AutoGenerated/>
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace " + CLONEABLE_NAMESPACE + @"
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
|
||||||
|
public sealed class " + CLONE_ATTRIBUTE_STRING + @" : Attribute
|
||||||
|
{
|
||||||
|
public " + CLONE_ATTRIBUTE_STRING + @"()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool " + PREVENT_DEEP_COPY_KEY_STRING + @" { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
private const string IGNORE_CLONE_PROPERTY_ATTRIBUTE_TEXT = @"// <AutoGenerated/>
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace " + CLONEABLE_NAMESPACE + @"
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
|
||||||
|
public sealed class " + IGNORE_CLONE_ATTRIBUTE_STRING + @" : Attribute
|
||||||
|
{
|
||||||
|
public " + IGNORE_CLONE_ATTRIBUTE_STRING + @"()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
private INamedTypeSymbol? _cloneableAttribute;
|
||||||
|
private INamedTypeSymbol? _ignoreCloneAttribute;
|
||||||
|
private INamedTypeSymbol? _cloneAttribute;
|
||||||
|
|
||||||
|
public void Initialize(GeneratorInitializationContext context)
|
||||||
|
=> context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
|
||||||
|
|
||||||
|
public void Execute(GeneratorExecutionContext context)
|
||||||
|
{
|
||||||
|
InjectCloneableAttributes(context);
|
||||||
|
GenerateCloneMethods(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateCloneMethods(GeneratorExecutionContext context)
|
||||||
|
{
|
||||||
|
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Compilation compilation = GetCompilation(context);
|
||||||
|
|
||||||
|
InitAttributes(compilation);
|
||||||
|
|
||||||
|
var classSymbols = GetClassSymbols(compilation, receiver);
|
||||||
|
foreach (var classSymbol in classSymbols)
|
||||||
|
{
|
||||||
|
if (!classSymbol.TryGetAttribute(_cloneableAttribute!, out var attributes))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var attribute = attributes.Single();
|
||||||
|
var isExplicit = (bool?)attribute.NamedArguments.FirstOrDefault(e => e.Key.Equals(EXPLICIT_DECLARATION_KEY_STRING)).Value.Value ?? false;
|
||||||
|
context.AddSource($"{classSymbol.Name}_cloneable.g.cs", SourceText.From(CreateCloneableCode(classSymbol, isExplicit), Encoding.UTF8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitAttributes(Compilation compilation)
|
||||||
|
{
|
||||||
|
_cloneableAttribute = compilation.GetTypeByMetadataName($"{CLONEABLE_NAMESPACE}.{CLONEABLE_ATTRIBUTE_STRING}")!;
|
||||||
|
_cloneAttribute = compilation.GetTypeByMetadataName($"{CLONEABLE_NAMESPACE}.{CLONE_ATTRIBUTE_STRING}")!;
|
||||||
|
_ignoreCloneAttribute = compilation.GetTypeByMetadataName($"{CLONEABLE_NAMESPACE}.{IGNORE_CLONE_ATTRIBUTE_STRING}")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Compilation GetCompilation(GeneratorExecutionContext context)
|
||||||
|
{
|
||||||
|
var options = context.Compilation.SyntaxTrees.First().Options as CSharpParseOptions;
|
||||||
|
|
||||||
|
var compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(CLONEABLE_ATTRIBUTE_TEXT, Encoding.UTF8), options)).
|
||||||
|
AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(CLONE_PROPERTY_ATTRIBUTE_TEXT, Encoding.UTF8), options)).
|
||||||
|
AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(IGNORE_CLONE_PROPERTY_ATTRIBUTE_TEXT, Encoding.UTF8), options));
|
||||||
|
return compilation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateCloneableCode(INamedTypeSymbol classSymbol, bool isExplicit)
|
||||||
|
{
|
||||||
|
string namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
|
||||||
|
var fieldAssignmentsCode = GenerateFieldAssignmentsCode(classSymbol, isExplicit).ToList();
|
||||||
|
var fieldAssignmentsCodeSafe = fieldAssignmentsCode.Select(x =>
|
||||||
|
{
|
||||||
|
if (x.isCloneable)
|
||||||
|
return x.line + "Safe(referenceChain)";
|
||||||
|
return x.line;
|
||||||
|
});
|
||||||
|
var fieldAssignmentsCodeFast = fieldAssignmentsCode.Select(x =>
|
||||||
|
{
|
||||||
|
if (x.isCloneable)
|
||||||
|
return x.line + "()";
|
||||||
|
return x.line;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $@"using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace {namespaceName}
|
||||||
|
{{
|
||||||
|
{GetAccessModifier(classSymbol)} partial class {classSymbol.Name}
|
||||||
|
{{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a copy of {classSymbol.Name} with NO circular reference checking. This method should be used if performance matters.
|
||||||
|
///
|
||||||
|
/// <exception cref=""StackOverflowException"">Will occur on any object that has circular references in the hierarchy.</exception>
|
||||||
|
/// </summary>
|
||||||
|
public {classSymbol.Name} Clone()
|
||||||
|
{{
|
||||||
|
return new {classSymbol.Name}
|
||||||
|
{{
|
||||||
|
{string.Join(",\n", fieldAssignmentsCodeFast)}
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a copy of {classSymbol.Name} with circular reference checking. If a circular reference was detected, only a reference of the leaf object is passed instead of cloning it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name=""referenceChain"">Should only be provided if specific objects should not be cloned but passed by reference instead.</param>
|
||||||
|
public {classSymbol.Name} CloneSafe(Stack<object> referenceChain = null)
|
||||||
|
{{
|
||||||
|
if(referenceChain?.Contains(this) == true)
|
||||||
|
return this;
|
||||||
|
referenceChain ??= new Stack<object>();
|
||||||
|
referenceChain.Push(this);
|
||||||
|
var result = new {classSymbol.Name}
|
||||||
|
{{
|
||||||
|
{string.Join($",\n", fieldAssignmentsCodeSafe)}
|
||||||
|
}};
|
||||||
|
referenceChain.Pop();
|
||||||
|
return result;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<(string line, bool isCloneable)> GenerateFieldAssignmentsCode(INamedTypeSymbol classSymbol, bool isExplicit )
|
||||||
|
{
|
||||||
|
var fieldNames = GetCloneableProperties(classSymbol, isExplicit);
|
||||||
|
|
||||||
|
var fieldAssignments = fieldNames.Select(field => IsFieldCloneable(field, classSymbol))
|
||||||
|
.OrderBy(x => x.isCloneable)
|
||||||
|
.Select(x => (GenerateAssignmentCode(x.item.Name, x.isCloneable), x.isCloneable));
|
||||||
|
return fieldAssignments;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateAssignmentCode(string name, bool isCloneable)
|
||||||
|
{
|
||||||
|
if (isCloneable)
|
||||||
|
{
|
||||||
|
return $@" {name} = this.{name}?.Clone";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $@" {name} = this.{name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private (IPropertySymbol item, bool isCloneable) IsFieldCloneable(IPropertySymbol x, INamedTypeSymbol classSymbol)
|
||||||
|
{
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(x.Type, classSymbol))
|
||||||
|
{
|
||||||
|
return (x, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!x.Type.TryGetAttribute(_cloneableAttribute!, out var attributes))
|
||||||
|
{
|
||||||
|
return (x, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var preventDeepCopy = (bool?)attributes.Single().NamedArguments.FirstOrDefault(e => e.Key.Equals(PREVENT_DEEP_COPY_KEY_STRING)).Value.Value ?? false;
|
||||||
|
return (item: x, !preventDeepCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetAccessModifier(INamedTypeSymbol classSymbol)
|
||||||
|
=> classSymbol.DeclaredAccessibility.ToString().ToLowerInvariant();
|
||||||
|
|
||||||
|
private IEnumerable<IPropertySymbol> GetCloneableProperties(ITypeSymbol classSymbol, bool isExplicit)
|
||||||
|
{
|
||||||
|
var targetSymbolMembers = classSymbol.GetMembers().OfType<IPropertySymbol>()
|
||||||
|
.Where(x => x.SetMethod is not null &&
|
||||||
|
x.CanBeReferencedByName);
|
||||||
|
if (isExplicit)
|
||||||
|
{
|
||||||
|
return targetSymbolMembers.Where(x => x.HasAttribute(_cloneAttribute!));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return targetSymbolMembers.Where(x => !x.HasAttribute(_ignoreCloneAttribute!));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<INamedTypeSymbol> GetClassSymbols(Compilation compilation, SyntaxReceiver receiver)
|
||||||
|
=> receiver.CandidateClasses.Select(clazz => GetClassSymbol(compilation, clazz));
|
||||||
|
|
||||||
|
private static INamedTypeSymbol GetClassSymbol(Compilation compilation, ClassDeclarationSyntax clazz)
|
||||||
|
{
|
||||||
|
var model = compilation.GetSemanticModel(clazz.SyntaxTree);
|
||||||
|
var classSymbol = model.GetDeclaredSymbol(clazz)!;
|
||||||
|
return classSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InjectCloneableAttributes(GeneratorExecutionContext context)
|
||||||
|
{
|
||||||
|
context.AddSource(CLONEABLE_ATTRIBUTE_STRING, SourceText.From(CLONEABLE_ATTRIBUTE_TEXT, Encoding.UTF8));
|
||||||
|
context.AddSource(CLONE_ATTRIBUTE_STRING, SourceText.From(CLONE_PROPERTY_ATTRIBUTE_TEXT, Encoding.UTF8));
|
||||||
|
context.AddSource(IGNORE_CLONE_ATTRIBUTE_STRING, SourceText.From(IGNORE_CLONE_PROPERTY_ATTRIBUTE_TEXT, Encoding.UTF8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
src/NadekoBot.Generators/Cloneable/SymbolExtensions.cs
Normal file
24
src/NadekoBot.Generators/Cloneable/SymbolExtensions.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Code temporarily yeeted from
|
||||||
|
// https://github.com/mostmand/Cloneable/blob/master/Cloneable/CloneableGenerator.cs
|
||||||
|
// because of NRT issue
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Cloneable
|
||||||
|
{
|
||||||
|
internal static class SymbolExtensions
|
||||||
|
{
|
||||||
|
public static bool TryGetAttribute(this ISymbol symbol, INamedTypeSymbol attributeType,
|
||||||
|
out IEnumerable<AttributeData> attributes)
|
||||||
|
{
|
||||||
|
attributes = symbol.GetAttributes()
|
||||||
|
.Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType));
|
||||||
|
return attributes.Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)
|
||||||
|
=> symbol.GetAttributes()
|
||||||
|
.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType));
|
||||||
|
}
|
||||||
|
}
|
27
src/NadekoBot.Generators/Cloneable/SyntaxReceiver.cs
Normal file
27
src/NadekoBot.Generators/Cloneable/SyntaxReceiver.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Code temporarily yeeted from
|
||||||
|
// https://github.com/mostmand/Cloneable/blob/master/Cloneable/CloneableGenerator.cs
|
||||||
|
// because of NRT issue
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace Cloneable
|
||||||
|
{
|
||||||
|
internal class SyntaxReceiver : ISyntaxReceiver
|
||||||
|
{
|
||||||
|
public IList<ClassDeclarationSyntax> CandidateClasses { get; } = new List<ClassDeclarationSyntax>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
|
||||||
|
/// </summary>
|
||||||
|
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||||
|
{
|
||||||
|
// any field with at least one attribute is a candidate for being cloneable
|
||||||
|
if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax &&
|
||||||
|
classDeclarationSyntax.AttributeLists.Count > 0)
|
||||||
|
{
|
||||||
|
CandidateClasses.Add(classDeclarationSyntax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
323
src/NadekoBot.Generators/Command/CommandAttributesGenerator.cs
Normal file
323
src/NadekoBot.Generators/Command/CommandAttributesGenerator.cs
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.CodeDom.Compiler;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace NadekoBot.Generators.Command;
|
||||||
|
|
||||||
|
[Generator]
|
||||||
|
public class CommandAttributesGenerator : IIncrementalGenerator
|
||||||
|
{
|
||||||
|
public const string ATTRIBUTE = @"// <AutoGenerated />
|
||||||
|
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
[System.AttributeUsage(System.AttributeTargets.Method)]
|
||||||
|
public class CmdAttribute : System.Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
}";
|
||||||
|
|
||||||
|
public class MethodModel
|
||||||
|
{
|
||||||
|
public string? Namespace { get; }
|
||||||
|
public IReadOnlyCollection<string> Classes { get; }
|
||||||
|
public string ReturnType { get; }
|
||||||
|
public string MethodName { get; }
|
||||||
|
public IEnumerable<string> Params { get; }
|
||||||
|
|
||||||
|
public MethodModel(string? ns, IReadOnlyCollection<string> classes, string returnType, string methodName, IEnumerable<string> @params)
|
||||||
|
{
|
||||||
|
Namespace = ns;
|
||||||
|
Classes = classes;
|
||||||
|
ReturnType = returnType;
|
||||||
|
MethodName = methodName;
|
||||||
|
Params = @params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileModel
|
||||||
|
{
|
||||||
|
public string? Namespace { get; }
|
||||||
|
public IReadOnlyCollection<string> ClassHierarchy { get; }
|
||||||
|
public IReadOnlyCollection<MethodModel> Methods { get; }
|
||||||
|
|
||||||
|
public FileModel(string? ns, IReadOnlyCollection<string> classHierarchy, IReadOnlyCollection<MethodModel> methods)
|
||||||
|
{
|
||||||
|
Namespace = ns;
|
||||||
|
ClassHierarchy = classHierarchy;
|
||||||
|
Methods = methods;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
// #if DEBUG
|
||||||
|
// SpinWait.SpinUntil(() => Debugger.IsAttached);
|
||||||
|
// #endif
|
||||||
|
context.RegisterPostInitializationOutput(static ctx => ctx.AddSource(
|
||||||
|
"CmdAttribute.g.cs",
|
||||||
|
SourceText.From(ATTRIBUTE, Encoding.UTF8)));
|
||||||
|
|
||||||
|
var methods = context.SyntaxProvider
|
||||||
|
.CreateSyntaxProvider(
|
||||||
|
static (node, _) => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 },
|
||||||
|
static (ctx, cancel) => Transform(ctx, cancel))
|
||||||
|
.Where(static m => m is not null)
|
||||||
|
.Where(static m => m?.ChildTokens().Any(static x => x.IsKind(SyntaxKind.PublicKeyword)) ?? false);
|
||||||
|
|
||||||
|
var compilationMethods = context.CompilationProvider.Combine(methods.Collect());
|
||||||
|
|
||||||
|
context.RegisterSourceOutput(compilationMethods,
|
||||||
|
static (ctx, tuple) => RegisterAction(in ctx, tuple.Left, in tuple.Right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterAction(in SourceProductionContext ctx,
|
||||||
|
Compilation comp,
|
||||||
|
in ImmutableArray<MethodDeclarationSyntax?> methods)
|
||||||
|
{
|
||||||
|
if (methods is { IsDefaultOrEmpty: true })
|
||||||
|
return;
|
||||||
|
|
||||||
|
var models = GetModels(comp, methods, ctx.CancellationToken);
|
||||||
|
|
||||||
|
foreach (var model in models)
|
||||||
|
{
|
||||||
|
var name = $"{model.Namespace}.{string.Join(".", model.ClassHierarchy)}.g.cs";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Writing {name}");
|
||||||
|
var source = GetSourceText(model);
|
||||||
|
ctx.AddSource(name, SourceText.From(source, Encoding.UTF8));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Error writing source file {name}\n" + ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSourceText(FileModel model)
|
||||||
|
{
|
||||||
|
using var sw = new StringWriter();
|
||||||
|
using var tw = new IndentedTextWriter(sw);
|
||||||
|
|
||||||
|
tw.WriteLine("// <AutoGenerated />");
|
||||||
|
tw.WriteLine("#pragma warning disable CS1066");
|
||||||
|
|
||||||
|
if (model.Namespace is not null)
|
||||||
|
{
|
||||||
|
tw.WriteLine($"namespace {model.Namespace};");
|
||||||
|
tw.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var className in model.ClassHierarchy)
|
||||||
|
{
|
||||||
|
tw.WriteLine($"public partial class {className}");
|
||||||
|
tw.WriteLine("{");
|
||||||
|
tw.Indent ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var method in model.Methods)
|
||||||
|
{
|
||||||
|
tw.WriteLine("[NadekoCommand]");
|
||||||
|
tw.WriteLine("[NadekoDescription]");
|
||||||
|
tw.WriteLine("[Aliases]");
|
||||||
|
tw.WriteLine($"public partial {method.ReturnType} {method.MethodName}({string.Join(", ", method.Params)});");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var _ in model.ClassHierarchy)
|
||||||
|
{
|
||||||
|
tw.Indent --;
|
||||||
|
tw.WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
tw.Flush();
|
||||||
|
return sw.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyCollection<FileModel> GetModels(Compilation compilation,
|
||||||
|
in ImmutableArray<MethodDeclarationSyntax?> inputMethods,
|
||||||
|
CancellationToken cancel)
|
||||||
|
{
|
||||||
|
var models = new List<FileModel>();
|
||||||
|
|
||||||
|
var methods = inputMethods
|
||||||
|
.Where(static x => x is not null)
|
||||||
|
.Distinct();
|
||||||
|
|
||||||
|
var methodModels = methods
|
||||||
|
.Select(x => MethodDeclarationToMethodModel(compilation, x!));
|
||||||
|
|
||||||
|
var groups = methodModels
|
||||||
|
.GroupBy(static x => $"{x.Namespace}.{string.Join(".", x.Classes)}");
|
||||||
|
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
if (cancel.IsCancellationRequested)
|
||||||
|
return new Collection<FileModel>();
|
||||||
|
|
||||||
|
if (group is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var elems = group.ToList();
|
||||||
|
if (elems.Count is 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var model = new FileModel(
|
||||||
|
methods: elems,
|
||||||
|
ns: elems[0].Namespace,
|
||||||
|
classHierarchy: elems[0].Classes
|
||||||
|
);
|
||||||
|
|
||||||
|
models.Add(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodModel MethodDeclarationToMethodModel(Compilation comp, MethodDeclarationSyntax decl)
|
||||||
|
{
|
||||||
|
// SpinWait.SpinUntil(static () => Debugger.IsAttached);
|
||||||
|
|
||||||
|
var semanticModel = comp.GetSemanticModel(decl.SyntaxTree);
|
||||||
|
var methodModel = new MethodModel(
|
||||||
|
@params: decl.ParameterList.Parameters
|
||||||
|
.Where(p => p.Type is not null)
|
||||||
|
.Select(p =>
|
||||||
|
{
|
||||||
|
var prefix = p.Modifiers.Any(static x => x.IsKind(SyntaxKind.ParamsKeyword))
|
||||||
|
? "params "
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
var type = semanticModel
|
||||||
|
.GetTypeInfo(p.Type!)
|
||||||
|
.Type
|
||||||
|
?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
|
|
||||||
|
|
||||||
|
var name = p.Identifier.Text;
|
||||||
|
|
||||||
|
var suffix = string.Empty;
|
||||||
|
if (p.Default is not null)
|
||||||
|
{
|
||||||
|
if (p.Default.Value is LiteralExpressionSyntax)
|
||||||
|
{
|
||||||
|
suffix = " = " + p.Default.Value;
|
||||||
|
}
|
||||||
|
else if (p.Default.Value is MemberAccessExpressionSyntax maes)
|
||||||
|
{
|
||||||
|
var maesSemModel = comp.GetSemanticModel(maes.SyntaxTree);
|
||||||
|
var sym = maesSemModel.GetSymbolInfo(maes.Name);
|
||||||
|
if (sym.Symbol is null)
|
||||||
|
{
|
||||||
|
suffix = " = " + p.Default.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
suffix = " = " + sym.Symbol.ToDisplayString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{prefix}{type} {name}{suffix}";
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
methodName: decl.Identifier.Text,
|
||||||
|
returnType: decl.ReturnType.ToString(),
|
||||||
|
ns: GetNamespace(decl),
|
||||||
|
classes: GetClasses(decl)
|
||||||
|
);
|
||||||
|
|
||||||
|
return methodModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
//https://github.com/andrewlock/NetEscapades.EnumGenerators/blob/main/src/NetEscapades.EnumGenerators/EnumGenerator.cs
|
||||||
|
static string? GetNamespace(MethodDeclarationSyntax declarationSyntax)
|
||||||
|
{
|
||||||
|
// determine the namespace the class is declared in, if any
|
||||||
|
string? nameSpace = null;
|
||||||
|
var parentOfInterest = declarationSyntax.Parent;
|
||||||
|
while (parentOfInterest is not null)
|
||||||
|
{
|
||||||
|
parentOfInterest = parentOfInterest.Parent;
|
||||||
|
|
||||||
|
if (parentOfInterest is BaseNamespaceDeclarationSyntax ns)
|
||||||
|
{
|
||||||
|
nameSpace = ns.Name.ToString();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (ns.Parent is not NamespaceDeclarationSyntax parent)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ns = parent;
|
||||||
|
nameSpace = $"{ns.Name}.{nameSpace}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IReadOnlyCollection<string> GetClasses(MethodDeclarationSyntax declarationSyntax)
|
||||||
|
{
|
||||||
|
// determine the namespace the class is declared in, if any
|
||||||
|
var classes = new LinkedList<string>();
|
||||||
|
var parentOfInterest = declarationSyntax.Parent;
|
||||||
|
while (parentOfInterest is not null)
|
||||||
|
{
|
||||||
|
if (parentOfInterest is ClassDeclarationSyntax cds)
|
||||||
|
{
|
||||||
|
classes.AddFirst(cds.Identifier.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
parentOfInterest = parentOfInterest.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine($"Method {declarationSyntax.Identifier.Text} has {classes.Count} classes");
|
||||||
|
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodDeclarationSyntax? Transform(GeneratorSyntaxContext ctx, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
var methodDecl = ctx.Node as MethodDeclarationSyntax;
|
||||||
|
if (methodDecl is null)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
foreach (var attListSyntax in methodDecl.AttributeLists)
|
||||||
|
{
|
||||||
|
foreach (var attSyntax in attListSyntax.Attributes)
|
||||||
|
{
|
||||||
|
if (cancel.IsCancellationRequested)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
var symbol = ctx.SemanticModel.GetSymbolInfo(attSyntax).Symbol;
|
||||||
|
if (symbol is not IMethodSymbol attSymbol)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (attSymbol.ContainingType.ToDisplayString() == "NadekoBot.Common.CmdAttribute")
|
||||||
|
return methodDecl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,26 +1,32 @@
|
|||||||
using System;
|
#nullable enable
|
||||||
|
using System;
|
||||||
using System.CodeDom.Compiler;
|
using System.CodeDom.Compiler;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.Text;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot.Generators
|
namespace NadekoBot.Generators
|
||||||
{
|
{
|
||||||
internal class TranslationPair
|
internal readonly struct TranslationPair
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; }
|
||||||
public string Value { get; set; }
|
public string Value { get; }
|
||||||
|
|
||||||
|
public TranslationPair(string name, string value)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Generator]
|
[Generator]
|
||||||
public class LocalizedStringsGenerator : ISourceGenerator
|
public class LocalizedStringsGenerator : ISourceGenerator
|
||||||
{
|
{
|
||||||
private const string LocStrSource = @"namespace NadekoBot
|
private const string LOC_STR_SOURCE = @"namespace NadekoBot
|
||||||
{
|
{
|
||||||
public readonly struct LocStr
|
public readonly struct LocStr
|
||||||
{
|
{
|
||||||
@@ -49,9 +55,8 @@ namespace NadekoBot.Generators
|
|||||||
using (var stringWriter = new StringWriter())
|
using (var stringWriter = new StringWriter())
|
||||||
using (var sw = new IndentedTextWriter(stringWriter))
|
using (var sw = new IndentedTextWriter(stringWriter))
|
||||||
{
|
{
|
||||||
sw.WriteLine("namespace NadekoBot");
|
sw.WriteLine("namespace NadekoBot;");
|
||||||
sw.WriteLine("{");
|
sw.WriteLine();
|
||||||
sw.Indent++;
|
|
||||||
|
|
||||||
sw.WriteLine("public static class strs");
|
sw.WriteLine("public static class strs");
|
||||||
sw.WriteLine("{");
|
sw.WriteLine("{");
|
||||||
@@ -84,32 +89,42 @@ namespace NadekoBot.Generators
|
|||||||
|
|
||||||
sw.Indent--;
|
sw.Indent--;
|
||||||
sw.WriteLine("}");
|
sw.WriteLine("}");
|
||||||
sw.Indent--;
|
|
||||||
sw.WriteLine("}");
|
|
||||||
|
|
||||||
|
|
||||||
sw.Flush();
|
sw.Flush();
|
||||||
context.AddSource("strs.cs", stringWriter.ToString());
|
context.AddSource("strs.g.cs", stringWriter.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AddSource("LocStr.cs", LocStrSource);
|
context.AddSource("LocStr.g.cs", LOC_STR_SOURCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TranslationPair> GetFields(string dataText)
|
private List<TranslationPair> GetFields(string? dataText)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(dataText))
|
if (string.IsNullOrWhiteSpace(dataText))
|
||||||
throw new ArgumentNullException(nameof(dataText));
|
return new();
|
||||||
|
|
||||||
var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(dataText);
|
Dictionary<string, string> data;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var output = JsonConvert.DeserializeObject<Dictionary<string, string>>(dataText!);
|
||||||
|
if (output is null)
|
||||||
|
return new();
|
||||||
|
|
||||||
|
data = output;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Debug.WriteLine("Failed parsing responses file.");
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
|
||||||
var list = new List<TranslationPair>();
|
var list = new List<TranslationPair>();
|
||||||
foreach (var entry in data)
|
foreach (var entry in data)
|
||||||
{
|
{
|
||||||
list.Add(new TranslationPair()
|
list.Add(new(
|
||||||
{
|
entry.Key,
|
||||||
Name = entry.Key,
|
entry.Value
|
||||||
Value = entry.Value
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
|
<IsRoslynComponent>true</IsRoslynComponent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" 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.1" PrivateAssets="all" GeneratePathProperty="true" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@ namespace NadekoBot.Tests
|
|||||||
private const string responsesPath = "../../../../NadekoBot/data/strings/responses";
|
private const string responsesPath = "../../../../NadekoBot/data/strings/responses";
|
||||||
private const string commandsPath = "../../../../NadekoBot/data/strings/commands";
|
private const string commandsPath = "../../../../NadekoBot/data/strings/commands";
|
||||||
private const string aliasesPath = "../../../../NadekoBot/data/aliases.yml";
|
private const string aliasesPath = "../../../../NadekoBot/data/aliases.yml";
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void AllCommandNamesHaveStrings()
|
public void AllCommandNamesHaveStrings()
|
||||||
{
|
{
|
||||||
@@ -25,15 +26,13 @@ namespace NadekoBot.Tests
|
|||||||
var culture = new CultureInfo("en-US");
|
var culture = new CultureInfo("en-US");
|
||||||
|
|
||||||
var isSuccess = true;
|
var isSuccess = true;
|
||||||
foreach (var entry in CommandNameLoadHelper.LoadCommandNames(aliasesPath))
|
foreach (var (methodName, _) in CommandNameLoadHelper.LoadAliases(aliasesPath))
|
||||||
{
|
{
|
||||||
var commandName = entry.Value[0];
|
var cmdStrings = strings.GetCommandStrings(culture.Name, methodName);
|
||||||
|
|
||||||
var cmdStrings = strings.GetCommandStrings(culture.Name, commandName);
|
|
||||||
if (cmdStrings is null)
|
if (cmdStrings is null)
|
||||||
{
|
{
|
||||||
isSuccess = false;
|
isSuccess = false;
|
||||||
TestContext.Out.WriteLine($"{commandName} doesn't exist in commands.en-US.yml");
|
TestContext.Out.WriteLine($"{methodName} doesn't exist in commands.en-US.yml");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ namespace NadekoBot.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static string[] GetCommandMethodNames()
|
private static string[] GetCommandMethodNames()
|
||||||
=> typeof(NadekoBot.Bot).Assembly
|
=> typeof(Bot).Assembly
|
||||||
.GetExportedTypes()
|
.GetExportedTypes()
|
||||||
.Where(type => type.IsClass && !type.IsAbstract)
|
.Where(type => type.IsClass && !type.IsAbstract)
|
||||||
.Where(type => typeof(NadekoModule).IsAssignableFrom(type) // if its a top level module
|
.Where(type => typeof(NadekoModule).IsAssignableFrom(type) // if its a top level module
|
||||||
@@ -55,7 +54,7 @@ namespace NadekoBot.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void AllCommandMethodsHaveNames()
|
public void AllCommandMethodsHaveNames()
|
||||||
{
|
{
|
||||||
var allAliases = CommandNameLoadHelper.LoadCommandNames(
|
var allAliases = CommandNameLoadHelper.LoadAliases(
|
||||||
aliasesPath);
|
aliasesPath);
|
||||||
|
|
||||||
var methodNames = GetCommandMethodNames();
|
var methodNames = GetCommandMethodNames();
|
||||||
@@ -63,7 +62,7 @@ namespace NadekoBot.Tests
|
|||||||
var isSuccess = true;
|
var isSuccess = true;
|
||||||
foreach (var methodName in methodNames)
|
foreach (var methodName in methodNames)
|
||||||
{
|
{
|
||||||
if (!allAliases.TryGetValue(methodName, out var _))
|
if (!allAliases.TryGetValue(methodName, out _))
|
||||||
{
|
{
|
||||||
TestContext.Error.WriteLine($"{methodName} is missing an alias.");
|
TestContext.Error.WriteLine($"{methodName} is missing an alias.");
|
||||||
isSuccess = false;
|
isSuccess = false;
|
||||||
@@ -76,7 +75,7 @@ namespace NadekoBot.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void NoObsoleteAliases()
|
public void NoObsoleteAliases()
|
||||||
{
|
{
|
||||||
var allAliases = CommandNameLoadHelper.LoadCommandNames(aliasesPath);
|
var allAliases = CommandNameLoadHelper.LoadAliases(aliasesPath);
|
||||||
|
|
||||||
var methodNames = GetCommandMethodNames()
|
var methodNames = GetCommandMethodNames()
|
||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
@@ -94,7 +93,10 @@ namespace NadekoBot.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsTrue(isSuccess);
|
if(isSuccess)
|
||||||
|
Assert.Pass();
|
||||||
|
else
|
||||||
|
Assert.Warn("There are some unused entries in data/aliases.yml");
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Test]
|
// [Test]
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NadekoBot.Extensions;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
@@ -13,9 +12,7 @@ namespace NadekoBot.Tests
|
|||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
=> _grouper = new GreetGrouper<int>();
|
||||||
_grouper = new GreetGrouper<int>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void CreateTest()
|
public void CreateTest()
|
||||||
@@ -62,8 +59,8 @@ namespace NadekoBot.Tests
|
|||||||
_grouper.CreateOrAdd(0, 5);
|
_grouper.CreateOrAdd(0, 5);
|
||||||
|
|
||||||
// add 15 items
|
// add 15 items
|
||||||
await Task.WhenAll(Enumerable.Range(10, 15)
|
await Enumerable.Range(10, 15)
|
||||||
.Select(x => Task.Run(() => _grouper.CreateOrAdd(0, x))));
|
.Select(x => Task.Run(() => _grouper.CreateOrAdd(0, x))).WhenAll();
|
||||||
|
|
||||||
// get 5 at most
|
// get 5 at most
|
||||||
_grouper.ClearGroup(0, 5, out var items);
|
_grouper.ClearGroup(0, 5, out var items);
|
||||||
|
@@ -62,7 +62,10 @@ namespace NadekoBot.Tests
|
|||||||
collection.Clear();
|
collection.Clear();
|
||||||
|
|
||||||
Assert.IsTrue(collection.Count == 0, "Collection has not been cleared.");
|
Assert.IsTrue(collection.Count == 0, "Collection has not been cleared.");
|
||||||
Assert.Throws<ArgumentOutOfRangeException>(() => collection.Contains(collection[0]), "Collection has not been cleared.");
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||||
|
{
|
||||||
|
_ = collection[0];
|
||||||
|
}, "Collection has not been cleared.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -115,7 +118,7 @@ namespace NadekoBot.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void ContainsTest()
|
public void ContainsTest()
|
||||||
{
|
{
|
||||||
var subCol = new ShopEntry[]
|
var subCol = new[]
|
||||||
{
|
{
|
||||||
new ShopEntry() { Id = 111 },
|
new ShopEntry() { Id = 111 },
|
||||||
new ShopEntry() { Id = 222 },
|
new ShopEntry() { Id = 222 },
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using System.Linq;
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
namespace NadekoBot.Tests
|
namespace NadekoBot.Tests
|
||||||
|
@@ -1,15 +1,16 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<LangVersion>9.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
|
<EnablePreviewFeatures>True</EnablePreviewFeatures>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -9,9 +9,7 @@ namespace NadekoBot.Tests
|
|||||||
{
|
{
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
=> Console.OutputEncoding = Encoding.UTF8;
|
||||||
Console.OutputEncoding = Encoding.UTF8;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Utf8CodepointsToEmoji()
|
public void Utf8CodepointsToEmoji()
|
||||||
|
25
src/NadekoBot.VotesApi/.dockerignore
Normal file
25
src/NadekoBot.VotesApi/.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/.idea
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
1
src/NadekoBot.VotesApi/.gitignore
vendored
Normal file
1
src/NadekoBot.VotesApi/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
store/
|
41
src/NadekoBot.VotesApi/Common/AuthHandler.cs
Normal file
41
src/NadekoBot.VotesApi/Common/AuthHandler.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class AuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||||
|
{
|
||||||
|
public const string SchemeName = "AUTHORIZATION_SCHEME";
|
||||||
|
public const string DiscordsClaim = "DISCORDS_CLAIM";
|
||||||
|
public const string TopggClaim = "TOPGG_CLAIM";
|
||||||
|
|
||||||
|
private readonly IConfiguration _conf;
|
||||||
|
|
||||||
|
public AuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||||
|
ILoggerFactory logger,
|
||||||
|
UrlEncoder encoder,
|
||||||
|
ISystemClock clock,
|
||||||
|
IConfiguration conf)
|
||||||
|
: base(options, logger, encoder, clock)
|
||||||
|
=> _conf = conf;
|
||||||
|
|
||||||
|
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>();
|
||||||
|
|
||||||
|
if (_conf[ConfKeys.DISCORDS_KEY].Trim() == Request.Headers["Authorization"].ToString().Trim())
|
||||||
|
claims.Add(new(DiscordsClaim, "true"));
|
||||||
|
|
||||||
|
if (_conf[ConfKeys.TOPGG_KEY] == Request.Headers["Authorization"].ToString().Trim())
|
||||||
|
claims.Add(new Claim(TopggClaim, "true"));
|
||||||
|
|
||||||
|
return Task.FromResult(AuthenticateResult.Success(new(new(new ClaimsIdentity(claims)), SchemeName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/NadekoBot.VotesApi/Common/ConfKeys.cs
Normal file
8
src/NadekoBot.VotesApi/Common/ConfKeys.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public static class ConfKeys
|
||||||
|
{
|
||||||
|
public const string DISCORDS_KEY = "DiscordsKey";
|
||||||
|
public const string TOPGG_KEY = "TopGGKey";
|
||||||
|
}
|
||||||
|
}
|
26
src/NadekoBot.VotesApi/Common/DiscordsVoteWebhookModel.cs
Normal file
26
src/NadekoBot.VotesApi/Common/DiscordsVoteWebhookModel.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class DiscordsVoteWebhookModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the user who voted
|
||||||
|
/// </summary>
|
||||||
|
public string User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the bot which recieved the vote
|
||||||
|
/// </summary>
|
||||||
|
public string Bot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains totalVotes, votesMonth, votes24, hasVoted - a list of IDs of users who have voted this month, and
|
||||||
|
/// Voted24 - a list of IDs of users who have voted today
|
||||||
|
/// </summary>
|
||||||
|
public string Votes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of event, whether it is a vote event or test event
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
src/NadekoBot.VotesApi/Common/Policies.cs
Normal file
8
src/NadekoBot.VotesApi/Common/Policies.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public static class Policies
|
||||||
|
{
|
||||||
|
public const string DiscordsAuth = "DiscordsAuth";
|
||||||
|
public const string TopggAuth = "TopggAuth";
|
||||||
|
}
|
||||||
|
}
|
30
src/NadekoBot.VotesApi/Common/TopggVoteWebhookModel.cs
Normal file
30
src/NadekoBot.VotesApi/Common/TopggVoteWebhookModel.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class TopggVoteWebhookModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Discord ID of the bot that received a vote.
|
||||||
|
/// </summary>
|
||||||
|
public string Bot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discord ID of the user who voted.
|
||||||
|
/// </summary>
|
||||||
|
public string User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the vote (should always be "upvote" except when using the test button it's "test").
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the weekend multiplier is in effect, meaning users votes count as two.
|
||||||
|
/// </summary>
|
||||||
|
public bool Weekend { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query string params found on the /bot/:ID/vote page. Example: ?a=1&b=2.
|
||||||
|
/// </summary>
|
||||||
|
public string Query { get; set; }
|
||||||
|
}
|
||||||
|
}
|
33
src/NadekoBot.VotesApi/Controllers/DiscordsController.cs
Normal file
33
src/NadekoBot.VotesApi/Controllers/DiscordsController.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NadekoBot.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class DiscordsController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<DiscordsController> _logger;
|
||||||
|
private readonly IVotesCache _cache;
|
||||||
|
|
||||||
|
public DiscordsController(ILogger<DiscordsController> logger, IVotesCache cache)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("new")]
|
||||||
|
[Authorize(Policy = Policies.DiscordsAuth)]
|
||||||
|
public async Task<IEnumerable<Vote>> New()
|
||||||
|
{
|
||||||
|
var votes = await _cache.GetNewDiscordsVotesAsync();
|
||||||
|
if(votes.Count > 0)
|
||||||
|
_logger.LogInformation("Sending {NewDiscordsVotes} new discords votes", votes.Count);
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/NadekoBot.VotesApi/Controllers/TopGgController.cs
Normal file
34
src/NadekoBot.VotesApi/Controllers/TopGgController.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NadekoBot.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class TopGgController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<TopGgController> _logger;
|
||||||
|
private readonly IVotesCache _cache;
|
||||||
|
|
||||||
|
public TopGgController(ILogger<TopGgController> logger, IVotesCache cache)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("new")]
|
||||||
|
[Authorize(Policy = Policies.TopggAuth)]
|
||||||
|
public async Task<IEnumerable<Vote>> New()
|
||||||
|
{
|
||||||
|
var votes = await _cache.GetNewTopGgVotesAsync();
|
||||||
|
if(votes.Count > 0)
|
||||||
|
_logger.LogInformation("Sending {NewTopggVotes} new topgg votes", votes.Count);
|
||||||
|
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
src/NadekoBot.VotesApi/Controllers/WebhookController.cs
Normal file
48
src/NadekoBot.VotesApi/Controllers/WebhookController.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NadekoBot.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
public class WebhookController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<WebhookController> _logger;
|
||||||
|
private readonly IVotesCache _votesCache;
|
||||||
|
|
||||||
|
public WebhookController(ILogger<WebhookController> logger, IVotesCache votesCache)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_votesCache = votesCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("/discordswebhook")]
|
||||||
|
[Authorize(Policy = Policies.DiscordsAuth)]
|
||||||
|
public async Task<IActionResult> DiscordsWebhook([FromBody]DiscordsVoteWebhookModel data)
|
||||||
|
{
|
||||||
|
|
||||||
|
_logger.LogInformation("User {UserId} has voted for Bot {BotId} on {Platform}",
|
||||||
|
data.User,
|
||||||
|
data.Bot,
|
||||||
|
"discords.com");
|
||||||
|
|
||||||
|
await _votesCache.AddNewDiscordsVote(data.User);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("/topggwebhook")]
|
||||||
|
[Authorize(Policy = Policies.TopggAuth)]
|
||||||
|
public async Task<IActionResult> TopggWebhook([FromBody] TopggVoteWebhookModel data)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("User {UserId} has voted for Bot {BotId} on {Platform}",
|
||||||
|
data.User,
|
||||||
|
data.Bot,
|
||||||
|
"top.gg");
|
||||||
|
|
||||||
|
await _votesCache.AddNewTopggVote(data.User);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/NadekoBot.VotesApi/Dockerfile
Normal file
20
src/NadekoBot.VotesApi/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj", "NadekoBot.VotesApi/"]
|
||||||
|
RUN dotnet restore "src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/NadekoBot.VotesApi"
|
||||||
|
RUN dotnet build "NadekoBot.VotesApi.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "NadekoBot.VotesApi.csproj" -c Release -o /app/publish
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "NadekoBot.VotesApi.dll"]
|
13
src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj
Normal file
13
src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
9
src/NadekoBot.VotesApi/Program.cs
Normal file
9
src/NadekoBot.VotesApi/Program.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using NadekoBot.VotesApi;
|
||||||
|
|
||||||
|
CreateHostBuilder(args).Build().Run();
|
||||||
|
|
||||||
|
static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
|
Host.CreateDefaultBuilder(args)
|
||||||
|
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
31
src/NadekoBot.VotesApi/Properties/launchSettings.json
Normal file
31
src/NadekoBot.VotesApi/Properties/launchSettings.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:16451",
|
||||||
|
"sslPort": 44323
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"NadekoBot.VotesApi": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": "true",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/NadekoBot.VotesApi/README.md
Normal file
46
src/NadekoBot.VotesApi/README.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
## Votes Api
|
||||||
|
|
||||||
|
This api is used if you want your bot to be able to reward users who vote for it on discords.com or top.gg
|
||||||
|
|
||||||
|
#### [GET] `/discords/new`
|
||||||
|
Get the discords votes received after previous call to this endpoint.
|
||||||
|
Input full url of this endpoint in your creds.yml file under Discords url field.
|
||||||
|
For example "https://api.my.cool.bot/discords/new"
|
||||||
|
#### [GET] `/topgg/new`
|
||||||
|
Get the topgg votes received after previous call to this endpoint.
|
||||||
|
Input full url of this endpoint in your creds.yml file under Topgg url field.
|
||||||
|
For example "https://api.my.cool.bot/topgg/new"
|
||||||
|
|
||||||
|
#### [POST] `/discordswebhook`
|
||||||
|
Input this endpoint as the webhook on discords.com bot edit page
|
||||||
|
model: https://docs.botsfordiscord.com/methods/receiving-votes
|
||||||
|
For example "https://api.my.cool.bot/topggwebhook"
|
||||||
|
#### [POST] `/topggwebhook`
|
||||||
|
Input this endpoint as the webhook https://top.gg/bot/:your-bot-id/webhooks (replace :your-bot-id with your bot's id)
|
||||||
|
model: https://docs.top.gg/resources/webhooks/#schema
|
||||||
|
For example "https://api.my.cool.bot/discordswebhook"
|
||||||
|
|
||||||
|
Input your super-secret header value in appsettings.json's DiscordsKey and TopGGKey fields
|
||||||
|
They must match your DiscordsKey and TopGG key respectively, as well as your secrets in the discords.com and top.gg webhook setup pages
|
||||||
|
|
||||||
|
Full Example:
|
||||||
|
|
||||||
|
⚠ Change TopggKey and DiscordsKey to a secure long string
|
||||||
|
⚠ You can use https://www.random.org/strings/?num=1&len=20&digits=on&upperalpha=on&loweralpha=on&unique=on&format=html&rnd=new to generate it
|
||||||
|
|
||||||
|
`creds.yml`
|
||||||
|
```yml
|
||||||
|
votes:
|
||||||
|
TopggServiceUrl: "https://api.my.cool.bot/topgg"
|
||||||
|
TopggKey: "my_topgg_key"
|
||||||
|
DiscordsServiceUrl: "https://api.my.cool.bot/discords"
|
||||||
|
DiscordsKey: "my_discords_key"
|
||||||
|
```
|
||||||
|
|
||||||
|
`appsettings.json`
|
||||||
|
```json
|
||||||
|
...
|
||||||
|
"DiscordsKey": "my_discords_key",
|
||||||
|
"TopGGKey": "my_topgg_key",
|
||||||
|
...
|
||||||
|
```
|
100
src/NadekoBot.VotesApi/Services/FileVotesCache.cs
Normal file
100
src/NadekoBot.VotesApi/Services/FileVotesCache.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using MorseCode.ITask;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi.Services
|
||||||
|
{
|
||||||
|
public class FileVotesCache : IVotesCache
|
||||||
|
{
|
||||||
|
// private const string STATS_FILE = "store/stats.json";
|
||||||
|
private const string TOPGG_FILE = "store/topgg.json";
|
||||||
|
private const string DISCORDS_FILE = "store/discords.json";
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
public FileVotesCache()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists("store"))
|
||||||
|
Directory.CreateDirectory("store");
|
||||||
|
|
||||||
|
if(!File.Exists(TOPGG_FILE))
|
||||||
|
File.WriteAllText(TOPGG_FILE, "[]");
|
||||||
|
|
||||||
|
if(!File.Exists(DISCORDS_FILE))
|
||||||
|
File.WriteAllText(DISCORDS_FILE, "[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask AddNewTopggVote(string userId)
|
||||||
|
=> AddNewVote(TOPGG_FILE, userId);
|
||||||
|
|
||||||
|
public ITask AddNewDiscordsVote(string userId)
|
||||||
|
=> AddNewVote(DISCORDS_FILE, userId);
|
||||||
|
|
||||||
|
private async ITask AddNewVote(string file, string userId)
|
||||||
|
{
|
||||||
|
await _locker.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var votes = await GetVotesAsync(file);
|
||||||
|
votes.Add(userId);
|
||||||
|
await File.WriteAllTextAsync(file , JsonSerializer.Serialize(votes));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_locker.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ITask<IList<Vote>> GetNewTopGgVotesAsync()
|
||||||
|
{
|
||||||
|
var votes = await EvictTopggVotes();
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ITask<IList<Vote>> GetNewDiscordsVotesAsync()
|
||||||
|
{
|
||||||
|
var votes = await EvictDiscordsVotes();
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ITask<List<Vote>> EvictTopggVotes()
|
||||||
|
=> EvictVotes(TOPGG_FILE);
|
||||||
|
|
||||||
|
private ITask<List<Vote>> EvictDiscordsVotes()
|
||||||
|
=> EvictVotes(DISCORDS_FILE);
|
||||||
|
|
||||||
|
private async ITask<List<Vote>> EvictVotes(string file)
|
||||||
|
{
|
||||||
|
await _locker.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
var ids = await GetVotesAsync(file);
|
||||||
|
await File.WriteAllTextAsync(file, "[]");
|
||||||
|
|
||||||
|
return ids?
|
||||||
|
.Select(x => (Ok: ulong.TryParse(x, out var r), Id: r))
|
||||||
|
.Where(x => x.Ok)
|
||||||
|
.Select(x => new Vote
|
||||||
|
{
|
||||||
|
UserId = x.Id
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_locker.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ITask<IList<string>> GetVotesAsync(string file)
|
||||||
|
{
|
||||||
|
await using var fs = File.Open(file, FileMode.Open);
|
||||||
|
var votes = await JsonSerializer.DeserializeAsync<List<string>>(fs);
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/NadekoBot.VotesApi/Services/IVotesCache.cs
Normal file
13
src/NadekoBot.VotesApi/Services/IVotesCache.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using MorseCode.ITask;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi.Services
|
||||||
|
{
|
||||||
|
public interface IVotesCache
|
||||||
|
{
|
||||||
|
ITask<IList<Vote>> GetNewTopGgVotesAsync();
|
||||||
|
ITask<IList<Vote>> GetNewDiscordsVotesAsync();
|
||||||
|
ITask AddNewTopggVote(string userId);
|
||||||
|
ITask AddNewDiscordsVote(string userId);
|
||||||
|
}
|
||||||
|
}
|
68
src/NadekoBot.VotesApi/Startup.cs
Normal file
68
src/NadekoBot.VotesApi/Startup.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using NadekoBot.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
=> Configuration = configuration;
|
||||||
|
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddControllers();
|
||||||
|
services.AddSingleton<IVotesCache, FileVotesCache>();
|
||||||
|
services.AddSwaggerGen(static c =>
|
||||||
|
{
|
||||||
|
c.SwaggerDoc("v1", new OpenApiInfo { Title = "NadekoBot.VotesApi", Version = "v1" });
|
||||||
|
});
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddAuthentication(opts =>
|
||||||
|
{
|
||||||
|
opts.DefaultScheme = AuthHandler.SchemeName;
|
||||||
|
opts.AddScheme<AuthHandler>(AuthHandler.SchemeName, AuthHandler.SchemeName);
|
||||||
|
});
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddAuthorization(static opts =>
|
||||||
|
{
|
||||||
|
opts.DefaultPolicy = new AuthorizationPolicyBuilder(AuthHandler.SchemeName)
|
||||||
|
.RequireAssertion(static _ => false)
|
||||||
|
.Build();
|
||||||
|
opts.AddPolicy(Policies.DiscordsAuth, static policy => policy.RequireClaim(AuthHandler.DiscordsClaim));
|
||||||
|
opts.AddPolicy(Policies.TopggAuth, static policy => policy.RequireClaim(AuthHandler.TopggClaim));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI(static c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "NadekoBot.VotesApi v1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseEndpoints(static endpoints => { endpoints.MapControllers(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/NadekoBot.VotesApi/WeatherForecast.cs
Normal file
7
src/NadekoBot.VotesApi/WeatherForecast.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class Vote
|
||||||
|
{
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
9
src/NadekoBot.VotesApi/appsettings.Development.json
Normal file
9
src/NadekoBot.VotesApi/appsettings.Development.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/NadekoBot.VotesApi/appsettings.json
Normal file
12
src/NadekoBot.VotesApi/appsettings.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DiscordsKey": "my_discords_key",
|
||||||
|
"TopGGKey": "my_topgg_key",
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
359
src/NadekoBot/.editorconfig
Normal file
359
src/NadekoBot/.editorconfig
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
root = true
|
||||||
|
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||||
|
|
||||||
|
[obj/**]
|
||||||
|
generated_code = true
|
||||||
|
|
||||||
|
# C# files
|
||||||
|
[*.cs]
|
||||||
|
|
||||||
|
|
||||||
|
#### Core EditorConfig Options ####
|
||||||
|
|
||||||
|
# Indentation and spacing
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
#### .NET Coding Conventions ####
|
||||||
|
|
||||||
|
# Organize usings
|
||||||
|
dotnet_separate_import_directive_groups = false
|
||||||
|
dotnet_sort_system_directives_first = false
|
||||||
|
|
||||||
|
# this. and Me. preferences
|
||||||
|
dotnet_style_qualification_for_event = false
|
||||||
|
dotnet_style_qualification_for_field = false
|
||||||
|
dotnet_style_qualification_for_method = false
|
||||||
|
dotnet_style_qualification_for_property = false
|
||||||
|
|
||||||
|
# Language keywords vs BCL types preferences
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||||
|
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||||
|
|
||||||
|
# Parentheses preferences
|
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
|
||||||
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
dotnet_style_require_accessibility_modifiers = always:error
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
dotnet_style_coalesce_expression = true
|
||||||
|
dotnet_style_collection_initializer = true
|
||||||
|
dotnet_style_explicit_tuple_names = true
|
||||||
|
dotnet_style_namespace_match_folder = true
|
||||||
|
dotnet_style_null_propagation = true
|
||||||
|
dotnet_style_object_initializer = true
|
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||||
|
dotnet_style_prefer_auto_properties = true:warning
|
||||||
|
dotnet_style_prefer_compound_assignment = true
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = false:suggestion
|
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||||
|
dotnet_style_prefer_inferred_tuple_names = true
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true
|
||||||
|
|
||||||
|
# Field preferences
|
||||||
|
dotnet_style_readonly_field = true:suggestion
|
||||||
|
|
||||||
|
# Parameter preferences
|
||||||
|
dotnet_code_quality_unused_parameters = all:warning
|
||||||
|
|
||||||
|
#### C# Coding Conventions ####
|
||||||
|
|
||||||
|
# var preferences
|
||||||
|
csharp_style_var_elsewhere = true
|
||||||
|
csharp_style_var_for_built_in_types = true:suggestion
|
||||||
|
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||||
|
|
||||||
|
# Expression-bodied members
|
||||||
|
csharp_style_expression_bodied_accessors = true:suggestion
|
||||||
|
csharp_style_expression_bodied_constructors = when_on_single_line:suggestion
|
||||||
|
csharp_style_expression_bodied_indexers = true:suggestion
|
||||||
|
csharp_style_expression_bodied_lambdas = true:suggestion
|
||||||
|
csharp_style_expression_bodied_local_functions = true:suggestion
|
||||||
|
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
|
||||||
|
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
|
||||||
|
csharp_style_expression_bodied_properties = true:suggestion
|
||||||
|
|
||||||
|
# Pattern matching preferences
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true:error
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true:error
|
||||||
|
csharp_style_prefer_not_pattern = true:error
|
||||||
|
csharp_style_prefer_pattern_matching = true:suggestion
|
||||||
|
csharp_style_prefer_switch_expression = true
|
||||||
|
|
||||||
|
# Null-checking preferences
|
||||||
|
csharp_style_conditional_delegate_call = true:error
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
csharp_prefer_static_local_function = true
|
||||||
|
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async
|
||||||
|
|
||||||
|
# Code-block preferences
|
||||||
|
csharp_prefer_braces = when_multiline:warning
|
||||||
|
csharp_prefer_simple_using_statement = true
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
csharp_prefer_simple_default_expression = true
|
||||||
|
csharp_style_deconstructed_variable_declaration = true
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:error
|
||||||
|
csharp_style_inlined_variable_declaration = true:warning
|
||||||
|
csharp_style_pattern_local_over_anonymous_function = true
|
||||||
|
csharp_style_prefer_index_operator = true
|
||||||
|
csharp_style_prefer_range_operator = true
|
||||||
|
csharp_style_throw_expression = true:error
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable:warning
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||||
|
|
||||||
|
# 'using' directive preferences
|
||||||
|
csharp_using_directive_placement = outside_namespace:error
|
||||||
|
|
||||||
|
# Enforce file-scoped namespaces
|
||||||
|
csharp_style_namespace_declarations = file_scoped:error
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
|
||||||
|
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
|
||||||
|
csharp_style_allow_embedded_statements_on_same_line_experimental = false
|
||||||
|
|
||||||
|
#### C# Formatting Rules ####
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
csharp_new_line_before_catch = true
|
||||||
|
csharp_new_line_before_else = true
|
||||||
|
csharp_new_line_before_finally = true
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_open_brace = all
|
||||||
|
csharp_new_line_between_query_expression_clauses = true
|
||||||
|
|
||||||
|
# Indentation preferences
|
||||||
|
csharp_indent_block_contents = true
|
||||||
|
csharp_indent_braces = false
|
||||||
|
csharp_indent_case_contents = true
|
||||||
|
csharp_indent_case_contents_when_block = true
|
||||||
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_indent_switch_labels = true
|
||||||
|
|
||||||
|
# Space preferences
|
||||||
|
csharp_space_after_cast = false
|
||||||
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_after_comma = true
|
||||||
|
csharp_space_after_dot = false
|
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_space_around_declaration_statements = false
|
||||||
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_before_comma = false
|
||||||
|
csharp_space_before_dot = false
|
||||||
|
csharp_space_before_open_square_brackets = false
|
||||||
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
|
csharp_space_between_empty_square_brackets = false
|
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||||
|
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_parentheses = false
|
||||||
|
csharp_space_between_square_brackets = false
|
||||||
|
|
||||||
|
# Wrapping preferences
|
||||||
|
csharp_preserve_single_line_blocks = true
|
||||||
|
csharp_preserve_single_line_statements = false
|
||||||
|
|
||||||
|
#### Naming styles ####
|
||||||
|
|
||||||
|
# Naming rules
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_readonly_field.symbols = private_readonly_field
|
||||||
|
dotnet_naming_rule.private_readonly_field.style = begins_with_underscore
|
||||||
|
dotnet_naming_rule.private_readonly_field.severity = warning
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_field.symbols = private_field
|
||||||
|
dotnet_naming_rule.private_field.style = camel_case
|
||||||
|
dotnet_naming_rule.private_field.severity = warning
|
||||||
|
|
||||||
|
dotnet_naming_rule.const_fields.symbols = const_fields
|
||||||
|
dotnet_naming_rule.const_fields.style = all_upper
|
||||||
|
dotnet_naming_rule.const_fields.severity = warning
|
||||||
|
|
||||||
|
# dotnet_naming_rule.class_should_be_pascal_case.severity = error
|
||||||
|
# dotnet_naming_rule.class_should_be_pascal_case.symbols = class
|
||||||
|
# dotnet_naming_rule.class_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.struct_should_be_pascal_case.severity = error
|
||||||
|
dotnet_naming_rule.struct_should_be_pascal_case.symbols = struct
|
||||||
|
dotnet_naming_rule.struct_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.severity = error
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||||
|
|
||||||
|
# dotnet_naming_rule.types_should_be_pascal_case.severity = error
|
||||||
|
# dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||||
|
# dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
# dotnet_naming_rule.enum_should_be_pascal_case.severity = error
|
||||||
|
# dotnet_naming_rule.enum_should_be_pascal_case.symbols = enum
|
||||||
|
# dotnet_naming_rule.enum_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
# dotnet_naming_rule.property_should_be_pascal_case.severity = error
|
||||||
|
# dotnet_naming_rule.property_should_be_pascal_case.symbols = property
|
||||||
|
# dotnet_naming_rule.property_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.method_should_be_pascal_case.severity = error
|
||||||
|
dotnet_naming_rule.method_should_be_pascal_case.symbols = method
|
||||||
|
dotnet_naming_rule.method_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.async_method_should_be_ends_with_async.severity = error
|
||||||
|
dotnet_naming_rule.async_method_should_be_ends_with_async.symbols = async_method
|
||||||
|
dotnet_naming_rule.async_method_should_be_ends_with_async.style = ends_with_async
|
||||||
|
|
||||||
|
# dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = error
|
||||||
|
# dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||||
|
# dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.local_variable_should_be_camel_case.severity = error
|
||||||
|
dotnet_naming_rule.local_variable_should_be_camel_case.symbols = local_variable
|
||||||
|
dotnet_naming_rule.local_variable_should_be_camel_case.style = camel_case
|
||||||
|
|
||||||
|
# Symbol specifications
|
||||||
|
|
||||||
|
dotnet_naming_symbols.const_fields.required_modifiers = const
|
||||||
|
dotnet_naming_symbols.const_fields.applicable_kinds = field
|
||||||
|
|
||||||
|
dotnet_naming_symbols.class.applicable_kinds = class
|
||||||
|
dotnet_naming_symbols.class.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.class.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.interface.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.struct.applicable_kinds = struct
|
||||||
|
dotnet_naming_symbols.struct.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.struct.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.enum.applicable_kinds = enum
|
||||||
|
dotnet_naming_symbols.enum.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.enum.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.method.applicable_kinds = method
|
||||||
|
dotnet_naming_symbols.method.applicable_accessibilities = public
|
||||||
|
dotnet_naming_symbols.method.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.property.applicable_kinds = property
|
||||||
|
dotnet_naming_symbols.property.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.property.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.types.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_readonly_field.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_readonly_field.applicable_accessibilities = private, protected
|
||||||
|
dotnet_naming_symbols.private_readonly_field.required_modifiers = readonly
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_field.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_field.applicable_accessibilities = private, protected
|
||||||
|
dotnet_naming_symbols.private_field.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.async_method.applicable_kinds = method, local_function
|
||||||
|
dotnet_naming_symbols.async_method.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.async_method.required_modifiers = async
|
||||||
|
|
||||||
|
dotnet_naming_symbols.local_variable.applicable_kinds = parameter, local
|
||||||
|
dotnet_naming_symbols.local_variable.applicable_accessibilities = local
|
||||||
|
dotnet_naming_symbols.local_variable.required_modifiers =
|
||||||
|
|
||||||
|
# Naming styles
|
||||||
|
|
||||||
|
|
||||||
|
dotnet_naming_style.all_upper.capitalization = all_upper
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix =
|
||||||
|
dotnet_naming_style.pascal_case.required_suffix =
|
||||||
|
dotnet_naming_style.pascal_case.word_separator =
|
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||||
|
dotnet_naming_style.begins_with_i.required_suffix =
|
||||||
|
dotnet_naming_style.begins_with_i.word_separator =
|
||||||
|
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_underscore.required_prefix = _
|
||||||
|
dotnet_naming_style.begins_with_underscore.required_suffix =
|
||||||
|
dotnet_naming_style.begins_with_underscore.word_separator =
|
||||||
|
dotnet_naming_style.begins_with_underscore.capitalization = camel_case
|
||||||
|
|
||||||
|
dotnet_naming_style.ends_with_async.required_prefix =
|
||||||
|
# dotnet_naming_style.ends_with_async.required_suffix = Async
|
||||||
|
dotnet_naming_style.ends_with_async.word_separator =
|
||||||
|
dotnet_naming_style.ends_with_async.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.camel_case.required_prefix =
|
||||||
|
dotnet_naming_style.camel_case.required_suffix =
|
||||||
|
dotnet_naming_style.camel_case.word_separator =
|
||||||
|
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||||
|
|
||||||
|
# CA1822: Mark members as static
|
||||||
|
dotnet_diagnostic.ca1822.severity = suggestion
|
||||||
|
|
||||||
|
# IDE0004: Cast is redundant
|
||||||
|
dotnet_diagnostic.ide0004.severity = warning
|
||||||
|
|
||||||
|
# IDE0058: Expression value is never used
|
||||||
|
dotnet_diagnostic.ide0058.severity = none
|
||||||
|
|
||||||
|
# # IDE0011: Add braces to 'if'/'else' statement
|
||||||
|
# dotnet_diagnostic.ide0011.severity = none
|
||||||
|
|
||||||
|
resharper_wrap_after_invocation_lpar = false
|
||||||
|
resharper_wrap_before_invocation_rpar = false
|
||||||
|
|
||||||
|
# ReSharper properties
|
||||||
|
resharper_align_multiline_calls_chain = true
|
||||||
|
resharper_csharp_wrap_after_declaration_lpar = true
|
||||||
|
resharper_csharp_wrap_after_invocation_lpar = false
|
||||||
|
resharper_csharp_wrap_before_binary_opsign = true
|
||||||
|
resharper_csharp_wrap_before_invocation_rpar = false
|
||||||
|
resharper_csharp_wrap_parameters_style = chop_if_long
|
||||||
|
resharper_force_chop_compound_if_expression = false
|
||||||
|
resharper_keep_existing_linebreaks = true
|
||||||
|
resharper_keep_user_linebreaks = true
|
||||||
|
resharper_max_formal_parameters_on_line = 3
|
||||||
|
resharper_place_simple_embedded_statement_on_same_line = false
|
||||||
|
resharper_wrap_chained_binary_expressions = chop_if_long
|
||||||
|
resharper_wrap_chained_binary_patterns = chop_if_long
|
||||||
|
resharper_wrap_chained_method_calls = chop_if_long
|
||||||
|
resharper_wrap_object_and_collection_initializer_style = chop_always
|
||||||
|
|
||||||
|
resharper_csharp_wrap_before_first_type_parameter_constraint = true
|
||||||
|
resharper_csharp_place_type_constraints_on_same_line = false
|
||||||
|
resharper_csharp_wrap_before_extends_colon = true
|
||||||
|
resharper_csharp_place_constructor_initializer_on_same_line = false
|
||||||
|
resharper_force_attribute_style = separate
|
||||||
|
resharper_csharp_braces_for_ifelse = required_for_multiline_statement
|
||||||
|
resharper_csharp_braces_for_foreach = required_for_multiline
|
||||||
|
resharper_csharp_braces_for_while = required_for_multiline
|
||||||
|
resharper_csharp_braces_for_for = required_for_multiline
|
||||||
|
resharper_arrange_redundant_parentheses_highlighting = hint
|
||||||
|
|
||||||
|
# IDE0011: Add braces
|
||||||
|
dotnet_diagnostic.IDE0011.severity = warning
|
@@ -1,35 +1,19 @@
|
|||||||
using Discord;
|
#nullable disable
|
||||||
using Discord.Commands;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using NadekoBot.Common;
|
using NadekoBot.Common.Configs;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Db;
|
||||||
|
using NadekoBot.Modules.Administration;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
using NadekoBot.Extensions;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using RunMode = Discord.Commands.RunMode;
|
||||||
using Discord.Net;
|
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
|
||||||
using NadekoBot.Common.Configs;
|
|
||||||
using NadekoBot.Db;
|
|
||||||
using NadekoBot.Modules.Administration.Services;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
public sealed class Bot
|
||||||
{
|
{
|
||||||
public sealed class Bot
|
|
||||||
{
|
|
||||||
private readonly IBotCredentials _creds;
|
|
||||||
private readonly CommandService _commandService;
|
|
||||||
private readonly DbService _db;
|
|
||||||
private readonly BotCredsProvider _credsProvider;
|
|
||||||
|
|
||||||
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
||||||
|
|
||||||
public DiscordSocketClient Client { get; }
|
public DiscordSocketClient Client { get; }
|
||||||
@@ -39,96 +23,115 @@ namespace NadekoBot
|
|||||||
|
|
||||||
public string Mention { get; private set; }
|
public string Mention { get; private set; }
|
||||||
public bool IsReady { get; private set; }
|
public bool IsReady { get; private set; }
|
||||||
|
public int ShardId { get; set; }
|
||||||
|
|
||||||
|
private readonly IBotCredentials _creds;
|
||||||
|
private readonly CommandService _commandService;
|
||||||
|
private readonly DbService _db;
|
||||||
|
|
||||||
|
private readonly IBotCredsProvider _credsProvider;
|
||||||
|
// private readonly InteractionService _interactionService;
|
||||||
|
|
||||||
public Bot(int shardId, int? totalShards)
|
public Bot(int shardId, int? totalShards)
|
||||||
{
|
{
|
||||||
if (shardId < 0)
|
if (shardId < 0)
|
||||||
throw new ArgumentOutOfRangeException(nameof(shardId));
|
throw new ArgumentOutOfRangeException(nameof(shardId));
|
||||||
|
|
||||||
|
ShardId = shardId;
|
||||||
_credsProvider = new BotCredsProvider(totalShards);
|
_credsProvider = new BotCredsProvider(totalShards);
|
||||||
_creds = _credsProvider.GetCreds();
|
_creds = _credsProvider.GetCreds();
|
||||||
|
|
||||||
_db = new DbService(_creds);
|
_db = new(_creds);
|
||||||
|
|
||||||
if (shardId == 0)
|
if (shardId == 0)
|
||||||
{
|
|
||||||
_db.Setup();
|
_db.Setup();
|
||||||
}
|
|
||||||
|
|
||||||
Client = new DiscordSocketClient(new DiscordSocketConfig
|
var messageCacheSize =
|
||||||
|
#if GLOBAL_NADEKO
|
||||||
|
0;
|
||||||
|
#else
|
||||||
|
50;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(!_creds.UsePrivilegedIntents)
|
||||||
|
Log.Warning("You are not using privileged intents. Some features will not work properly");
|
||||||
|
|
||||||
|
Client = new(new()
|
||||||
{
|
{
|
||||||
MessageCacheSize = 50,
|
MessageCacheSize = messageCacheSize,
|
||||||
LogLevel = LogSeverity.Warning,
|
LogLevel = LogSeverity.Warning,
|
||||||
ConnectionTimeout = int.MaxValue,
|
ConnectionTimeout = int.MaxValue,
|
||||||
TotalShards = _creds.TotalShards,
|
TotalShards = _creds.TotalShards,
|
||||||
ShardId = shardId,
|
ShardId = shardId,
|
||||||
AlwaysDownloadUsers = false,
|
AlwaysDownloadUsers = false,
|
||||||
ExclusiveBulkDelete = true,
|
AlwaysResolveStickers = false,
|
||||||
|
AlwaysDownloadDefaultStickers = false,
|
||||||
|
GatewayIntents = _creds.UsePrivilegedIntents
|
||||||
|
? GatewayIntents.All
|
||||||
|
: GatewayIntents.AllUnprivileged,
|
||||||
|
LogGatewayIntentWarnings = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
_commandService = new CommandService(new CommandServiceConfig()
|
_commandService = new(new()
|
||||||
{
|
{
|
||||||
CaseSensitiveCommands = false,
|
CaseSensitiveCommands = false,
|
||||||
DefaultRunMode = RunMode.Sync,
|
DefaultRunMode = RunMode.Sync
|
||||||
});
|
});
|
||||||
|
|
||||||
#if GLOBAL_NADEKO || DEBUG
|
// _interactionService = new(Client.Rest);
|
||||||
|
|
||||||
Client.Log += Client_Log;
|
Client.Log += Client_Log;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<ulong> GetCurrentGuildIds()
|
public List<ulong> GetCurrentGuildIds()
|
||||||
{
|
=> Client.Guilds.Select(x => x.Id).ToList();
|
||||||
return Client.Guilds.Select(x => x.Id).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddServices()
|
private void AddServices()
|
||||||
{
|
{
|
||||||
var startingGuildIdList = GetCurrentGuildIds();
|
var startingGuildIdList = GetCurrentGuildIds();
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
var _bot = Client.CurrentUser;
|
var bot = Client.CurrentUser;
|
||||||
|
|
||||||
using (var uow = _db.GetDbContext())
|
using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
uow.EnsureUserCreated(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId);
|
uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId);
|
||||||
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
|
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var svcs = new ServiceCollection()
|
var svcs = new ServiceCollection().AddTransient(_ => _credsProvider.GetCreds()) // bot creds
|
||||||
.AddTransient<IBotCredentials>(_ => _creds) // bot creds
|
|
||||||
.AddSingleton(_credsProvider)
|
.AddSingleton(_credsProvider)
|
||||||
.AddSingleton(_db) // database
|
.AddSingleton(_db) // database
|
||||||
.AddRedis(_creds.RedisOptions) // redis
|
.AddRedis(_creds.RedisOptions) // redis
|
||||||
.AddSingleton(Client) // discord socket client
|
.AddSingleton(Client) // discord socket client
|
||||||
.AddSingleton(_commandService)
|
.AddSingleton(_commandService)
|
||||||
|
// .AddSingleton(_interactionService)
|
||||||
.AddSingleton(this)
|
.AddSingleton(this)
|
||||||
.AddSingleton<ISeria, JsonSeria>()
|
.AddSingleton<ISeria, JsonSeria>()
|
||||||
.AddSingleton<IPubSub, RedisPubSub>()
|
.AddSingleton<IPubSub, RedisPubSub>()
|
||||||
.AddSingleton<IConfigSeria, YamlSeria>()
|
.AddSingleton<IConfigSeria, YamlSeria>()
|
||||||
.AddBotStringsServices()
|
.AddBotStringsServices(_creds.TotalShards)
|
||||||
.AddConfigServices()
|
.AddConfigServices()
|
||||||
.AddConfigMigrators()
|
.AddConfigMigrators()
|
||||||
.AddMemoryCache()
|
.AddMemoryCache()
|
||||||
// music
|
// music
|
||||||
.AddMusic()
|
.AddMusic();
|
||||||
// admin
|
// admin
|
||||||
#if GLOBAL_NADEKO
|
#if GLOBAL_NADEKO
|
||||||
.AddSingleton<ILogCommandService, DummyLogCommandService>()
|
svcs.AddSingleton<ILogCommandService, DummyLogCommandService>();
|
||||||
#else
|
#else
|
||||||
.AddSingleton<ILogCommandService, LogCommandService>()
|
svcs.AddSingleton<ILogCommandService, LogCommandService>();
|
||||||
#endif
|
#endif
|
||||||
;
|
|
||||||
|
|
||||||
svcs.AddHttpClient();
|
svcs.AddHttpClient();
|
||||||
svcs.AddHttpClient("memelist").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
|
svcs.AddHttpClient("memelist")
|
||||||
|
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = false
|
AllowAutoRedirect = false
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
|
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
|
||||||
{
|
|
||||||
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
|
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
svcs.AddSingleton<RemoteGrpcCoordinator>()
|
svcs.AddSingleton<RemoteGrpcCoordinator>()
|
||||||
@@ -143,8 +146,7 @@ namespace NadekoBot
|
|||||||
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RedisImagesCache>())
|
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RedisImagesCache>())
|
||||||
.AddSingleton<IDataCache, RedisCache>();
|
.AddSingleton<IDataCache, RedisCache>();
|
||||||
|
|
||||||
svcs.Scan(scan => scan
|
svcs.Scan(scan => scan.FromAssemblyOf<IReadyExecutor>()
|
||||||
.FromAssemblyOf<IReadyExecutor>()
|
|
||||||
.AddClasses(classes => classes.AssignableToAny(
|
.AddClasses(classes => classes.AssignableToAny(
|
||||||
// services
|
// services
|
||||||
typeof(INService),
|
typeof(INService),
|
||||||
@@ -159,8 +161,7 @@ namespace NadekoBot
|
|||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
.AsSelfWithInterfaces()
|
.AsSelfWithInterfaces()
|
||||||
.WithSingletonLifetime()
|
.WithSingletonLifetime());
|
||||||
);
|
|
||||||
|
|
||||||
//initialize Services
|
//initialize Services
|
||||||
Services = svcs.BuildServiceProvider();
|
Services = svcs.BuildServiceProvider();
|
||||||
@@ -168,14 +169,12 @@ namespace NadekoBot
|
|||||||
exec.Initialize();
|
exec.Initialize();
|
||||||
|
|
||||||
if (Client.ShardId == 0)
|
if (Client.ShardId == 0)
|
||||||
{
|
|
||||||
ApplyConfigMigrations();
|
ApplyConfigMigrations();
|
||||||
}
|
|
||||||
|
|
||||||
_ = LoadTypeReaders(typeof(Bot).Assembly);
|
_ = LoadTypeReaders(typeof(Bot).Assembly);
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
Log.Information($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s");
|
Log.Information( "All services loaded in {ServiceLoadTime:F2}s", sw.Elapsed.TotalSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyConfigMigrations()
|
private void ApplyConfigMigrations()
|
||||||
@@ -183,10 +182,8 @@ namespace NadekoBot
|
|||||||
// execute all migrators
|
// execute all migrators
|
||||||
var migrators = Services.GetServices<IConfigMigrator>();
|
var migrators = Services.GetServices<IConfigMigrator>();
|
||||||
foreach (var migrator in migrators)
|
foreach (var migrator in migrators)
|
||||||
{
|
|
||||||
migrator.EnsureMigrated();
|
migrator.EnsureMigrated();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<object> LoadTypeReaders(Assembly assembly)
|
private IEnumerable<object> LoadTypeReaders(Assembly assembly)
|
||||||
{
|
{
|
||||||
@@ -200,9 +197,9 @@ namespace NadekoBot
|
|||||||
Log.Warning(ex.LoaderExceptions[0], "Error getting types");
|
Log.Warning(ex.LoaderExceptions[0], "Error getting types");
|
||||||
return Enumerable.Empty<object>();
|
return Enumerable.Empty<object>();
|
||||||
}
|
}
|
||||||
var filteredTypes = allTypes
|
|
||||||
.Where(x => x.IsSubclassOf(typeof(TypeReader))
|
var filteredTypes = allTypes.Where(x => x.IsSubclassOf(typeof(TypeReader))
|
||||||
&& x.BaseType.GetGenericArguments().Length > 0
|
&& x.BaseType?.GetGenericArguments().Length > 0
|
||||||
&& !x.IsAbstract);
|
&& !x.IsAbstract);
|
||||||
|
|
||||||
var toReturn = new List<object>();
|
var toReturn = new List<object>();
|
||||||
@@ -210,6 +207,8 @@ namespace NadekoBot
|
|||||||
{
|
{
|
||||||
var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
|
var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
|
||||||
var baseType = ft.BaseType;
|
var baseType = ft.BaseType;
|
||||||
|
if (baseType is null)
|
||||||
|
continue;
|
||||||
var typeArgs = baseType.GetGenericArguments();
|
var typeArgs = baseType.GetGenericArguments();
|
||||||
_commandService.AddTypeReader(typeArgs[0], x);
|
_commandService.AddTypeReader(typeArgs[0], x);
|
||||||
toReturn.Add(x);
|
toReturn.Add(x);
|
||||||
@@ -220,34 +219,30 @@ namespace NadekoBot
|
|||||||
|
|
||||||
private async Task LoginAsync(string token)
|
private async Task LoginAsync(string token)
|
||||||
{
|
{
|
||||||
var clientReady = new TaskCompletionSource<bool>();
|
var clientReady = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
Task SetClientReady()
|
async Task SetClientReady()
|
||||||
{
|
|
||||||
var _ = Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
clientReady.TrySetResult(true);
|
clientReady.TrySetResult(true);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var chan in (await Client.GetDMChannelsAsync().ConfigureAwait(false)))
|
foreach (var chan in await Client.GetDMChannelsAsync())
|
||||||
{
|
await chan.CloseAsync();
|
||||||
await chan.CloseAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//connect
|
//connect
|
||||||
Log.Information("Shard {ShardId} logging in ...", Client.ShardId);
|
Log.Information("Shard {ShardId} logging in ...", Client.ShardId);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
|
Client.Ready += SetClientReady;
|
||||||
await Client.StartAsync().ConfigureAwait(false);
|
|
||||||
|
await Client.LoginAsync(TokenType.Bot, token);
|
||||||
|
await Client.StartAsync();
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
@@ -260,33 +255,33 @@ namespace NadekoBot
|
|||||||
Helpers.ReadErrorAndExit(4);
|
Helpers.ReadErrorAndExit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
Client.Ready += SetClientReady;
|
|
||||||
await clientReady.Task.ConfigureAwait(false);
|
await clientReady.Task.ConfigureAwait(false);
|
||||||
Client.Ready -= SetClientReady;
|
Client.Ready -= SetClientReady;
|
||||||
|
|
||||||
Client.JoinedGuild += Client_JoinedGuild;
|
Client.JoinedGuild += Client_JoinedGuild;
|
||||||
Client.LeftGuild += Client_LeftGuild;
|
Client.LeftGuild += Client_LeftGuild;
|
||||||
|
|
||||||
Log.Information("Shard {0} logged in.", Client.ShardId);
|
Log.Information("Shard {ShardId} logged in", Client.ShardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task Client_LeftGuild(SocketGuild arg)
|
private Task Client_LeftGuild(SocketGuild arg)
|
||||||
{
|
{
|
||||||
Log.Information("Left server: {0} [{1}]", arg?.Name, arg?.Id);
|
Log.Information("Left server: {GuildName} [{GuildId}]", arg?.Name, arg?.Id);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task Client_JoinedGuild(SocketGuild arg)
|
private Task Client_JoinedGuild(SocketGuild arg)
|
||||||
{
|
{
|
||||||
Log.Information($"Joined server: {0} [{1}]", arg.Name, arg.Id);
|
Log.Information("Joined server: {GuildName} [{GuildId}]", arg.Name, arg.Id);
|
||||||
var _ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
GuildConfig gc;
|
GuildConfig gc;
|
||||||
using (var uow = _db.GetDbContext())
|
await using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
gc = uow.GuildConfigsForId(arg.Id);
|
gc = uow.GuildConfigsForId(arg.Id, null);
|
||||||
}
|
}
|
||||||
await JoinedGuild.Invoke(gc).ConfigureAwait(false);
|
|
||||||
|
await JoinedGuild.Invoke(gc);
|
||||||
});
|
});
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -295,7 +290,7 @@ namespace NadekoBot
|
|||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
await LoginAsync(_creds.Token).ConfigureAwait(false);
|
await LoginAsync(_creds.Token);
|
||||||
|
|
||||||
Mention = Client.CurrentUser.Mention;
|
Mention = Client.CurrentUser.Mention;
|
||||||
Log.Information("Shard {ShardId} loading services...", Client.ShardId);
|
Log.Information("Shard {ShardId} loading services...", Client.ShardId);
|
||||||
@@ -314,10 +309,10 @@ namespace NadekoBot
|
|||||||
var commandHandler = Services.GetRequiredService<CommandHandler>();
|
var commandHandler = Services.GetRequiredService<CommandHandler>();
|
||||||
|
|
||||||
// start handling messages received in commandhandler
|
// start handling messages received in commandhandler
|
||||||
await commandHandler.StartHandling().ConfigureAwait(false);
|
await commandHandler.StartHandling();
|
||||||
|
|
||||||
await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
||||||
|
// await _interactionService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
||||||
IsReady = true;
|
IsReady = true;
|
||||||
_ = Task.Run(ExecuteReadySubscriptions);
|
_ = Task.Run(ExecuteReadySubscriptions);
|
||||||
Log.Information("Shard {ShardId} ready", Client.ShardId);
|
Log.Information("Shard {ShardId} ready", Client.ShardId);
|
||||||
@@ -341,23 +336,48 @@ namespace NadekoBot
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.WhenAll(tasks);
|
return tasks.WhenAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task Client_Log(LogMessage arg)
|
private Task Client_Log(LogMessage arg)
|
||||||
{
|
{
|
||||||
if (arg.Exception != null)
|
if (arg.Message?.Contains("unknown dispatch", StringComparison.InvariantCultureIgnoreCase) ?? false)
|
||||||
Log.Warning(arg.Exception, arg.Source + " | " + arg.Message);
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (arg.Exception is { InnerException: WebSocketClosedException { CloseCode: 4014 } })
|
||||||
|
{
|
||||||
|
Log.Error(@"
|
||||||
|
Login failed.
|
||||||
|
|
||||||
|
*** Please enable privileged intents ***
|
||||||
|
|
||||||
|
Certain Nadeko features require Discord's privileged gateway intents.
|
||||||
|
These include greeting and goodbye messages, as well as creating the Owner message channels for DM forwarding.
|
||||||
|
|
||||||
|
How to enable privileged intents:
|
||||||
|
1. Head over to the Discord Developer Portal https://discord.com/developers/applications/
|
||||||
|
2. Select your Application.
|
||||||
|
3. Click on `Bot` in the left side navigation panel, and scroll down to the intents section.
|
||||||
|
4. Enable both intents.
|
||||||
|
5. Restart your bot.
|
||||||
|
|
||||||
|
Read this only if your bot is in 100 or more servers:
|
||||||
|
|
||||||
|
You'll need to apply to use the intents with Discord, but for small selfhosts, all that is required is enabling the intents in the developer portal.
|
||||||
|
Yes, this is a new thing from Discord, as of October 2020. No, there's nothing we can do about it. Yes, we're aware it worked before.
|
||||||
|
While waiting for your bot to be accepted, you can change the 'usePrivilegedIntents' inside your creds.yml to 'false', although this will break many of the nadeko's features");
|
||||||
|
}
|
||||||
|
else if (arg.Exception is not null)
|
||||||
|
Log.Warning(arg.Exception, "{ErrorSource} | {ErrorMessage}", arg.Source, arg.Message);
|
||||||
else
|
else
|
||||||
Log.Warning(arg.Source + " | " + arg.Message);
|
Log.Warning("{ErrorSource} | {ErrorMessage}", arg.Source, arg.Message);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunAndBlockAsync()
|
public async Task RunAndBlockAsync()
|
||||||
{
|
{
|
||||||
await RunAsync().ConfigureAwait(false);
|
await RunAsync();
|
||||||
await Task.Delay(-1).ConfigureAwait(false);
|
await Task.Delay(-1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
10
src/NadekoBot/Common/AddRemove.cs
Normal file
10
src/NadekoBot/Common/AddRemove.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#nullable disable
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public enum AddRemove
|
||||||
|
{
|
||||||
|
Add = int.MinValue,
|
||||||
|
Remove = int.MinValue + 1,
|
||||||
|
Rem = int.MinValue + 1,
|
||||||
|
Rm = int.MinValue + 1
|
||||||
|
}
|
@@ -1,20 +1,20 @@
|
|||||||
using System;
|
#nullable disable
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class AsyncLazy<T> : Lazy<Task<T>>
|
||||||
{
|
{
|
||||||
public class AsyncLazy<T> : Lazy<Task<T>>
|
public AsyncLazy(Func<T> valueFactory)
|
||||||
|
: base(() => Task.Run(valueFactory))
|
||||||
{
|
{
|
||||||
public AsyncLazy(Func<T> valueFactory) :
|
|
||||||
base(() => Task.Run(valueFactory))
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public AsyncLazy(Func<Task<T>> taskFactory) :
|
|
||||||
base(() => Task.Run(taskFactory))
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AsyncLazy(Func<Task<T>> taskFactory)
|
||||||
|
: base(() => Task.Run(taskFactory))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskAwaiter<T> GetAwaiter()
|
||||||
|
=> Value.GetAwaiter();
|
||||||
}
|
}
|
@@ -1,18 +1,12 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Discord.Commands;
|
|
||||||
using NadekoBot.Services;
|
namespace NadekoBot.Common.Attributes;
|
||||||
namespace NadekoBot.Common.Attributes
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class AliasesAttribute : AliasAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
|
||||||
public sealed class AliasesAttribute : AliasAttribute
|
|
||||||
{
|
|
||||||
public AliasesAttribute([CallerMemberName] string memberName = "")
|
public AliasesAttribute([CallerMemberName] string memberName = "")
|
||||||
: base(CommandNameLoadHelper.GetAliasesFor(memberName))
|
: base(CommandNameLoadHelper.GetAliasesFor(memberName))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,15 +0,0 @@
|
|||||||
using Discord.Commands;
|
|
||||||
|
|
||||||
namespace Discord
|
|
||||||
{
|
|
||||||
public class BotPermAttribute : RequireBotPermissionAttribute
|
|
||||||
{
|
|
||||||
public BotPermAttribute(GuildPerm permission) : base((GuildPermission)permission)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public BotPermAttribute(ChannelPerm permission) : base((ChannelPermission)permission)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,36 +1,31 @@
|
|||||||
using System;
|
using YamlDotNet.Serialization;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
public static class CommandNameLoadHelper
|
||||||
{
|
{
|
||||||
public static class CommandNameLoadHelper
|
private static readonly IDeserializer _deserializer = new Deserializer();
|
||||||
{
|
|
||||||
|
|
||||||
private static YamlDotNet.Serialization.IDeserializer _deserializer
|
private static readonly Lazy<Dictionary<string, string[]>> _lazyCommandAliases
|
||||||
= new YamlDotNet.Serialization.Deserializer();
|
= new(() => LoadAliases());
|
||||||
|
|
||||||
public static Lazy<Dictionary<string, string[]>> LazyCommandAliases
|
public static Dictionary<string, string[]> LoadAliases(string aliasesFilePath = "data/aliases.yml")
|
||||||
= new Lazy<Dictionary<string, string[]>>(() => LoadCommandNames());
|
|
||||||
public static Dictionary<string, string[]> LoadCommandNames(string aliasesFilePath = "data/aliases.yml")
|
|
||||||
{
|
{
|
||||||
var text = File.ReadAllText(aliasesFilePath);
|
var text = File.ReadAllText(aliasesFilePath);
|
||||||
return _deserializer.Deserialize<Dictionary<string, string[]>>(text);
|
return _deserializer.Deserialize<Dictionary<string, string[]>>(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string[] GetAliasesFor(string methodName)
|
public static string[] GetAliasesFor(string methodName)
|
||||||
=> LazyCommandAliases.Value.TryGetValue(methodName.ToLowerInvariant(), out var aliases) && aliases.Length > 1
|
=> _lazyCommandAliases.Value.TryGetValue(methodName.ToLowerInvariant(), out var aliases) && aliases.Length > 1
|
||||||
? aliases.Skip(1).ToArray()
|
? aliases.Skip(1).ToArray()
|
||||||
: Array.Empty<string>();
|
: Array.Empty<string>();
|
||||||
|
|
||||||
public static string GetCommandNameFor(string methodName)
|
public static string GetCommandNameFor(string methodName)
|
||||||
{
|
{
|
||||||
methodName = methodName.ToLowerInvariant();
|
methodName = methodName.ToLowerInvariant();
|
||||||
var toReturn = LazyCommandAliases.Value.TryGetValue(methodName, out var aliases) && aliases.Length > 0
|
var toReturn = _lazyCommandAliases.Value.TryGetValue(methodName, out var aliases) && aliases.Length > 0
|
||||||
? aliases[0]
|
? aliases[0]
|
||||||
: methodName;
|
: methodName;
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Discord.Commands;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
|
||||||
public sealed class DescriptionAttribute : SummaryAttribute
|
|
||||||
{
|
|
||||||
// Localization.LoadCommand(memberName.ToLowerInvariant()).Desc
|
|
||||||
public DescriptionAttribute(string text = "") : base(text)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
namespace Discord.Commands
|
|
||||||
{
|
|
||||||
public class LeftoverAttribute : RemainderAttribute
|
|
||||||
{
|
|
||||||
public LeftoverAttribute()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,19 +1,13 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Discord.Commands;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class NadekoCommandAttribute : CommandAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
|
||||||
public sealed class NadekoCommandAttribute : CommandAttribute
|
|
||||||
{
|
|
||||||
public NadekoCommandAttribute([CallerMemberName] string memberName="")
|
|
||||||
: base(CommandNameLoadHelper.GetCommandNameFor(memberName))
|
|
||||||
{
|
|
||||||
this.MethodName = memberName.ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string MethodName { get; }
|
public string MethodName { get; }
|
||||||
}
|
|
||||||
|
public NadekoCommandAttribute([CallerMemberName] string memberName = "")
|
||||||
|
: base(CommandNameLoadHelper.GetCommandNameFor(memberName))
|
||||||
|
=> MethodName = memberName.ToLowerInvariant();
|
||||||
}
|
}
|
@@ -1,14 +1,30 @@
|
|||||||
using System;
|
using System.Runtime.CompilerServices;
|
||||||
using Discord.Commands;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
internal sealed class NadekoModuleAttribute : GroupAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
public NadekoModuleAttribute(string moduleName)
|
||||||
sealed class NadekoModuleAttribute : GroupAttribute
|
: base(moduleName)
|
||||||
{
|
{
|
||||||
public NadekoModuleAttribute(string moduleName) : base(moduleName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
internal sealed class NadekoDescriptionAttribute : SummaryAttribute
|
||||||
|
{
|
||||||
|
public NadekoDescriptionAttribute([CallerMemberName] string name = "")
|
||||||
|
: base(name.ToLowerInvariant())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
internal sealed class NadekoUsageAttribute : RemarksAttribute
|
||||||
|
{
|
||||||
|
public NadekoUsageAttribute([CallerMemberName] string name = "")
|
||||||
|
: base(name.ToLowerInvariant())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -1,15 +1,10 @@
|
|||||||
using System;
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class NadekoOptionsAttribute : Attribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
|
||||||
public sealed class NadekoOptionsAttribute : Attribute
|
|
||||||
{
|
|
||||||
public Type OptionType { get; set; }
|
public Type OptionType { get; set; }
|
||||||
|
|
||||||
public NadekoOptionsAttribute(Type t)
|
public NadekoOptionsAttribute(Type t)
|
||||||
{
|
=> OptionType = t;
|
||||||
this.OptionType = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,20 +1,19 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using NadekoBot.Extensions;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||||
|
public sealed class OwnerOnlyAttribute : PreconditionAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||||
public sealed class OwnerOnlyAttribute : PreconditionAttribute
|
ICommandContext context,
|
||||||
|
CommandInfo command,
|
||||||
|
IServiceProvider services)
|
||||||
{
|
{
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
|
var creds = services.GetRequiredService<IBotCredsProvider>().GetCreds();
|
||||||
{
|
|
||||||
var creds = services.GetRequiredService<BotCredsProvider>().GetCreds();
|
|
||||||
|
|
||||||
return Task.FromResult((creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
|
return Task.FromResult(creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id
|
||||||
}
|
? PreconditionResult.FromSuccess()
|
||||||
|
: PreconditionResult.FromError("Not owner"));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,14 +1,10 @@
|
|||||||
using Discord.Commands;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class RatelimitAttribute : PreconditionAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
|
||||||
public sealed class RatelimitAttribute : PreconditionAttribute
|
|
||||||
{
|
|
||||||
public int Seconds { get; }
|
public int Seconds { get; }
|
||||||
|
|
||||||
public RatelimitAttribute(int seconds)
|
public RatelimitAttribute(int seconds)
|
||||||
@@ -19,7 +15,10 @@ namespace NadekoBot.Common.Attributes
|
|||||||
Seconds = seconds;
|
Seconds = seconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||||
|
ICommandContext context,
|
||||||
|
CommandInfo command,
|
||||||
|
IServiceProvider services)
|
||||||
{
|
{
|
||||||
if (Seconds == 0)
|
if (Seconds == 0)
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
@@ -27,12 +26,11 @@ namespace NadekoBot.Common.Attributes
|
|||||||
var cache = services.GetRequiredService<IDataCache>();
|
var cache = services.GetRequiredService<IDataCache>();
|
||||||
var rem = cache.TryAddRatelimit(context.User.Id, command.Name, Seconds);
|
var rem = cache.TryAddRatelimit(context.User.Id, command.Name, Seconds);
|
||||||
|
|
||||||
if(rem is null)
|
if (rem is null)
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
|
|
||||||
var msgContent = $"You can use this command again in {rem.Value.TotalSeconds:F1}s.";
|
var msgContent = $"You can use this command again in {rem.Value.TotalSeconds:F1}s.";
|
||||||
|
|
||||||
return Task.FromResult(PreconditionResult.FromError(msgContent));
|
return Task.FromResult(PreconditionResult.FromError(msgContent));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,21 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Discord.Commands;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
|
||||||
public sealed class UsageAttribute : RemarksAttribute
|
|
||||||
{
|
|
||||||
// public static string GetUsage(string memberName)
|
|
||||||
// {
|
|
||||||
// var usage = Localization.LoadCommand(memberName.ToLowerInvariant()).Usage;
|
|
||||||
// return JsonConvert.SerializeObject(usage);
|
|
||||||
// }
|
|
||||||
public UsageAttribute(string text = "") : base(text)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,33 +1,30 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using NadekoBot.Modules.Administration.Services;
|
using NadekoBot.Modules.Administration.Services;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord;
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
|
||||||
public class UserPermAttribute : PreconditionAttribute
|
|
||||||
{
|
|
||||||
public RequireUserPermissionAttribute UserPermissionAttribute { get; }
|
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class UserPermAttribute : RequireUserPermissionAttribute
|
||||||
|
{
|
||||||
public UserPermAttribute(GuildPerm permission)
|
public UserPermAttribute(GuildPerm permission)
|
||||||
|
: base(permission)
|
||||||
{
|
{
|
||||||
UserPermissionAttribute = new RequireUserPermissionAttribute((GuildPermission)permission);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserPermAttribute(ChannelPerm permission)
|
public UserPermAttribute(ChannelPerm permission)
|
||||||
|
: base(permission)
|
||||||
{
|
{
|
||||||
UserPermissionAttribute = new RequireUserPermissionAttribute((ChannelPermission)permission);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||||
|
ICommandContext context,
|
||||||
|
CommandInfo command,
|
||||||
|
IServiceProvider services)
|
||||||
{
|
{
|
||||||
var permService = services.GetRequiredService<DiscordPermOverrideService>();
|
var permService = services.GetRequiredService<DiscordPermOverrideService>();
|
||||||
if (permService.TryGetOverrides(context.Guild?.Id ?? 0, command.Name.ToUpperInvariant(), out var _))
|
if (permService.TryGetOverrides(context.Guild?.Id ?? 0, command.Name.ToUpperInvariant(), out _))
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
|
|
||||||
return UserPermissionAttribute.CheckPermissionsAsync(context, command, services);
|
return base.CheckPermissionsAsync(context, command, services);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,20 +1,17 @@
|
|||||||
using Newtonsoft.Json;
|
#nullable disable
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class CmdStrings
|
||||||
{
|
{
|
||||||
public class CmdStrings
|
|
||||||
{
|
|
||||||
public string[] Usages { get; }
|
public string[] Usages { get; }
|
||||||
public string Description { get; }
|
public string Description { get; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public CmdStrings(
|
public CmdStrings([JsonProperty("args")] string[] usages, [JsonProperty("desc")] string description)
|
||||||
[JsonProperty("args")]string[] usages,
|
|
||||||
[JsonProperty("desc")]string description
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
Usages = usages;
|
Usages = usages;
|
||||||
Description = description;
|
Description = description;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,77 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Collections
|
|
||||||
{
|
|
||||||
public static class DisposableReadOnlyListExtensions
|
|
||||||
{
|
|
||||||
public static IDisposableReadOnlyList<T> AsDisposable<T>(this IReadOnlyList<T> arr) where T : IDisposable
|
|
||||||
=> new DisposableReadOnlyList<T>(arr);
|
|
||||||
|
|
||||||
public static IDisposableReadOnlyList<KeyValuePair<TKey, TValue>> AsDisposable<TKey, TValue>(this IReadOnlyList<KeyValuePair<TKey, TValue>> arr) where TValue : IDisposable
|
|
||||||
=> new DisposableReadOnlyList<TKey, TValue>(arr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IDisposableReadOnlyList<T> : IReadOnlyList<T>, IDisposable
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class DisposableReadOnlyList<T> : IDisposableReadOnlyList<T>
|
|
||||||
where T : IDisposable
|
|
||||||
{
|
|
||||||
private readonly IReadOnlyList<T> _arr;
|
|
||||||
|
|
||||||
public int Count => _arr.Count;
|
|
||||||
|
|
||||||
public T this[int index] => _arr[index];
|
|
||||||
|
|
||||||
public DisposableReadOnlyList(IReadOnlyList<T> arr)
|
|
||||||
{
|
|
||||||
this._arr = arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
|
||||||
=> _arr.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
=> _arr.GetEnumerator();
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
foreach (var item in _arr)
|
|
||||||
{
|
|
||||||
item.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class DisposableReadOnlyList<T, U> : IDisposableReadOnlyList<KeyValuePair<T, U>>
|
|
||||||
where U : IDisposable
|
|
||||||
{
|
|
||||||
private readonly IReadOnlyList<KeyValuePair<T, U>> _arr;
|
|
||||||
|
|
||||||
public int Count => _arr.Count;
|
|
||||||
|
|
||||||
KeyValuePair<T, U> IReadOnlyList<KeyValuePair<T, U>>.this[int index] => _arr[index];
|
|
||||||
|
|
||||||
public DisposableReadOnlyList(IReadOnlyList<KeyValuePair<T, U>> arr)
|
|
||||||
{
|
|
||||||
this._arr = arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<T, U>> GetEnumerator() =>
|
|
||||||
_arr.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() =>
|
|
||||||
_arr.GetEnumerator();
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
foreach (var item in _arr)
|
|
||||||
{
|
|
||||||
item.Value.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,23 +1,37 @@
|
|||||||
using System.Collections;
|
#nullable disable
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Collections
|
namespace NadekoBot.Common.Collections;
|
||||||
|
|
||||||
|
public class IndexedCollection<T> : IList<T>
|
||||||
|
where T : class, IIndexed
|
||||||
{
|
{
|
||||||
public class IndexedCollection<T> : IList<T> where T : class, IIndexed
|
|
||||||
{
|
|
||||||
public List<T> Source { get; }
|
public List<T> Source { get; }
|
||||||
private readonly object _locker = new object();
|
|
||||||
|
|
||||||
public int Count => Source.Count;
|
public int Count
|
||||||
public bool IsReadOnly => false;
|
=> Source.Count;
|
||||||
public int IndexOf(T item) => item.Index;
|
|
||||||
|
public bool IsReadOnly
|
||||||
|
=> false;
|
||||||
|
|
||||||
|
public virtual T this[int index]
|
||||||
|
{
|
||||||
|
get => Source[index];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
value.Index = index;
|
||||||
|
Source[index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly object _locker = new();
|
||||||
|
|
||||||
public IndexedCollection()
|
public IndexedCollection()
|
||||||
{
|
=> Source = new();
|
||||||
Source = new List<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexedCollection(IEnumerable<T> source)
|
public IndexedCollection(IEnumerable<T> source)
|
||||||
{
|
{
|
||||||
@@ -28,31 +42,19 @@ namespace NadekoBot.Common.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateIndexes()
|
public int IndexOf(T item)
|
||||||
{
|
=> item?.Index ?? -1;
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < Source.Count; i++)
|
|
||||||
{
|
|
||||||
if (Source[i].Index != i)
|
|
||||||
Source[i].Index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator List<T>(IndexedCollection<T> x) =>
|
public IEnumerator<T> GetEnumerator()
|
||||||
x.Source;
|
=> Source.GetEnumerator();
|
||||||
|
|
||||||
public List<T> ToList() => Source.ToList();
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
=> Source.GetEnumerator();
|
||||||
public IEnumerator<T> GetEnumerator() =>
|
|
||||||
Source.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() =>
|
|
||||||
Source.GetEnumerator();
|
|
||||||
|
|
||||||
public void Add(T item)
|
public void Add(T item)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
lock (_locker)
|
lock (_locker)
|
||||||
{
|
{
|
||||||
item.Index = Source.Count;
|
item.Index = Source.Count;
|
||||||
@@ -86,19 +88,21 @@ namespace NadekoBot.Common.Collections
|
|||||||
|
|
||||||
public virtual bool Remove(T item)
|
public virtual bool Remove(T item)
|
||||||
{
|
{
|
||||||
bool removed;
|
|
||||||
lock (_locker)
|
lock (_locker)
|
||||||
{
|
{
|
||||||
if (removed = Source.Remove(item))
|
if (Source.Remove(item))
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Source.Count; i++)
|
for (var i = 0; i < Source.Count; i++)
|
||||||
{
|
{
|
||||||
if (Source[i].Index != i)
|
if (Source[i].Index != i)
|
||||||
Source[i].Index = i;
|
Source[i].Index = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removed;
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Insert(int index, T item)
|
public virtual void Insert(int index, T item)
|
||||||
@@ -106,36 +110,36 @@ namespace NadekoBot.Common.Collections
|
|||||||
lock (_locker)
|
lock (_locker)
|
||||||
{
|
{
|
||||||
Source.Insert(index, item);
|
Source.Insert(index, item);
|
||||||
for (int i = index; i < Source.Count; i++)
|
for (var i = index; i < Source.Count; i++)
|
||||||
{
|
|
||||||
Source[i].Index = i;
|
Source[i].Index = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void RemoveAt(int index)
|
public virtual void RemoveAt(int index)
|
||||||
{
|
{
|
||||||
lock (_locker)
|
lock (_locker)
|
||||||
{
|
{
|
||||||
Source.RemoveAt(index);
|
Source.RemoveAt(index);
|
||||||
for (int i = index; i < Source.Count; i++)
|
for (var i = index; i < Source.Count; i++)
|
||||||
|
Source[i].Index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateIndexes()
|
||||||
{
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Source.Count; i++)
|
||||||
|
{
|
||||||
|
if (Source[i].Index != i)
|
||||||
Source[i].Index = i;
|
Source[i].Index = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual T this[int index]
|
public static implicit operator List<T>(IndexedCollection<T> x)
|
||||||
{
|
=> x.Source;
|
||||||
get { return Source[index]; }
|
|
||||||
set
|
public List<T> ToList()
|
||||||
{
|
=> Source.ToList();
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
value.Index = index;
|
|
||||||
Source[index] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,9 +1,9 @@
|
|||||||
namespace NadekoBot.Common
|
#nullable disable
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class CommandData
|
||||||
{
|
{
|
||||||
public class CommandData
|
|
||||||
{
|
|
||||||
public string Cmd { get; set; }
|
public string Cmd { get; set; }
|
||||||
public string Desc { get; set; }
|
public string Desc { get; set; }
|
||||||
public string[] Usage { get; set; }
|
public string[] Usage { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,16 +1,16 @@
|
|||||||
using System.Collections.Generic;
|
#nullable disable
|
||||||
using System.Globalization;
|
|
||||||
using Cloneable;
|
using Cloneable;
|
||||||
using NadekoBot.Common.Yml;
|
using NadekoBot.Common.Yml;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using System.Globalization;
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Configs
|
namespace NadekoBot.Common.Configs;
|
||||||
|
|
||||||
|
[Cloneable]
|
||||||
|
public sealed partial class BotConfig : ICloneable<BotConfig>
|
||||||
{
|
{
|
||||||
[Cloneable]
|
|
||||||
public sealed partial class BotConfig : ICloneable<BotConfig>
|
|
||||||
{
|
|
||||||
[Comment(@"DO NOT CHANGE")]
|
[Comment(@"DO NOT CHANGE")]
|
||||||
public int Version { get; set; } = 2;
|
public int Version { get; set; } = 2;
|
||||||
|
|
||||||
@@ -39,7 +39,8 @@ Allowed values: Simple, Normal, None")]
|
|||||||
[Comment(@"Do you want any messages sent by users in Bot's DM to be forwarded to the owner(s)?")]
|
[Comment(@"Do you want any messages sent by users in Bot's DM to be forwarded to the owner(s)?")]
|
||||||
public bool ForwardMessages { get; set; }
|
public bool ForwardMessages { get; set; }
|
||||||
|
|
||||||
[Comment(@"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
|
[Comment(
|
||||||
|
@"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)")]
|
or all owners? (this might cause the bot to lag if there's a lot of owners specified)")]
|
||||||
public bool ForwardToAllOwners { get; set; }
|
public bool ForwardToAllOwners { get; set; }
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ Leave empty to reply with DmHelpText to every DM.")]
|
|||||||
[Comment(@"This is the response for the .h command")]
|
[Comment(@"This is the response for the .h command")]
|
||||||
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
||||||
public string HelpText { get; set; }
|
public string HelpText { get; set; }
|
||||||
|
|
||||||
[Comment(@"List of modules and commands completely blocked on the bot")]
|
[Comment(@"List of modules and commands completely blocked on the bot")]
|
||||||
public BlockedConfig Blocked { get; set; }
|
public BlockedConfig Blocked { get; set; }
|
||||||
|
|
||||||
@@ -75,28 +77,15 @@ note: This setting is primarily used if you're afraid of raids, or you're runnin
|
|||||||
public bool GroupGreets { get; set; }
|
public bool GroupGreets { get; set; }
|
||||||
|
|
||||||
[Comment(@"Whether the bot will rotate through all specified statuses.
|
[Comment(@"Whether the bot will rotate through all specified statuses.
|
||||||
This setting can be changed via .rots command.
|
This setting can be changed via .ropl command.
|
||||||
See RotatingStatuses submodule in Administration.")]
|
See RotatingStatuses submodule in Administration.")]
|
||||||
public bool RotateStatuses { get; set; }
|
public bool RotateStatuses { get; set; }
|
||||||
|
|
||||||
// [Comment(@"Whether the prefix will be a suffix, or prefix.
|
|
||||||
// For example, if your prefix is ! you will run a command called 'cash' by typing either
|
|
||||||
// '!cash @Someone' if your prefixIsSuffix: false or
|
|
||||||
// 'cash @Someone!' if your prefixIsSuffix: true")]
|
|
||||||
// public bool PrefixIsSuffix { get; set; }
|
|
||||||
|
|
||||||
// public string Prefixed(string text) => PrefixIsSuffix
|
|
||||||
// ? text + Prefix
|
|
||||||
// : Prefix + text;
|
|
||||||
|
|
||||||
public string Prefixed(string text)
|
|
||||||
=> Prefix + text;
|
|
||||||
|
|
||||||
public BotConfig()
|
public BotConfig()
|
||||||
{
|
{
|
||||||
var color = new ColorConfig();
|
var color = new ColorConfig();
|
||||||
Color = color;
|
Color = color;
|
||||||
DefaultLocale = new CultureInfo("en-US");
|
DefaultLocale = new("en-US");
|
||||||
ConsoleOutputType = ConsoleOutputType.Normal;
|
ConsoleOutputType = ConsoleOutputType.Normal;
|
||||||
ForwardMessages = false;
|
ForwardMessages = false;
|
||||||
ForwardToAllOwners = false;
|
ForwardToAllOwners = false;
|
||||||
@@ -131,7 +120,7 @@ See RotatingStatuses submodule in Administration.")]
|
|||||||
Prefix = ".";
|
Prefix = ".";
|
||||||
RotateStatuses = false;
|
RotateStatuses = false;
|
||||||
GroupGreets = false;
|
GroupGreets = false;
|
||||||
DmHelpTextKeywords = new List<string>()
|
DmHelpTextKeywords = new()
|
||||||
{
|
{
|
||||||
"help",
|
"help",
|
||||||
"commands",
|
"commands",
|
||||||
@@ -140,24 +129,37 @@ See RotatingStatuses submodule in Administration.")]
|
|||||||
"can you do"
|
"can you do"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[Cloneable]
|
// [Comment(@"Whether the prefix will be a suffix, or prefix.
|
||||||
public sealed partial class BlockedConfig
|
// For example, if your prefix is ! you will run a command called 'cash' by typing either
|
||||||
{
|
// '!cash @Someone' if your prefixIsSuffix: false or
|
||||||
|
// 'cash @Someone!' if your prefixIsSuffix: true")]
|
||||||
|
// public bool PrefixIsSuffix { get; set; }
|
||||||
|
|
||||||
|
// public string Prefixed(string text) => PrefixIsSuffix
|
||||||
|
// ? text + Prefix
|
||||||
|
// : Prefix + text;
|
||||||
|
|
||||||
|
public string Prefixed(string text)
|
||||||
|
=> Prefix + text;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cloneable]
|
||||||
|
public sealed partial class BlockedConfig
|
||||||
|
{
|
||||||
public HashSet<string> Commands { get; set; }
|
public HashSet<string> Commands { get; set; }
|
||||||
public HashSet<string> Modules { get; set; }
|
public HashSet<string> Modules { get; set; }
|
||||||
|
|
||||||
public BlockedConfig()
|
public BlockedConfig()
|
||||||
{
|
{
|
||||||
Modules = new HashSet<string>();
|
Modules = new();
|
||||||
Commands = new HashSet<string>();
|
Commands = new();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
public partial class ColorConfig
|
public partial class ColorConfig
|
||||||
{
|
{
|
||||||
[Comment(@"Color used for embed responses when command successfully executes")]
|
[Comment(@"Color used for embed responses when command successfully executes")]
|
||||||
public Rgba32 Ok { get; set; }
|
public Rgba32 Ok { get; set; }
|
||||||
|
|
||||||
@@ -173,12 +175,11 @@ See RotatingStatuses submodule in Administration.")]
|
|||||||
Error = Rgba32.ParseHex("ee281f");
|
Error = Rgba32.ParseHex("ee281f");
|
||||||
Pending = Rgba32.ParseHex("faa61a");
|
Pending = Rgba32.ParseHex("faa61a");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ConsoleOutputType
|
public enum ConsoleOutputType
|
||||||
{
|
{
|
||||||
Normal = 0,
|
Normal = 0,
|
||||||
Simple = 1,
|
Simple = 1,
|
||||||
None = 2,
|
None = 2
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,18 +1,18 @@
|
|||||||
namespace NadekoBot.Common.Configs
|
namespace NadekoBot.Common.Configs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base interface for available config serializers
|
||||||
|
/// </summary>
|
||||||
|
public interface IConfigSeria
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Base interface for available config serializers
|
|
||||||
/// </summary>
|
|
||||||
public interface IConfigSeria
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialize the object to string
|
/// Serialize the object to string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Serialize<T>(T obj);
|
public string Serialize<T>(T obj)
|
||||||
|
where T : notnull;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserialize string data into an object of the specified type
|
/// Deserialize string data into an object of the specified type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public T Deserialize<T>(string data);
|
public T Deserialize<T>(string data);
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,36 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
#nullable disable
|
||||||
using NadekoBot.Common.Yml;
|
using NadekoBot.Common.Yml;
|
||||||
using YamlDotNet.Serialization;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed class Creds : IBotCredentials
|
||||||
{
|
{
|
||||||
public sealed class Creds : IBotCredentials
|
|
||||||
{
|
|
||||||
public Creds()
|
|
||||||
{
|
|
||||||
Version = 1;
|
|
||||||
Token = string.Empty;
|
|
||||||
OwnerIds = new List<ulong>();
|
|
||||||
TotalShards = 1;
|
|
||||||
GoogleApiKey = string.Empty;
|
|
||||||
Votes = new(string.Empty, string.Empty);
|
|
||||||
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
|
||||||
BotListToken = string.Empty;
|
|
||||||
CleverbotApiKey = string.Empty;
|
|
||||||
RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=";
|
|
||||||
Db = new()
|
|
||||||
{
|
|
||||||
Type = "sqlite",
|
|
||||||
ConnectionString = "Data Source=data/NadekoBot.db"
|
|
||||||
};
|
|
||||||
|
|
||||||
CoordinatorUrl = "http://localhost:3442";
|
|
||||||
|
|
||||||
RestartCommand = new()
|
|
||||||
{
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[Comment(@"DO NOT CHANGE")]
|
[Comment(@"DO NOT CHANGE")]
|
||||||
public int Version { get; set; }
|
public int Version { get; set; }
|
||||||
|
|
||||||
@@ -41,11 +15,15 @@ namespace NadekoBot.Common
|
|||||||
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
|
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
|
||||||
public ICollection<ulong> OwnerIds { get; set; }
|
public ICollection<ulong> OwnerIds { get; set; }
|
||||||
|
|
||||||
|
[Comment("Keep this on 'true' unless you're sure your bot shouldn't use privileged intents or you're waiting to be accepted")]
|
||||||
|
public bool UsePrivilegedIntents { get; set; }
|
||||||
|
|
||||||
[Comment(@"The number of shards that the bot will running on.
|
[Comment(@"The number of shards that the bot will running on.
|
||||||
Leave at 1 if you don't know what you're doing.")]
|
Leave at 1 if you don't know what you're doing.")]
|
||||||
public int TotalShards { get; set; }
|
public int TotalShards { get; set; }
|
||||||
|
|
||||||
[Comment(@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
[Comment(
|
||||||
|
@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
||||||
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
||||||
Used only for Youtube Data Api (at the moment).")]
|
Used only for Youtube Data Api (at the moment).")]
|
||||||
public string GoogleApiKey { get; set; }
|
public string GoogleApiKey { get; set; }
|
||||||
@@ -73,17 +51,8 @@ go to https://www.patreon.com/portal -> my clients -> create client")]
|
|||||||
Change only if you've changed the coordinator address or port.")]
|
Change only if you've changed the coordinator address or port.")]
|
||||||
public string CoordinatorUrl { get; set; }
|
public string CoordinatorUrl { get; set; }
|
||||||
|
|
||||||
[YamlIgnore]
|
[Comment(
|
||||||
public string PatreonCampaignId => Patreon?.CampaignId;
|
@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
||||||
[YamlIgnore]
|
|
||||||
public string PatreonAccessToken => Patreon?.AccessToken;
|
|
||||||
|
|
||||||
[YamlIgnore]
|
|
||||||
public string VotesUrl => Votes?.Url;
|
|
||||||
[YamlIgnore]
|
|
||||||
public string VotesToken => Votes.Key;
|
|
||||||
|
|
||||||
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
|
||||||
public string RapidApiKey { get; set; }
|
public string RapidApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
|
[Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
|
||||||
@@ -98,9 +67,23 @@ Used only for .time command")]
|
|||||||
Used for cryptocurrency related commands.")]
|
Used for cryptocurrency related commands.")]
|
||||||
public string CoinmarketcapApiKey { get; set; }
|
public string CoinmarketcapApiKey { get; set; }
|
||||||
|
|
||||||
|
// [Comment(@"https://polygon.io/dashboard/api-keys api key. Free plan allows for 5 queries per minute.
|
||||||
|
// Used for stocks related commands.")]
|
||||||
|
// public string PolygonIoApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
|
[Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
|
||||||
public string OsuApiKey { get; set; }
|
public string OsuApiKey { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Optional Trovo client id.
|
||||||
|
You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors.")]
|
||||||
|
public string TrovoClientId { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Obtain by creating an application at https://dev.twitch.tv/console/apps")]
|
||||||
|
public string TwitchClientId { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Obtain by creating an application at https://dev.twitch.tv/console/apps")]
|
||||||
|
public string TwitchClientSecret { get; set; }
|
||||||
|
|
||||||
[Comment(@"Command and args which will be used to restart the bot.
|
[Comment(@"Command and args which will be used to restart the bot.
|
||||||
Only used if bot is executed directly (NOT through the coordinator)
|
Only used if bot is executed directly (NOT through the coordinator)
|
||||||
placeholders:
|
placeholders:
|
||||||
@@ -114,93 +97,102 @@ Windows default
|
|||||||
args: {0}")]
|
args: {0}")]
|
||||||
public RestartConfig RestartCommand { get; set; }
|
public RestartConfig RestartCommand { get; set; }
|
||||||
|
|
||||||
|
public Creds()
|
||||||
|
{
|
||||||
|
Version = 4;
|
||||||
|
Token = string.Empty;
|
||||||
|
UsePrivilegedIntents = true;
|
||||||
|
OwnerIds = new List<ulong>();
|
||||||
|
TotalShards = 1;
|
||||||
|
GoogleApiKey = string.Empty;
|
||||||
|
Votes = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||||
|
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||||
|
BotListToken = string.Empty;
|
||||||
|
CleverbotApiKey = string.Empty;
|
||||||
|
RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=";
|
||||||
|
Db = new()
|
||||||
|
{
|
||||||
|
Type = "sqlite",
|
||||||
|
ConnectionString = "Data Source=data/NadekoBot.db"
|
||||||
|
};
|
||||||
|
|
||||||
|
CoordinatorUrl = "http://localhost:3442";
|
||||||
|
|
||||||
|
RestartCommand = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public class DbOptions
|
public class DbOptions
|
||||||
{
|
{
|
||||||
[Comment(@"Database type. Only sqlite supported atm")]
|
[Comment(@"Database type. Only sqlite supported atm")]
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
|
|
||||||
[Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
|
[Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
|
||||||
public string ConnectionString { get; set; }
|
public string ConnectionString { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo fixup patreon
|
|
||||||
public sealed record PatreonSettings
|
public sealed record PatreonSettings
|
||||||
{
|
{
|
||||||
[Comment(@"Access token. You have to manually update this 1st of each month by refreshing the token on https://patreon.com/portal")]
|
public string ClientId { get; set; }
|
||||||
public string AccessToken { get; set; }
|
public string AccessToken { get; set; }
|
||||||
[Comment(@"Unused atm")]
|
|
||||||
public string RefreshToken { get; set; }
|
public string RefreshToken { get; set; }
|
||||||
[Comment(@"Unused atm")]
|
|
||||||
public string ClientSecret { get; set; }
|
public string ClientSecret { get; set; }
|
||||||
|
|
||||||
[Comment(@"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")]
|
[Comment(
|
||||||
|
@"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")]
|
||||||
public string CampaignId { get; set; }
|
public string CampaignId { get; set; }
|
||||||
|
|
||||||
public PatreonSettings(string accessToken, string refreshToken, string clientSecret, string campaignId)
|
public PatreonSettings(
|
||||||
|
string accessToken,
|
||||||
|
string refreshToken,
|
||||||
|
string clientSecret,
|
||||||
|
string campaignId)
|
||||||
{
|
{
|
||||||
AccessToken = accessToken;
|
AccessToken = accessToken;
|
||||||
RefreshToken = refreshToken;
|
RefreshToken = refreshToken;
|
||||||
ClientSecret = clientSecret;
|
ClientSecret = clientSecret;
|
||||||
CampaignId = campaignId;
|
CampaignId = campaignId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PatreonSettings()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record VotesSettings
|
public sealed record VotesSettings
|
||||||
{
|
{
|
||||||
[Comment(@"")]
|
[Comment(@"top.gg votes service url
|
||||||
public string Url { get; set; }
|
This is the url of your instance of the NadekoBot.Votes api
|
||||||
[Comment(@"")]
|
Example: https://votes.my.cool.bot.com")]
|
||||||
public string Key { get; set; }
|
public string TopggServiceUrl { get; set; }
|
||||||
|
|
||||||
public VotesSettings(string url, string key)
|
[Comment(@"Authorization header value sent to the TopGG service url with each request
|
||||||
|
This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file")]
|
||||||
|
public string TopggKey { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"discords.com votes service url
|
||||||
|
This is the url of your instance of the NadekoBot.Votes api
|
||||||
|
Example: https://votes.my.cool.bot.com")]
|
||||||
|
public string DiscordsServiceUrl { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Authorization header value sent to the Discords service url with each request
|
||||||
|
This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file")]
|
||||||
|
public string DiscordsKey { get; set; }
|
||||||
|
|
||||||
|
public VotesSettings()
|
||||||
{
|
{
|
||||||
Url = url;
|
|
||||||
Key = key;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Old
|
public VotesSettings(
|
||||||
|
string topggServiceUrl,
|
||||||
|
string topggKey,
|
||||||
|
string discordsServiceUrl,
|
||||||
|
string discordsKey)
|
||||||
{
|
{
|
||||||
public string Token { get; set; } = string.Empty;
|
TopggServiceUrl = topggServiceUrl;
|
||||||
public ulong[] OwnerIds { get; set; } = new ulong[1];
|
TopggKey = topggKey;
|
||||||
public string LoLApiKey { get; set; } = string.Empty;
|
DiscordsServiceUrl = discordsServiceUrl;
|
||||||
public string GoogleApiKey { get; set; } = string.Empty;
|
DiscordsKey = discordsKey;
|
||||||
public string MashapeKey { get; set; } = string.Empty;
|
|
||||||
public string OsuApiKey { get; set; } = string.Empty;
|
|
||||||
public string SoundCloudClientId { get; set; } = string.Empty;
|
|
||||||
public string CleverbotApiKey { get; set; } = string.Empty;
|
|
||||||
public string CarbonKey { get; set; } = string.Empty;
|
|
||||||
public int TotalShards { get; set; } = 1;
|
|
||||||
public string PatreonAccessToken { get; set; } = string.Empty;
|
|
||||||
public string PatreonCampaignId { get; set; } = "334038";
|
|
||||||
public RestartConfig RestartCommand { get; set; } = null;
|
|
||||||
|
|
||||||
public string ShardRunCommand { get; set; } = string.Empty;
|
|
||||||
public string ShardRunArguments { get; set; } = string.Empty;
|
|
||||||
public int? ShardRunPort { get; set; } = null;
|
|
||||||
public string MiningProxyUrl { get; set; } = string.Empty;
|
|
||||||
public string MiningProxyCreds { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string BotListToken { get; set; } = string.Empty;
|
|
||||||
public string TwitchClientId { get; set; } = string.Empty;
|
|
||||||
public string VotesToken { get; set; } = string.Empty;
|
|
||||||
public string VotesUrl { get; set; } = string.Empty;
|
|
||||||
public string RedisOptions { get; set; } = string.Empty;
|
|
||||||
public string LocationIqApiKey { get; set; } = string.Empty;
|
|
||||||
public string TimezoneDbApiKey { get; set; } = string.Empty;
|
|
||||||
public string CoinmarketcapApiKey { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public class RestartConfig
|
|
||||||
{
|
|
||||||
public RestartConfig(string cmd, string args)
|
|
||||||
{
|
|
||||||
this.Cmd = cmd;
|
|
||||||
this.Args = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Cmd { get; set; }
|
|
||||||
public string Args { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,16 +1,10 @@
|
|||||||
using NadekoBot.Services;
|
#nullable disable
|
||||||
using System;
|
namespace NadekoBot.Common;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
public class DownloadTracker : INService
|
||||||
{
|
{
|
||||||
public class DownloadTracker : INService
|
private ConcurrentDictionary<ulong, DateTime> LastDownloads { get; } = new();
|
||||||
{
|
private readonly SemaphoreSlim _downloadUsersSemaphore = new(1, 1);
|
||||||
private ConcurrentDictionary<ulong, DateTime> LastDownloads { get; } = new ConcurrentDictionary<ulong, DateTime>();
|
|
||||||
private SemaphoreSlim downloadUsersSemaphore = new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures all users on the specified guild were downloaded within the last hour.
|
/// Ensures all users on the specified guild were downloaded within the last hour.
|
||||||
@@ -19,16 +13,18 @@ namespace NadekoBot.Common
|
|||||||
/// <returns>Task representing download state</returns>
|
/// <returns>Task representing download state</returns>
|
||||||
public async Task EnsureUsersDownloadedAsync(IGuild guild)
|
public async Task EnsureUsersDownloadedAsync(IGuild guild)
|
||||||
{
|
{
|
||||||
await downloadUsersSemaphore.WaitAsync();
|
#if GLOBAL_NADEKO
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
await _downloadUsersSemaphore.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
// download once per hour at most
|
// download once per hour at most
|
||||||
var added = LastDownloads.AddOrUpdate(
|
var added = LastDownloads.AddOrUpdate(guild.Id,
|
||||||
guild.Id,
|
|
||||||
now,
|
now,
|
||||||
(key, old) => (now - old) > TimeSpan.FromHours(1) ? now : old);
|
(_, old) => now - old > TimeSpan.FromHours(1) ? now : old);
|
||||||
|
|
||||||
// means that this entry was just added - download the users
|
// means that this entry was just added - download the users
|
||||||
if (added == now)
|
if (added == now)
|
||||||
@@ -36,8 +32,7 @@ namespace NadekoBot.Common
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
downloadUsersSemaphore.Release();
|
_downloadUsersSemaphore.Release();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,11 +0,0 @@
|
|||||||
using Discord;
|
|
||||||
using NadekoBot.Common;
|
|
||||||
|
|
||||||
namespace NadekoBot.Extensions
|
|
||||||
{
|
|
||||||
public static class BotCredentialsExtensions
|
|
||||||
{
|
|
||||||
public static bool IsOwner(this IBotCredentials creds, IUser user)
|
|
||||||
=> creds.OwnerIds.Contains(user.Id);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,77 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using NadekoBot.Common;
|
|
||||||
using NadekoBot.Modules.Music;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
using NadekoBot.Modules.Administration.Services;
|
|
||||||
using NadekoBot.Modules.Music.Resolvers;
|
|
||||||
using NadekoBot.Modules.Music.Services;
|
|
||||||
using StackExchange.Redis;
|
|
||||||
|
|
||||||
namespace NadekoBot.Extensions
|
|
||||||
{
|
|
||||||
public static class ServiceCollectionExtensions
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddBotStringsServices(this IServiceCollection services)
|
|
||||||
=> services
|
|
||||||
.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
|
||||||
.AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
|
|
||||||
.AddSingleton<IBotStrings, BotStrings>();
|
|
||||||
|
|
||||||
public static IServiceCollection AddConfigServices(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
var baseType = typeof(ConfigServiceBase<>);
|
|
||||||
|
|
||||||
foreach (var type in Assembly.GetCallingAssembly().ExportedTypes.Where(x => x.IsSealed))
|
|
||||||
{
|
|
||||||
if (type.BaseType?.IsGenericType == true && type.BaseType.GetGenericTypeDefinition() == baseType)
|
|
||||||
{
|
|
||||||
services.AddSingleton(type);
|
|
||||||
services.AddSingleton(x => (IConfigService)x.GetRequiredService(type));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddConfigMigrators(this IServiceCollection services)
|
|
||||||
=> services.AddSealedSubclassesOf(typeof(IConfigMigrator));
|
|
||||||
|
|
||||||
public static IServiceCollection AddMusic(this IServiceCollection services)
|
|
||||||
=> services
|
|
||||||
.AddSingleton<IMusicService, MusicService>()
|
|
||||||
.AddSingleton<ITrackResolveProvider, TrackResolveProvider>()
|
|
||||||
.AddSingleton<IYoutubeResolver, YtdlYoutubeResolver>()
|
|
||||||
.AddSingleton<ISoundcloudResolver, SoundcloudResolver>()
|
|
||||||
.AddSingleton<ILocalTrackResolver, LocalTrackResolver>()
|
|
||||||
.AddSingleton<IRadioResolver, RadioResolver>()
|
|
||||||
.AddSingleton<ITrackCacher, RedisTrackCacher>()
|
|
||||||
.AddSingleton<YtLoader>()
|
|
||||||
.AddSingleton<IPlaceholderProvider>(svc => svc.GetRequiredService<IMusicService>());
|
|
||||||
|
|
||||||
// consider using scrutor, because slightly different versions
|
|
||||||
// of this might be needed in several different places
|
|
||||||
public static IServiceCollection AddSealedSubclassesOf(this IServiceCollection services, Type baseType)
|
|
||||||
{
|
|
||||||
var subTypes = Assembly.GetCallingAssembly()
|
|
||||||
.ExportedTypes
|
|
||||||
.Where(type => type.IsSealed && baseType.IsAssignableFrom(type));
|
|
||||||
|
|
||||||
foreach (var subType in subTypes)
|
|
||||||
{
|
|
||||||
services.AddSingleton(baseType, subType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddRedis(this IServiceCollection services, string redisOptions)
|
|
||||||
{
|
|
||||||
var conf = ConfigurationOptions.Parse(redisOptions);
|
|
||||||
services.AddSingleton(ConnectionMultiplexer.Connect(conf));
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,255 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Discord
|
|
||||||
{
|
|
||||||
// just a copy paste from discord.net in order to rename it, for compatibility iwth v3 which is gonna use custom lib
|
|
||||||
|
|
||||||
|
|
||||||
// Summary:
|
|
||||||
// Defines the available permissions for a channel.
|
|
||||||
[Flags]
|
|
||||||
public enum GuildPerm : ulong
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows creation of instant invites.
|
|
||||||
CreateInstantInvite = 1,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows kicking members.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
KickMembers = 2,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows banning members.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
BanMembers = 4,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows all permissions and bypasses channel permission overwrites.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
Administrator = 8,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of channels.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageChannels = 16,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of the guild.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageGuild = 32,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for the addition of reactions to messages.
|
|
||||||
AddReactions = 64,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for viewing of audit logs.
|
|
||||||
ViewAuditLog = 128,
|
|
||||||
PrioritySpeaker = 256,
|
|
||||||
ReadMessages = 1024,
|
|
||||||
ViewChannel = 1024,
|
|
||||||
SendMessages = 2048,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for sending of text-to-speech messages.
|
|
||||||
SendTTSMessages = 4096,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for deletion of other users messages.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageMessages = 8192,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows links sent by users with this permission will be auto-embedded.
|
|
||||||
EmbedLinks = 16384,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for uploading images and files.
|
|
||||||
AttachFiles = 32768,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for reading of message history.
|
|
||||||
ReadMessageHistory = 65536,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for using the @everyone tag to notify all users in a channel, and the
|
|
||||||
// @here tag to notify all online users in a channel.
|
|
||||||
MentionEveryone = 131072,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows the usage of custom emojis from other servers.
|
|
||||||
UseExternalEmojis = 262144,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for joining of a voice channel.
|
|
||||||
Connect = 1048576,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for speaking in a voice channel.
|
|
||||||
Speak = 2097152,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for muting members in a voice channel.
|
|
||||||
MuteMembers = 4194304,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for deafening of members in a voice channel.
|
|
||||||
DeafenMembers = 8388608,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for moving of members between voice channels.
|
|
||||||
MoveMembers = 16777216,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for using voice-activity-detection in a voice channel.
|
|
||||||
UseVAD = 33554432,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for modification of own nickname.
|
|
||||||
ChangeNickname = 67108864,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for modification of other users nicknames.
|
|
||||||
ManageNicknames = 134217728,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of roles.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageRoles = 268435456,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of webhooks.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageWebhooks = 536870912,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of emojis.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageEmojis = 1073741824
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Defines the available permissions for a channel.
|
|
||||||
[Flags]
|
|
||||||
public enum ChannelPerm : ulong
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows creation of instant invites.
|
|
||||||
CreateInstantInvite = 1,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of channels.
|
|
||||||
ManageChannel = 16,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for the addition of reactions to messages.
|
|
||||||
AddReactions = 64,
|
|
||||||
PrioritySpeaker = 256,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for reading of messages. This flag is obsolete, use Discord.ChannelPermission.ViewChannel
|
|
||||||
// instead.
|
|
||||||
ReadMessages = 1024,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows guild members to view a channel, which includes reading messages in text
|
|
||||||
// channels.
|
|
||||||
ViewChannel = 1024,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for sending messages in a channel.
|
|
||||||
SendMessages = 2048,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for sending of text-to-speech messages.
|
|
||||||
SendTTSMessages = 4096,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for deletion of other users messages.
|
|
||||||
ManageMessages = 8192,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows links sent by users with this permission will be auto-embedded.
|
|
||||||
EmbedLinks = 16384,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for uploading images and files.
|
|
||||||
AttachFiles = 32768,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for reading of message history.
|
|
||||||
ReadMessageHistory = 65536,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for using the @everyone tag to notify all users in a channel, and the
|
|
||||||
// @here tag to notify all online users in a channel.
|
|
||||||
MentionEveryone = 131072,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows the usage of custom emojis from other servers.
|
|
||||||
UseExternalEmojis = 262144,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for joining of a voice channel.
|
|
||||||
Connect = 1048576,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for speaking in a voice channel.
|
|
||||||
Speak = 2097152,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for muting members in a voice channel.
|
|
||||||
MuteMembers = 4194304,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for deafening of members in a voice channel.
|
|
||||||
DeafenMembers = 8388608,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for moving of members between voice channels.
|
|
||||||
MoveMembers = 16777216,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for using voice-activity-detection in a voice channel.
|
|
||||||
UseVAD = 33554432,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of roles.
|
|
||||||
ManageRoles = 268435456,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of webhooks.
|
|
||||||
ManageWebhooks = 536870912
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,9 +1,8 @@
|
|||||||
using System;
|
#nullable disable
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
public static class Helpers
|
||||||
{
|
{
|
||||||
public static class Helpers
|
|
||||||
{
|
|
||||||
public static void ReadErrorAndExit(int exitCode)
|
public static void ReadErrorAndExit(int exitCode)
|
||||||
{
|
{
|
||||||
if (!Console.IsInputRedirected)
|
if (!Console.IsInputRedirected)
|
||||||
@@ -11,5 +10,4 @@ namespace NadekoBot.Common
|
|||||||
|
|
||||||
Environment.Exit(exitCode);
|
Environment.Exit(exitCode);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,38 +1,34 @@
|
|||||||
using System.Collections.Generic;
|
#nullable disable
|
||||||
using Discord;
|
namespace NadekoBot;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using NadekoBot.Common;
|
|
||||||
|
|
||||||
namespace NadekoBot
|
public interface IBotCredentials
|
||||||
{
|
{
|
||||||
public interface IBotCredentials
|
|
||||||
{
|
|
||||||
string Token { get; }
|
string Token { get; }
|
||||||
string GoogleApiKey { get; }
|
string GoogleApiKey { get; }
|
||||||
ICollection<ulong> OwnerIds { get; }
|
ICollection<ulong> OwnerIds { get; }
|
||||||
|
bool UsePrivilegedIntents { get; }
|
||||||
string RapidApiKey { get; }
|
string RapidApiKey { get; }
|
||||||
string PatreonAccessToken { get; }
|
|
||||||
|
|
||||||
Creds.DbOptions Db { get; }
|
Creds.DbOptions Db { get; }
|
||||||
string OsuApiKey { get; }
|
string OsuApiKey { get; }
|
||||||
int TotalShards { get; }
|
int TotalShards { get; }
|
||||||
string PatreonCampaignId { get; }
|
Creds.PatreonSettings Patreon { get; }
|
||||||
string CleverbotApiKey { get; }
|
string CleverbotApiKey { get; }
|
||||||
RestartConfig RestartCommand { get; }
|
RestartConfig RestartCommand { get; }
|
||||||
string VotesUrl { get; }
|
Creds.VotesSettings Votes { get; }
|
||||||
string VotesToken { get; }
|
|
||||||
string BotListToken { get; }
|
string BotListToken { get; }
|
||||||
string RedisOptions { get; }
|
string RedisOptions { get; }
|
||||||
string LocationIqApiKey { get; }
|
string LocationIqApiKey { get; }
|
||||||
string TimezoneDbApiKey { get; }
|
string TimezoneDbApiKey { get; }
|
||||||
string CoinmarketcapApiKey { get; }
|
string CoinmarketcapApiKey { get; }
|
||||||
|
string TrovoClientId { get; }
|
||||||
string CoordinatorUrl { get; set; }
|
string CoordinatorUrl { get; set; }
|
||||||
}
|
string TwitchClientId { get; set; }
|
||||||
|
string TwitchClientSecret { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class RestartConfig
|
public class RestartConfig
|
||||||
{
|
{
|
||||||
public string Cmd { get; set; }
|
public string Cmd { get; set; }
|
||||||
public string Args { get; set; }
|
public string Args { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,7 +1,8 @@
|
|||||||
namespace NadekoBot.Common
|
#nullable disable
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public interface ICloneable<T>
|
||||||
|
where T : new()
|
||||||
{
|
{
|
||||||
public interface ICloneable<T> where T : new()
|
|
||||||
{
|
|
||||||
public T Clone();
|
public T Clone();
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,9 +1,8 @@
|
|||||||
using Discord;
|
#nullable disable
|
||||||
|
namespace NadekoBot;
|
||||||
|
|
||||||
namespace NadekoBot
|
public interface IEmbedBuilder
|
||||||
{
|
{
|
||||||
public interface IEmbedBuilder
|
|
||||||
{
|
|
||||||
IEmbedBuilder WithDescription(string desc);
|
IEmbedBuilder WithDescription(string desc);
|
||||||
IEmbedBuilder WithTitle(string title);
|
IEmbedBuilder WithTitle(string title);
|
||||||
IEmbedBuilder AddField(string title, object value, bool isInline = false);
|
IEmbedBuilder AddField(string title, object value, bool isInline = false);
|
||||||
@@ -14,12 +13,11 @@ namespace NadekoBot
|
|||||||
IEmbedBuilder WithUrl(string url);
|
IEmbedBuilder WithUrl(string url);
|
||||||
IEmbedBuilder WithImageUrl(string url);
|
IEmbedBuilder WithImageUrl(string url);
|
||||||
IEmbedBuilder WithThumbnailUrl(string url);
|
IEmbedBuilder WithThumbnailUrl(string url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EmbedColor
|
public enum EmbedColor
|
||||||
{
|
{
|
||||||
Ok,
|
Ok,
|
||||||
Pending,
|
Pending,
|
||||||
Error,
|
Error
|
||||||
}
|
|
||||||
}
|
}
|
31
src/NadekoBot/Common/ILogCommandService.cs
Normal file
31
src/NadekoBot/Common/ILogCommandService.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public interface ILogCommandService
|
||||||
|
{
|
||||||
|
void AddDeleteIgnore(ulong xId);
|
||||||
|
Task LogServer(ulong guildId, ulong channelId, bool actionValue);
|
||||||
|
bool LogIgnore(ulong guildId, ulong itemId, IgnoredItemType itemType);
|
||||||
|
LogSetting? GetGuildLogSettings(ulong guildId);
|
||||||
|
bool Log(ulong guildId, ulong? channelId, LogType type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LogType
|
||||||
|
{
|
||||||
|
Other,
|
||||||
|
MessageUpdated,
|
||||||
|
MessageDeleted,
|
||||||
|
UserJoined,
|
||||||
|
UserLeft,
|
||||||
|
UserBanned,
|
||||||
|
UserUnbanned,
|
||||||
|
UserUpdated,
|
||||||
|
ChannelCreated,
|
||||||
|
ChannelDestroyed,
|
||||||
|
ChannelUpdated,
|
||||||
|
UserPresence,
|
||||||
|
VoicePresence,
|
||||||
|
VoicePresenceTts,
|
||||||
|
UserMuted
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
namespace NadekoBot.Common
|
#nullable disable
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public interface INadekoCommandOptions
|
||||||
{
|
{
|
||||||
public interface INadekoCommandOptions
|
|
||||||
{
|
|
||||||
void NormalizeOptions();
|
void NormalizeOptions();
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,10 +1,7 @@
|
|||||||
using System;
|
#nullable disable
|
||||||
using System.Collections.Generic;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
public interface IPlaceholderProvider
|
||||||
{
|
{
|
||||||
public interface IPlaceholderProvider
|
|
||||||
{
|
|
||||||
public IEnumerable<(string Name, Func<string> Func)> GetPlaceholders();
|
public IEnumerable<(string Name, Func<string> Func)> GetPlaceholders();
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,10 +1,12 @@
|
|||||||
using System;
|
#nullable disable
|
||||||
|
using NadekoBot.Common.Yml;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class ImageUrls
|
||||||
{
|
{
|
||||||
public class ImageUrls
|
[Comment("DO NOT CHANGE")]
|
||||||
{
|
public int Version { get; set; } = 3;
|
||||||
public int Version { get; set; } = 2;
|
|
||||||
|
|
||||||
public CoinData Coins { get; set; }
|
public CoinData Coins { get; set; }
|
||||||
public Uri[] Currency { get; set; }
|
public Uri[] Currency { get; set; }
|
||||||
@@ -25,7 +27,6 @@ namespace NadekoBot.Common
|
|||||||
public class SlotData
|
public class SlotData
|
||||||
{
|
{
|
||||||
public Uri[] Emojis { get; set; }
|
public Uri[] Emojis { get; set; }
|
||||||
public Uri[] Numbers { get; set; }
|
|
||||||
public Uri Bg { get; set; }
|
public Uri Bg { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,5 +46,4 @@ namespace NadekoBot.Common
|
|||||||
{
|
{
|
||||||
public Uri Bg { get; set; }
|
public Uri Bg { get; set; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
14
src/NadekoBot/Common/JsonConverters/CultureInfoConverter.cs
Normal file
14
src/NadekoBot/Common/JsonConverters/CultureInfoConverter.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Common.JsonConverters;
|
||||||
|
|
||||||
|
public class CultureInfoConverter : JsonConverter<CultureInfo>
|
||||||
|
{
|
||||||
|
public override CultureInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
=> new(reader.GetString() ?? "en-US");
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options)
|
||||||
|
=> writer.WriteStringValue(value.Name);
|
||||||
|
}
|
@@ -1,34 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.JsonConverters
|
|
||||||
{
|
|
||||||
public class Rgba32Converter : JsonConverter<Rgba32>
|
|
||||||
{
|
|
||||||
public override Rgba32 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
return Rgba32.ParseHex(reader.GetString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
writer.WriteStringValue(value.ToHex());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CultureInfoConverter : JsonConverter<CultureInfo>
|
|
||||||
{
|
|
||||||
public override CultureInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
return new CultureInfo(reader.GetString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
writer.WriteStringValue(value.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
14
src/NadekoBot/Common/JsonConverters/Rgba32Converter.cs
Normal file
14
src/NadekoBot/Common/JsonConverters/Rgba32Converter.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Common.JsonConverters;
|
||||||
|
|
||||||
|
public class Rgba32Converter : JsonConverter<Rgba32>
|
||||||
|
{
|
||||||
|
public override Rgba32 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
=> Rgba32.ParseHex(reader.GetString());
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options)
|
||||||
|
=> writer.WriteStringValue(value.ToHex());
|
||||||
|
}
|
@@ -1,14 +1,17 @@
|
|||||||
using System;
|
#nullable disable
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
// needs proper invalid input check (character array input out of range)
|
||||||
|
// needs negative number support
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
#pragma warning disable IDE1006
|
||||||
|
public readonly struct kwum : IEquatable<kwum>
|
||||||
|
#pragma warning restore IDE1006
|
||||||
{
|
{
|
||||||
// needs proper invalid input check (character array input out of range)
|
private const string VALID_CHARACTERS = "23456789abcdefghijkmnpqrstuvwxyz";
|
||||||
// needs negative number support
|
|
||||||
public readonly struct kwum : IEquatable<kwum>
|
|
||||||
{
|
|
||||||
private readonly int _value;
|
private readonly int _value;
|
||||||
private const string ValidCharacters = "23456789abcdefghijkmnpqrstuvwxyz";
|
|
||||||
|
|
||||||
public kwum(int num)
|
public kwum(int num)
|
||||||
=> _value = num;
|
=> _value = num;
|
||||||
@@ -21,12 +24,8 @@ namespace NadekoBot.Common
|
|||||||
_value = InternalCharToValue(c);
|
_value = InternalCharToValue(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static int InternalCharToValue(in char c)
|
|
||||||
=> ValidCharacters.IndexOf(c);
|
|
||||||
|
|
||||||
public kwum(in ReadOnlySpan<char> input)
|
public kwum(in ReadOnlySpan<char> input)
|
||||||
{;
|
{
|
||||||
_value = 0;
|
_value = 0;
|
||||||
for (var index = 0; index < input.Length; index++)
|
for (var index = 0; index < input.Length; index++)
|
||||||
{
|
{
|
||||||
@@ -34,23 +33,29 @@ namespace NadekoBot.Common
|
|||||||
if (!IsValidChar(c))
|
if (!IsValidChar(c))
|
||||||
throw new ArgumentException("All characters need to be a valid kwum characters.", nameof(input));
|
throw new ArgumentException("All characters need to be a valid kwum characters.", nameof(input));
|
||||||
|
|
||||||
_value += ValidCharacters.IndexOf(c) * (int)Math.Pow(ValidCharacters.Length, input.Length - index - 1);
|
_value += VALID_CHARACTERS.IndexOf(c) * (int)Math.Pow(VALID_CHARACTERS.Length, input.Length - index - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int InternalCharToValue(in char c)
|
||||||
|
=> VALID_CHARACTERS.IndexOf(c);
|
||||||
|
|
||||||
public static bool TryParse(in ReadOnlySpan<char> input, out kwum value)
|
public static bool TryParse(in ReadOnlySpan<char> input, out kwum value)
|
||||||
{
|
{
|
||||||
value = default;
|
value = default;
|
||||||
foreach(var c in input)
|
foreach (var c in input)
|
||||||
|
{
|
||||||
if (!IsValidChar(c))
|
if (!IsValidChar(c))
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
value = new kwum(input);
|
value = new(input);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static kwum operator +(kwum left, kwum right)
|
public static kwum operator +(kwum left, kwum right)
|
||||||
=> new kwum(left._value + right._value);
|
=> new(left._value + right._value);
|
||||||
|
|
||||||
public static bool operator ==(kwum left, kwum right)
|
public static bool operator ==(kwum left, kwum right)
|
||||||
=> left._value == right._value;
|
=> left._value == right._value;
|
||||||
@@ -63,25 +68,26 @@ namespace NadekoBot.Common
|
|||||||
|
|
||||||
public static implicit operator int(kwum kwum)
|
public static implicit operator int(kwum kwum)
|
||||||
=> kwum._value;
|
=> kwum._value;
|
||||||
|
|
||||||
public static implicit operator kwum(int num)
|
public static implicit operator kwum(int num)
|
||||||
=> new kwum(num);
|
=> new(num);
|
||||||
|
|
||||||
public static bool IsValidChar(char c)
|
public static bool IsValidChar(char c)
|
||||||
=> ValidCharacters.Contains(c);
|
=> VALID_CHARACTERS.Contains(c);
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var count = ValidCharacters.Length;
|
var count = VALID_CHARACTERS.Length;
|
||||||
var localValue = _value;
|
var localValue = _value;
|
||||||
var arrSize = (int)Math.Log(localValue, count) + 1;
|
var arrSize = (int)Math.Log(localValue, count) + 1;
|
||||||
Span<char> chars = new char[arrSize];
|
Span<char> chars = new char[arrSize];
|
||||||
while (localValue > 0)
|
while (localValue > 0)
|
||||||
{
|
{
|
||||||
localValue = Math.DivRem(localValue, count, out var rem);
|
localValue = Math.DivRem(localValue, count, out var rem);
|
||||||
chars[--arrSize] = ValidCharacters[(int)rem];
|
chars[--arrSize] = VALID_CHARACTERS[rem];
|
||||||
}
|
}
|
||||||
|
|
||||||
return new string(chars);
|
return new(chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
@@ -91,8 +97,5 @@ namespace NadekoBot.Common
|
|||||||
=> other == this;
|
=> other == this;
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
=> _value.GetHashCode();
|
||||||
return _value.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,14 +1,14 @@
|
|||||||
using CommandLine;
|
#nullable disable
|
||||||
|
using CommandLine;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class LbOpts : INadekoCommandOptions
|
||||||
{
|
{
|
||||||
public class LbOpts : INadekoCommandOptions
|
|
||||||
{
|
|
||||||
[Option('c', "clean", Default = false, HelpText = "Only show users who are on the server.")]
|
[Option('c', "clean", Default = false, HelpText = "Only show users who are on the server.")]
|
||||||
public bool Clean { get; set; }
|
public bool Clean { get; set; }
|
||||||
|
|
||||||
public void NormalizeOptions()
|
public void NormalizeOptions()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,18 +1,14 @@
|
|||||||
using System;
|
#nullable disable
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Discord.Net;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class LoginErrorHandler
|
||||||
{
|
{
|
||||||
public class LoginErrorHandler
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Handle(Exception ex)
|
public static void Handle(Exception ex)
|
||||||
{
|
=> Log.Fatal(ex, "A fatal error has occurred while attempting to connect to Discord");
|
||||||
Log.Fatal(ex, "A fatal error has occurred while attempting to connect to Discord");
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Handle(HttpException ex)
|
public static void Handle(HttpException ex)
|
||||||
@@ -20,20 +16,20 @@ namespace NadekoBot.Common
|
|||||||
switch (ex.HttpCode)
|
switch (ex.HttpCode)
|
||||||
{
|
{
|
||||||
case HttpStatusCode.Unauthorized:
|
case HttpStatusCode.Unauthorized:
|
||||||
Log.Error("Your bot token is wrong.\n" +
|
Log.Error("Your bot token is wrong.\n"
|
||||||
"You can find the bot token under the Bot tab in the developer page.\n" +
|
+ "You can find the bot token under the Bot tab in the developer page.\n"
|
||||||
"Fix your token in the credentials file and restart the bot");
|
+ "Fix your token in the credentials file and restart the bot");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.BadRequest:
|
case HttpStatusCode.BadRequest:
|
||||||
Log.Error("Something has been incorrectly formatted in your credentials file.\n" +
|
Log.Error("Something has been incorrectly formatted in your credentials file.\n"
|
||||||
"Use the JSON Guide as reference to fix it and restart the bot.");
|
+ "Use the JSON Guide as reference to fix it and restart the bot");
|
||||||
Log.Error("If you are on Linux, make sure Redis is installed and running");
|
Log.Error("If you are on Linux, make sure Redis is installed and running");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.RequestTimeout:
|
case HttpStatusCode.RequestTimeout:
|
||||||
Log.Error("The request timed out. Make sure you have no external program blocking the bot " +
|
Log.Error("The request timed out. Make sure you have no external program blocking the bot "
|
||||||
"from connecting to the internet");
|
+ "from connecting to the internet");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.ServiceUnavailable:
|
case HttpStatusCode.ServiceUnavailable:
|
||||||
@@ -42,8 +38,8 @@ namespace NadekoBot.Common
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.TooManyRequests:
|
case HttpStatusCode.TooManyRequests:
|
||||||
Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n" +
|
Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n"
|
||||||
"Global ratelimits usually last for an hour");
|
+ "Global ratelimits usually last for an hour");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -51,7 +47,6 @@ namespace NadekoBot.Common
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Fatal(ex.ToString());
|
Log.Fatal(ex, "Fatal error occurred while loading credentials");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,14 +1,10 @@
|
|||||||
using System.Threading.Tasks;
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
using Discord;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
/// <summary>
|
||||||
|
/// Implemented by modules which block execution before anything is executed
|
||||||
|
/// </summary>
|
||||||
|
public interface IEarlyBehavior
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Implemented by modules which block execution before anything is executed
|
|
||||||
/// </summary>
|
|
||||||
public interface IEarlyBehavior
|
|
||||||
{
|
|
||||||
int Priority { get; }
|
int Priority { get; }
|
||||||
Task<bool> RunBehavior(IGuild guild, IUserMessage msg);
|
Task<bool> RunBehavior(IGuild guild, IUserMessage msg);
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,10 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Discord;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
|
||||||
{
|
|
||||||
public interface IInputTransformer
|
|
||||||
{
|
|
||||||
Task<string> TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input);
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user