mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Compare commits
163 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cc522ef872 | ||
|
0e192ee7f0 | ||
|
ddd0592b30 | ||
|
c4efe2965b | ||
|
a90b5a62f3 | ||
|
50a4497532 | ||
|
c3d6183d73 | ||
|
864a8fd7b6 | ||
|
e7db631151 | ||
|
c7b312196e | ||
|
8cd7a50720 | ||
|
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 | ||
|
9223d78849 | ||
|
edd60ae656 | ||
|
da2ee0c158 | ||
|
1b2017024c | ||
|
345a9e9524 | ||
|
cd379fd308 | ||
|
ee33313519 | ||
|
bc31dae965 |
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
|
||||||
|
|
||||||
|
158
.gitlab-ci.yml
158
.gitlab-ci.yml
@@ -1,4 +1,4 @@
|
|||||||
image: mcr.microsoft.com/dotnet/sdk:5.0
|
image: mcr.microsoft.com/dotnet/sdk:6.0
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
@@ -30,29 +30,29 @@ build:
|
|||||||
- "$WIN_X64_OUTPUT_DIR/"
|
- "$WIN_X64_OUTPUT_DIR/"
|
||||||
|
|
||||||
upload-builds:
|
upload-builds:
|
||||||
stage: upload-builds
|
stage: upload-builds
|
||||||
image: alpine:latest
|
image: alpine:latest
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_TAG
|
- if: $CI_COMMIT_TAG
|
||||||
script:
|
script:
|
||||||
- apk add --no-cache curl tar zip
|
- apk add --no-cache curl tar zip
|
||||||
- "tar cvf $LINUX_X64_RELEASE $LINUX_X64_OUTPUT_DIR/*"
|
- "tar cvf $LINUX_X64_RELEASE $LINUX_X64_OUTPUT_DIR/*"
|
||||||
- "zip -r $WIN_X64_RELEASE $WIN_X64_OUTPUT_DIR/*"
|
- "zip -r $WIN_X64_RELEASE $WIN_X64_OUTPUT_DIR/*"
|
||||||
- |
|
- |
|
||||||
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $LINUX_X64_RELEASE $PACKAGE_REGISTRY_URL/$LINUX_X64_RELEASE
|
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $LINUX_X64_RELEASE $PACKAGE_REGISTRY_URL/$LINUX_X64_RELEASE
|
||||||
- |
|
- |
|
||||||
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $WIN_X64_RELEASE $PACKAGE_REGISTRY_URL/$WIN_X64_RELEASE
|
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $WIN_X64_RELEASE $PACKAGE_REGISTRY_URL/$WIN_X64_RELEASE
|
||||||
|
|
||||||
release:
|
release:
|
||||||
stage: release
|
stage: release
|
||||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||||
rules:
|
rules:
|
||||||
- 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}\"}"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
@@ -63,63 +63,63 @@ test:
|
|||||||
- "dotnet test"
|
- "dotnet test"
|
||||||
|
|
||||||
publish-windows:
|
publish-windows:
|
||||||
stage: publish-windows
|
stage: publish-windows
|
||||||
rules:
|
rules:
|
||||||
- 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:
|
||||||
- "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
- "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
||||||
script:
|
script:
|
||||||
- dotnet clean
|
- dotnet clean
|
||||||
- dotnet restore
|
- dotnet restore
|
||||||
- dotnet publish -c Release --runtime win7-x64 /p:Version=$CI_COMMIT_TAG src/NadekoBot
|
- dotnet publish -c Release --runtime win7-x64 /p:Version=$CI_COMMIT_TAG src/NadekoBot
|
||||||
- $env:NADEKOBOT_INSTALL_VERSION = $CI_COMMIT_TAG
|
- $env:NADEKOBOT_INSTALL_VERSION = $CI_COMMIT_TAG
|
||||||
- iscc.exe "/O+" ".\exe_builder.iss"
|
- iscc.exe "/O+" ".\exe_builder.iss"
|
||||||
tags:
|
tags:
|
||||||
- windows
|
- windows
|
||||||
|
|
||||||
upload-windows-updater-release:
|
upload-windows-updater-release:
|
||||||
stage: upload-windows-updater-release
|
stage: upload-windows-updater-release
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_TAG'
|
- if: '$CI_COMMIT_TAG'
|
||||||
image:
|
image:
|
||||||
name: amazon/aws-cli
|
name: amazon/aws-cli
|
||||||
entrypoint: [""]
|
entrypoint: [""]
|
||||||
script:
|
script:
|
||||||
- sed -i "s/_INSTALLER_FILE_NAME_/$INSTALLER_FILE_NAME/g" releases-v3.json
|
- sed -i "s/_INSTALLER_FILE_NAME_/$INSTALLER_FILE_NAME/g" releases-v3.json
|
||||||
- sed -i "s/_VERSION_/$CI_COMMIT_TAG/g" releases-v3.json
|
- sed -i "s/_VERSION_/$CI_COMMIT_TAG/g" releases-v3.json
|
||||||
- aws --version
|
- aws --version
|
||||||
- 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
|
||||||
|
98
CHANGELOG.md
98
CHANGELOG.md
@@ -1,8 +1,102 @@
|
|||||||
# 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
|
||||||
|
|
||||||
## [3.0.12] = 06.01.2021
|
## Unreleased
|
||||||
|
|
||||||
|
- More cool stuff coming soon
|
||||||
|
|
||||||
|
## [4.0.4] - 04.03.2022
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the `id` which shows up when you add a new Expression
|
||||||
|
- Fixed some strings which were still referring to "CustomReaction(s)" instead of "Expression(s)"
|
||||||
|
|
||||||
|
## [4.0.3] - 04.03.2022
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Console should no longer spam numbers when `.antispam` is enabled
|
||||||
|
|
||||||
|
## [4.0.2] - 03.03.2022
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `.rero` not working due to a bug introduced in 4.0
|
||||||
|
|
||||||
|
## [4.0.1] - 03.03.2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- 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 twitch stream notifications (rewrote it to use the new api)
|
||||||
|
- 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
|
### Fixed
|
||||||
- `.smch` Fixed
|
- `.smch` Fixed
|
||||||
@@ -399,4 +493,4 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
|
|||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed admin requirement on `.scrm` as it didn't make sense
|
- Removed admin requirement on `.scrm` as it didn't make sense
|
||||||
- Some Music commands are removed because of the complexity they bring in with little value (if you *really* want them back, you can open an issue and specify your *good* reason)
|
- Some Music commands are removed because of the complexity they bring in with little value (if you *really* want them back, you can open an issue and specify your *good* reason)
|
||||||
|
@@ -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,7 +18,7 @@ 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; \
|
||||||
|
@@ -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,11 +13,11 @@ 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
|
- Scroll down to the **`Privileged Gateway Intents`** section
|
||||||
- Enabled the following:
|
- **Enable the following:**
|
||||||
- PRESENCE INTENT
|
- **PRESENCE INTENT**
|
||||||
- SERVER MEMBERS INTENT
|
- **SERVER MEMBERS INTENT**
|
||||||
- MESSAGE CONTENT INTENT
|
- **MESSAGE CONTENT INTENT**
|
||||||
|
|
||||||
These are required for a number of features to function properly, and all should be on.
|
These are required for a number of features to function properly, and all should be on.
|
||||||
|
|
||||||
|
@@ -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,19 +1,49 @@
|
|||||||
## 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
|
||||||
|
|
||||||
Open Terminal (if you're on an installation with a window manager) and navigate to the location where you want to install the bot (for example `cd ~`)
|
##### Migration from v3 -> v4
|
||||||
|
|
||||||
|
Follow the following few steps only if you're migrating from v3. If not, skip to installation instructions.
|
||||||
|
|
||||||
|
Use the new installer script: `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/v4/linuxAIO.sh && bash linuxAIO.sh`
|
||||||
|
> - Install prerequisites (type `1` and press `enter`)
|
||||||
|
> - Download (type `2` and press `enter`)
|
||||||
|
> - Run (type `3` and press `enter`)
|
||||||
|
> - Done
|
||||||
|
|
||||||
##### 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`
|
Open Terminal (if you're on an installation with a window manager) and navigate to the location where you want to install the bot (for example `cd ~`)
|
||||||
|
|
||||||
|
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/v4/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 (type `5` and press enter)
|
4. Exit the installer (type `6` 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`
|
||||||
@@ -22,18 +52,21 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
|||||||
- `CTRL` + `X`
|
- `CTRL` + `X`
|
||||||
- `Y`
|
- `Y`
|
||||||
- `Enter`
|
- `Enter`
|
||||||
8. Run the bot (type `3` and press enter)
|
8. Run the installer script again `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/v4/linuxAIO.sh && bash linuxAIO.sh`
|
||||||
|
9. 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/v4/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)
|
||||||
5. 🎉
|
5. 🎉
|
||||||
|
|
||||||
## 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>
|
||||||
@@ -57,7 +90,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 +137,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.
|
||||||
|
|
||||||
@@ -275,3 +308,12 @@ If you are running your droplet for the first time, it will most likely ask you
|
|||||||
**Save the new password somewhere safe.**
|
**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.
|
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.
|
|
@@ -12,7 +12,7 @@ Open Terminal (if you don't know how to, click on the magnifying glass on the to
|
|||||||
- `brew install wget`
|
- `brew install wget`
|
||||||
|
|
||||||
###### Dotnet
|
###### Dotnet
|
||||||
- Download [.net5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0)
|
- Download [.net6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0)
|
||||||
- Open the `.pkg` file you've downloaded and install it.
|
- 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)
|
- Run this command in Terminal. There might be output. If there is, disregard it. (copy-paste the entire block)
|
||||||
```bash
|
```bash
|
||||||
@@ -31,7 +31,7 @@ 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/v4/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 in order to set up your `creds.yml`
|
||||||
@@ -49,13 +49,15 @@ sudo ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/
|
|||||||
##### Update Instructions
|
##### 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/v4/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)
|
||||||
5. 🎉
|
5. 🎉
|
||||||
|
|
||||||
## 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>
|
||||||
@@ -120,4 +122,4 @@ rm -r nadekobot-old/data/strings && \
|
|||||||
cp -RT nadekobot-old/data/ nadekobot/data/ && \
|
cp -RT nadekobot-old/data/ nadekobot/data/ && \
|
||||||
cp nadekobot-old/creds.yml nadekobot/ && \
|
cp nadekobot-old/creds.yml nadekobot/ && \
|
||||||
cd nadekobot && chmod +x NadekoBot
|
cd nadekobot && chmod +x NadekoBot
|
||||||
```
|
```
|
||||||
|
@@ -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|
|
||||||
@@ -48,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
|
||||||
@@ -69,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
|
||||||
|
|
||||||
@@ -80,7 +78,7 @@ 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`
|
4. `cd output`
|
||||||
@@ -124,7 +122,7 @@ In order to use music commands, you need ffmpeg and youtube-dl installed.
|
|||||||
- [ffmpeg-32bit] | [ffmpeg-64bit] - Download the **appropriate version** for your system (32 bit if you're running a 32 bit OS, or 64 if you're running a 64bit OS). Unzip it, and move `ffmpeg.exe` to a path that's in your PATH environment variable. If you don't know what that is, just move the `ffmpeg.exe` file to `NadekoBot/output`.
|
- [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
|
||||||
|
@@ -75,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`
|
||||||
@@ -161,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,7 +9,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.AspNetCore" Version="2.41.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.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||||
|
@@ -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();
|
||||||
|
|
||||||
@@ -191,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,
|
||||||
@@ -205,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)
|
||||||
{
|
{
|
||||||
@@ -239,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,
|
||||||
@@ -284,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();
|
||||||
@@ -314,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,
|
||||||
});
|
});
|
||||||
@@ -346,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;
|
||||||
}
|
}
|
||||||
@@ -357,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
|
||||||
{
|
{
|
||||||
@@ -365,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,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.1.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,10 +9,8 @@ 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()
|
||||||
{
|
{
|
||||||
|
@@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Authentication;
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using NadekoBot.VotesApi.Controllers;
|
|
||||||
|
|
||||||
namespace NadekoBot.VotesApi
|
namespace NadekoBot.VotesApi
|
||||||
{
|
{
|
||||||
@@ -24,9 +23,7 @@ namespace NadekoBot.VotesApi
|
|||||||
ISystemClock clock,
|
ISystemClock clock,
|
||||||
IConfiguration conf)
|
IConfiguration conf)
|
||||||
: base(options, logger, encoder, clock)
|
: base(options, logger, encoder, clock)
|
||||||
{
|
=> _conf = conf;
|
||||||
_conf = conf;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
public bool Weekend { get; set; }
|
public bool Weekend { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Query string params found on the /bot/:ID/vote page. Example: ?a=1&b=2.
|
/// Query string params found on the /bot/:ID/vote page. Example: ?a=1&b=2.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Query { get; set; }
|
public string Query { get; set; }
|
||||||
}
|
}
|
||||||
|
@@ -11,10 +11,10 @@ namespace NadekoBot.VotesApi.Controllers
|
|||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
public class DiscordsController : ControllerBase
|
public class DiscordsController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly ILogger<TopGgController> _logger;
|
private readonly ILogger<DiscordsController> _logger;
|
||||||
private readonly IVotesCache _cache;
|
private readonly IVotesCache _cache;
|
||||||
|
|
||||||
public DiscordsController(ILogger<TopGgController> logger, IVotesCache cache)
|
public DiscordsController(ILogger<DiscordsController> logger, IVotesCache cache)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
@@ -26,7 +26,7 @@ namespace NadekoBot.VotesApi.Controllers
|
|||||||
{
|
{
|
||||||
var votes = await _cache.GetNewDiscordsVotesAsync();
|
var votes = await _cache.GetNewDiscordsVotesAsync();
|
||||||
if(votes.Count > 0)
|
if(votes.Count > 0)
|
||||||
_logger.LogInformation("Sending {NewDiscordsVotes} new discords votes.", votes.Count);
|
_logger.LogInformation("Sending {NewDiscordsVotes} new discords votes", votes.Count);
|
||||||
return votes;
|
return votes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ namespace NadekoBot.VotesApi.Controllers
|
|||||||
{
|
{
|
||||||
var votes = await _cache.GetNewTopGgVotesAsync();
|
var votes = await _cache.GetNewTopGgVotesAsync();
|
||||||
if(votes.Count > 0)
|
if(votes.Count > 0)
|
||||||
_logger.LogInformation("Sending {NewTopggVotes} new topgg votes.", votes.Count);
|
_logger.LogInformation("Sending {NewTopggVotes} new topgg votes", votes.Count);
|
||||||
|
|
||||||
return votes;
|
return votes;
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NadekoBot.VotesApi.Services;
|
using NadekoBot.VotesApi.Services;
|
||||||
|
|
||||||
@@ -13,13 +11,11 @@ namespace NadekoBot.VotesApi.Controllers
|
|||||||
{
|
{
|
||||||
private readonly ILogger<WebhookController> _logger;
|
private readonly ILogger<WebhookController> _logger;
|
||||||
private readonly IVotesCache _votesCache;
|
private readonly IVotesCache _votesCache;
|
||||||
private readonly IConfiguration _conf;
|
|
||||||
|
|
||||||
public WebhookController(ILogger<WebhookController> logger, IVotesCache votesCache, IConfiguration conf)
|
public WebhookController(ILogger<WebhookController> logger, IVotesCache votesCache)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_votesCache = votesCache;
|
_votesCache = votesCache;
|
||||||
_conf = conf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("/discordswebhook")]
|
[HttpPost("/discordswebhook")]
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
|
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -1,23 +1,9 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using NadekoBot.VotesApi;
|
||||||
|
|
||||||
namespace NadekoBot.VotesApi
|
CreateHostBuilder(args).Build().Run();
|
||||||
{
|
|
||||||
public class Program
|
|
||||||
{
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
CreateHostBuilder(args).Build().Run();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
Host.CreateDefaultBuilder(args)
|
Host.CreateDefaultBuilder(args)
|
||||||
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
||||||
}
|
|
||||||
}
|
|
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -10,37 +9,33 @@ namespace NadekoBot.VotesApi.Services
|
|||||||
{
|
{
|
||||||
public class FileVotesCache : IVotesCache
|
public class FileVotesCache : IVotesCache
|
||||||
{
|
{
|
||||||
private const string statsFile = "store/stats.json";
|
// private const string STATS_FILE = "store/stats.json";
|
||||||
private const string topggFile = "store/topgg.json";
|
private const string TOPGG_FILE = "store/topgg.json";
|
||||||
private const string discordsFile = "store/discords.json";
|
private const string DISCORDS_FILE = "store/discords.json";
|
||||||
|
|
||||||
private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
public FileVotesCache()
|
public FileVotesCache()
|
||||||
{
|
{
|
||||||
if (!Directory.Exists("store"))
|
if (!Directory.Exists("store"))
|
||||||
Directory.CreateDirectory("store");
|
Directory.CreateDirectory("store");
|
||||||
|
|
||||||
if(!File.Exists(topggFile))
|
if(!File.Exists(TOPGG_FILE))
|
||||||
File.WriteAllText(topggFile, "[]");
|
File.WriteAllText(TOPGG_FILE, "[]");
|
||||||
|
|
||||||
if(!File.Exists(discordsFile))
|
if(!File.Exists(DISCORDS_FILE))
|
||||||
File.WriteAllText(discordsFile, "[]");
|
File.WriteAllText(DISCORDS_FILE, "[]");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITask AddNewTopggVote(string userId)
|
public ITask AddNewTopggVote(string userId)
|
||||||
{
|
=> AddNewVote(TOPGG_FILE, userId);
|
||||||
return AddNewVote(topggFile, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITask AddNewDiscordsVote(string userId)
|
public ITask AddNewDiscordsVote(string userId)
|
||||||
{
|
=> AddNewVote(DISCORDS_FILE, userId);
|
||||||
return AddNewVote(discordsFile, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ITask AddNewVote(string file, string userId)
|
private async ITask AddNewVote(string file, string userId)
|
||||||
{
|
{
|
||||||
await locker.WaitAsync();
|
await _locker.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var votes = await GetVotesAsync(file);
|
var votes = await GetVotesAsync(file);
|
||||||
@@ -49,7 +44,7 @@ namespace NadekoBot.VotesApi.Services
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
locker.Release();
|
_locker.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,14 +61,14 @@ namespace NadekoBot.VotesApi.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ITask<List<Vote>> EvictTopggVotes()
|
private ITask<List<Vote>> EvictTopggVotes()
|
||||||
=> EvictVotes(topggFile);
|
=> EvictVotes(TOPGG_FILE);
|
||||||
|
|
||||||
private ITask<List<Vote>> EvictDiscordsVotes()
|
private ITask<List<Vote>> EvictDiscordsVotes()
|
||||||
=> EvictVotes(discordsFile);
|
=> EvictVotes(DISCORDS_FILE);
|
||||||
|
|
||||||
private async ITask<List<Vote>> EvictVotes(string file)
|
private async ITask<List<Vote>> EvictVotes(string file)
|
||||||
{
|
{
|
||||||
await locker.WaitAsync();
|
await _locker.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -91,7 +86,7 @@ namespace NadekoBot.VotesApi.Services
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
locker.Release();
|
_locker.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,19 +11,18 @@ namespace NadekoBot.VotesApi
|
|||||||
{
|
{
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
public Startup(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
Configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IConfiguration Configuration { get; }
|
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.
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddControllers();
|
services.AddControllers();
|
||||||
services.AddSingleton<IVotesCache, FileVotesCache>();
|
services.AddSingleton<IVotesCache, FileVotesCache>();
|
||||||
services.AddSwaggerGen(c =>
|
services.AddSwaggerGen(static c =>
|
||||||
{
|
{
|
||||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "NadekoBot.VotesApi", Version = "v1" });
|
c.SwaggerDoc("v1", new OpenApiInfo { Title = "NadekoBot.VotesApi", Version = "v1" });
|
||||||
});
|
});
|
||||||
@@ -36,13 +35,13 @@ namespace NadekoBot.VotesApi
|
|||||||
});
|
});
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddAuthorization(opts =>
|
.AddAuthorization(static opts =>
|
||||||
{
|
{
|
||||||
opts.DefaultPolicy = new AuthorizationPolicyBuilder(AuthHandler.SchemeName)
|
opts.DefaultPolicy = new AuthorizationPolicyBuilder(AuthHandler.SchemeName)
|
||||||
.RequireAssertion(x => false)
|
.RequireAssertion(static _ => false)
|
||||||
.Build();
|
.Build();
|
||||||
opts.AddPolicy(Policies.DiscordsAuth, policy => policy.RequireClaim(AuthHandler.DiscordsClaim));
|
opts.AddPolicy(Policies.DiscordsAuth, static policy => policy.RequireClaim(AuthHandler.DiscordsClaim));
|
||||||
opts.AddPolicy(Policies.TopggAuth, policy => policy.RequireClaim(AuthHandler.TopggClaim));
|
opts.AddPolicy(Policies.TopggAuth, static policy => policy.RequireClaim(AuthHandler.TopggClaim));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ namespace NadekoBot.VotesApi
|
|||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "NadekoBot.VotesApi v1"));
|
app.UseSwaggerUI(static c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "NadekoBot.VotesApi v1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
@@ -63,7 +62,7 @@ namespace NadekoBot.VotesApi
|
|||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
|
app.UseEndpoints(static endpoints => { endpoints.MapControllers(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,3 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace NadekoBot.VotesApi
|
namespace NadekoBot.VotesApi
|
||||||
{
|
{
|
||||||
public class Vote
|
public class Vote
|
||||||
|
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,365 +1,383 @@
|
|||||||
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 NadekoBot.Modules.Searches;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
public sealed class Bot
|
||||||
{
|
{
|
||||||
public sealed class Bot
|
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
||||||
|
|
||||||
|
public DiscordSocketClient Client { get; }
|
||||||
|
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
|
||||||
|
|
||||||
|
private IServiceProvider Services { get; set; }
|
||||||
|
|
||||||
|
public string Mention { 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)
|
||||||
{
|
{
|
||||||
private readonly IBotCredentials _creds;
|
if (shardId < 0)
|
||||||
private readonly CommandService _commandService;
|
throw new ArgumentOutOfRangeException(nameof(shardId));
|
||||||
private readonly DbService _db;
|
|
||||||
private readonly IBotCredsProvider _credsProvider;
|
|
||||||
|
|
||||||
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
|
||||||
|
|
||||||
public DiscordSocketClient Client { get; }
|
|
||||||
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
|
|
||||||
|
|
||||||
private IServiceProvider Services { get; set; }
|
ShardId = shardId;
|
||||||
|
_credsProvider = new BotCredsProvider(totalShards);
|
||||||
public string Mention { get; private set; }
|
_creds = _credsProvider.GetCreds();
|
||||||
public bool IsReady { get; private set; }
|
|
||||||
|
|
||||||
public Bot(int shardId, int? totalShards)
|
_db = new(_creds);
|
||||||
{
|
|
||||||
if (shardId < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(shardId));
|
|
||||||
|
|
||||||
_credsProvider = new BotCredsProvider(totalShards);
|
if (shardId == 0)
|
||||||
_creds = _credsProvider.GetCreds();
|
_db.Setup();
|
||||||
|
|
||||||
_db = new DbService(_creds);
|
|
||||||
|
|
||||||
if (shardId == 0)
|
var messageCacheSize =
|
||||||
{
|
|
||||||
_db.Setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
Client = new DiscordSocketClient(new DiscordSocketConfig
|
|
||||||
{
|
|
||||||
MessageCacheSize = 50,
|
|
||||||
LogLevel = LogSeverity.Warning,
|
|
||||||
ConnectionTimeout = int.MaxValue,
|
|
||||||
TotalShards = _creds.TotalShards,
|
|
||||||
ShardId = shardId,
|
|
||||||
AlwaysDownloadUsers = false,
|
|
||||||
ExclusiveBulkDelete = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
_commandService = new CommandService(new CommandServiceConfig()
|
|
||||||
{
|
|
||||||
CaseSensitiveCommands = false,
|
|
||||||
DefaultRunMode = RunMode.Sync,
|
|
||||||
});
|
|
||||||
|
|
||||||
#if GLOBAL_NADEKO || DEBUG
|
|
||||||
Client.Log += Client_Log;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ulong> GetCurrentGuildIds()
|
|
||||||
{
|
|
||||||
return Client.Guilds.Select(x => x.Id).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddServices()
|
|
||||||
{
|
|
||||||
var startingGuildIdList = GetCurrentGuildIds();
|
|
||||||
var sw = Stopwatch.StartNew();
|
|
||||||
var _bot = Client.CurrentUser;
|
|
||||||
|
|
||||||
using (var uow = _db.GetDbContext())
|
|
||||||
{
|
|
||||||
uow.EnsureUserCreated(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId);
|
|
||||||
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
var svcs = new ServiceCollection()
|
|
||||||
.AddTransient<IBotCredentials>(_ => _credsProvider.GetCreds()) // bot creds
|
|
||||||
.AddSingleton<IBotCredsProvider>(_credsProvider)
|
|
||||||
.AddSingleton(_db) // database
|
|
||||||
.AddRedis(_creds.RedisOptions) // redis
|
|
||||||
.AddSingleton(Client) // discord socket client
|
|
||||||
.AddSingleton(_commandService)
|
|
||||||
.AddSingleton(this)
|
|
||||||
.AddSingleton<ISeria, JsonSeria>()
|
|
||||||
.AddSingleton<IPubSub, RedisPubSub>()
|
|
||||||
.AddSingleton<IConfigSeria, YamlSeria>()
|
|
||||||
.AddBotStringsServices(_creds.TotalShards)
|
|
||||||
.AddConfigServices()
|
|
||||||
.AddConfigMigrators()
|
|
||||||
.AddMemoryCache()
|
|
||||||
// music
|
|
||||||
.AddMusic()
|
|
||||||
// admin
|
|
||||||
#if GLOBAL_NADEKO
|
#if GLOBAL_NADEKO
|
||||||
.AddSingleton<ILogCommandService, DummyLogCommandService>()
|
0;
|
||||||
#else
|
#else
|
||||||
.AddSingleton<ILogCommandService, LogCommandService>()
|
50;
|
||||||
#endif
|
#endif
|
||||||
;
|
|
||||||
|
|
||||||
svcs.AddHttpClient();
|
if(!_creds.UsePrivilegedIntents)
|
||||||
svcs.AddHttpClient("memelist").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
|
Log.Warning("You are not using privileged intents. Some features will not work properly");
|
||||||
|
|
||||||
|
Client = new(new()
|
||||||
|
{
|
||||||
|
MessageCacheSize = messageCacheSize,
|
||||||
|
LogLevel = LogSeverity.Warning,
|
||||||
|
ConnectionTimeout = int.MaxValue,
|
||||||
|
TotalShards = _creds.TotalShards,
|
||||||
|
ShardId = shardId,
|
||||||
|
AlwaysDownloadUsers = false,
|
||||||
|
AlwaysResolveStickers = false,
|
||||||
|
AlwaysDownloadDefaultStickers = false,
|
||||||
|
GatewayIntents = _creds.UsePrivilegedIntents
|
||||||
|
? GatewayIntents.All
|
||||||
|
: GatewayIntents.AllUnprivileged,
|
||||||
|
LogGatewayIntentWarnings = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
_commandService = new(new()
|
||||||
|
{
|
||||||
|
CaseSensitiveCommands = false,
|
||||||
|
DefaultRunMode = RunMode.Sync
|
||||||
|
});
|
||||||
|
|
||||||
|
// _interactionService = new(Client.Rest);
|
||||||
|
|
||||||
|
Client.Log += Client_Log;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<ulong> GetCurrentGuildIds()
|
||||||
|
=> Client.Guilds.Select(x => x.Id).ToList();
|
||||||
|
|
||||||
|
private void AddServices()
|
||||||
|
{
|
||||||
|
var startingGuildIdList = GetCurrentGuildIds();
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
var bot = Client.CurrentUser;
|
||||||
|
|
||||||
|
using (var uow = _db.GetDbContext())
|
||||||
|
{
|
||||||
|
uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId);
|
||||||
|
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
var svcs = new ServiceCollection().AddTransient(_ => _credsProvider.GetCreds()) // bot creds
|
||||||
|
.AddSingleton(_credsProvider)
|
||||||
|
.AddSingleton(_db) // database
|
||||||
|
.AddRedis(_creds.RedisOptions) // redis
|
||||||
|
.AddSingleton(Client) // discord socket client
|
||||||
|
.AddSingleton(_commandService)
|
||||||
|
// .AddSingleton(_interactionService)
|
||||||
|
.AddSingleton(this)
|
||||||
|
.AddSingleton<ISeria, JsonSeria>()
|
||||||
|
.AddSingleton<IPubSub, RedisPubSub>()
|
||||||
|
.AddSingleton<IConfigSeria, YamlSeria>()
|
||||||
|
.AddBotStringsServices(_creds.TotalShards)
|
||||||
|
.AddConfigServices()
|
||||||
|
.AddConfigMigrators()
|
||||||
|
.AddMemoryCache()
|
||||||
|
// music
|
||||||
|
.AddMusic();
|
||||||
|
// admin
|
||||||
|
#if GLOBAL_NADEKO
|
||||||
|
svcs.AddSingleton<ILogCommandService, DummyLogCommandService>();
|
||||||
|
#else
|
||||||
|
svcs.AddSingleton<ILogCommandService, LogCommandService>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
svcs.AddHttpClient();
|
||||||
|
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>()
|
||||||
{
|
.AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
|
||||||
svcs.AddSingleton<RemoteGrpcCoordinator>()
|
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
|
||||||
.AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
|
}
|
||||||
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
|
|
||||||
}
|
|
||||||
|
|
||||||
svcs.AddSingleton<RedisLocalDataCache>()
|
svcs.AddSingleton<RedisLocalDataCache>()
|
||||||
.AddSingleton<ILocalDataCache>(x => x.GetRequiredService<RedisLocalDataCache>())
|
.AddSingleton<ILocalDataCache>(x => x.GetRequiredService<RedisLocalDataCache>())
|
||||||
.AddSingleton<RedisImagesCache>()
|
.AddSingleton<RedisImagesCache>()
|
||||||
.AddSingleton<IImageCache>(x => x.GetRequiredService<RedisImagesCache>())
|
.AddSingleton<IImageCache>(x => x.GetRequiredService<RedisImagesCache>())
|
||||||
.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
|
// services
|
||||||
.AssignableToAny(
|
typeof(INService),
|
||||||
// services
|
|
||||||
typeof(INService),
|
// behaviours
|
||||||
|
typeof(IEarlyBehavior),
|
||||||
// behaviours
|
typeof(ILateBlocker),
|
||||||
typeof(IEarlyBehavior),
|
typeof(IInputTransformer),
|
||||||
typeof(ILateBlocker),
|
typeof(ILateExecutor))
|
||||||
typeof(IInputTransformer),
|
|
||||||
typeof(ILateExecutor))
|
|
||||||
#if GLOBAL_NADEKO
|
#if GLOBAL_NADEKO
|
||||||
.WithoutAttribute<NoPublicBotAttribute>()
|
.WithoutAttribute<NoPublicBotAttribute>()
|
||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
.AsSelfWithInterfaces()
|
.AsSelfWithInterfaces()
|
||||||
.WithSingletonLifetime()
|
.WithSingletonLifetime());
|
||||||
);
|
|
||||||
|
|
||||||
//initialize Services
|
//initialize Services
|
||||||
Services = svcs.BuildServiceProvider();
|
Services = svcs.BuildServiceProvider();
|
||||||
var exec = Services.GetRequiredService<IBehaviourExecutor>();
|
var exec = Services.GetRequiredService<IBehaviourExecutor>();
|
||||||
exec.Initialize();
|
exec.Initialize();
|
||||||
|
|
||||||
if (Client.ShardId == 0)
|
if (Client.ShardId == 0)
|
||||||
{
|
ApplyConfigMigrations();
|
||||||
ApplyConfigMigrations();
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = LoadTypeReaders(typeof(Bot).Assembly);
|
|
||||||
|
|
||||||
sw.Stop();
|
_ = LoadTypeReaders(typeof(Bot).Assembly);
|
||||||
Log.Information($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyConfigMigrations()
|
sw.Stop();
|
||||||
{
|
Log.Information( "All services loaded in {ServiceLoadTime:F2}s", sw.Elapsed.TotalSeconds);
|
||||||
// execute all migrators
|
|
||||||
var migrators = Services.GetServices<IConfigMigrator>();
|
|
||||||
foreach (var migrator in migrators)
|
|
||||||
{
|
|
||||||
migrator.EnsureMigrated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<object> LoadTypeReaders(Assembly assembly)
|
|
||||||
{
|
|
||||||
Type[] allTypes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
allTypes = assembly.GetTypes();
|
|
||||||
}
|
|
||||||
catch (ReflectionTypeLoadException ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex.LoaderExceptions[0], "Error getting types");
|
|
||||||
return Enumerable.Empty<object>();
|
|
||||||
}
|
|
||||||
var filteredTypes = allTypes
|
|
||||||
.Where(x => x.IsSubclassOf(typeof(TypeReader))
|
|
||||||
&& x.BaseType.GetGenericArguments().Length > 0
|
|
||||||
&& !x.IsAbstract);
|
|
||||||
|
|
||||||
var toReturn = new List<object>();
|
|
||||||
foreach (var ft in filteredTypes)
|
|
||||||
{
|
|
||||||
var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
|
|
||||||
var baseType = ft.BaseType;
|
|
||||||
var typeArgs = baseType.GetGenericArguments();
|
|
||||||
_commandService.AddTypeReader(typeArgs[0], x);
|
|
||||||
toReturn.Add(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoginAsync(string token)
|
|
||||||
{
|
|
||||||
var clientReady = new TaskCompletionSource<bool>();
|
|
||||||
|
|
||||||
Task SetClientReady()
|
|
||||||
{
|
|
||||||
var _ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
clientReady.TrySetResult(true);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var chan in (await Client.GetDMChannelsAsync().ConfigureAwait(false)))
|
|
||||||
{
|
|
||||||
await chan.CloseAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
//connect
|
|
||||||
Log.Information("Shard {ShardId} logging in ...", Client.ShardId);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
|
|
||||||
await Client.StartAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (HttpException ex)
|
|
||||||
{
|
|
||||||
LoginErrorHandler.Handle(ex);
|
|
||||||
Helpers.ReadErrorAndExit(3);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LoginErrorHandler.Handle(ex);
|
|
||||||
Helpers.ReadErrorAndExit(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
Client.Ready += SetClientReady;
|
|
||||||
await clientReady.Task.ConfigureAwait(false);
|
|
||||||
Client.Ready -= SetClientReady;
|
|
||||||
|
|
||||||
Client.JoinedGuild += Client_JoinedGuild;
|
|
||||||
Client.LeftGuild += Client_LeftGuild;
|
|
||||||
|
|
||||||
Log.Information("Shard {0} logged in.", Client.ShardId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Client_LeftGuild(SocketGuild arg)
|
|
||||||
{
|
|
||||||
Log.Information("Left server: {0} [{1}]", arg?.Name, arg?.Id);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Client_JoinedGuild(SocketGuild arg)
|
|
||||||
{
|
|
||||||
Log.Information($"Joined server: {0} [{1}]", arg.Name, arg.Id);
|
|
||||||
var _ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
GuildConfig gc;
|
|
||||||
using (var uow = _db.GetDbContext())
|
|
||||||
{
|
|
||||||
gc = uow.GuildConfigsForId(arg.Id);
|
|
||||||
}
|
|
||||||
await JoinedGuild.Invoke(gc).ConfigureAwait(false);
|
|
||||||
});
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RunAsync()
|
|
||||||
{
|
|
||||||
var sw = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
await LoginAsync(_creds.Token).ConfigureAwait(false);
|
|
||||||
|
|
||||||
Mention = Client.CurrentUser.Mention;
|
|
||||||
Log.Information("Shard {ShardId} loading services...", Client.ShardId);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AddServices();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Error adding services");
|
|
||||||
Helpers.ReadErrorAndExit(9);
|
|
||||||
}
|
|
||||||
|
|
||||||
sw.Stop();
|
|
||||||
Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, sw.Elapsed.TotalSeconds);
|
|
||||||
var commandHandler = Services.GetRequiredService<CommandHandler>();
|
|
||||||
|
|
||||||
// start handling messages received in commandhandler
|
|
||||||
await commandHandler.StartHandling().ConfigureAwait(false);
|
|
||||||
|
|
||||||
await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
|
||||||
|
|
||||||
IsReady = true;
|
|
||||||
_ = Task.Run(ExecuteReadySubscriptions);
|
|
||||||
Log.Information("Shard {ShardId} ready", Client.ShardId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task ExecuteReadySubscriptions()
|
|
||||||
{
|
|
||||||
var readyExecutors = Services.GetServices<IReadyExecutor>();
|
|
||||||
var tasks = readyExecutors.Select(async toExec =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await toExec.OnReadyAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex,
|
|
||||||
"Failed running OnReadyAsync method on {Type} type: {Message}",
|
|
||||||
toExec.GetType().Name,
|
|
||||||
ex.Message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.WhenAll(tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Client_Log(LogMessage arg)
|
|
||||||
{
|
|
||||||
if (arg.Exception != null)
|
|
||||||
Log.Warning(arg.Exception, arg.Source + " | " + arg.Message);
|
|
||||||
else
|
|
||||||
Log.Warning(arg.Source + " | " + arg.Message);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RunAndBlockAsync()
|
|
||||||
{
|
|
||||||
await RunAsync().ConfigureAwait(false);
|
|
||||||
await Task.Delay(-1).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void ApplyConfigMigrations()
|
||||||
|
{
|
||||||
|
// execute all migrators
|
||||||
|
var migrators = Services.GetServices<IConfigMigrator>();
|
||||||
|
foreach (var migrator in migrators)
|
||||||
|
migrator.EnsureMigrated();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<object> LoadTypeReaders(Assembly assembly)
|
||||||
|
{
|
||||||
|
Type[] allTypes;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
allTypes = assembly.GetTypes();
|
||||||
|
}
|
||||||
|
catch (ReflectionTypeLoadException ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex.LoaderExceptions[0], "Error getting types");
|
||||||
|
return Enumerable.Empty<object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var filteredTypes = allTypes.Where(x => x.IsSubclassOf(typeof(TypeReader))
|
||||||
|
&& x.BaseType?.GetGenericArguments().Length > 0
|
||||||
|
&& !x.IsAbstract);
|
||||||
|
|
||||||
|
var toReturn = new List<object>();
|
||||||
|
foreach (var ft in filteredTypes)
|
||||||
|
{
|
||||||
|
var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
|
||||||
|
var baseType = ft.BaseType;
|
||||||
|
if (baseType is null)
|
||||||
|
continue;
|
||||||
|
var typeArgs = baseType.GetGenericArguments();
|
||||||
|
_commandService.AddTypeReader(typeArgs[0], x);
|
||||||
|
toReturn.Add(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoginAsync(string token)
|
||||||
|
{
|
||||||
|
var clientReady = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
async Task SetClientReady()
|
||||||
|
{
|
||||||
|
clientReady.TrySetResult(true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var chan in await Client.GetDMChannelsAsync())
|
||||||
|
await chan.CloseAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//connect
|
||||||
|
Log.Information("Shard {ShardId} logging in ...", Client.ShardId);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Client.Ready += SetClientReady;
|
||||||
|
|
||||||
|
await Client.LoginAsync(TokenType.Bot, token);
|
||||||
|
await Client.StartAsync();
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
LoginErrorHandler.Handle(ex);
|
||||||
|
Helpers.ReadErrorAndExit(3);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoginErrorHandler.Handle(ex);
|
||||||
|
Helpers.ReadErrorAndExit(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
await clientReady.Task.ConfigureAwait(false);
|
||||||
|
Client.Ready -= SetClientReady;
|
||||||
|
|
||||||
|
Client.JoinedGuild += Client_JoinedGuild;
|
||||||
|
Client.LeftGuild += Client_LeftGuild;
|
||||||
|
|
||||||
|
Log.Information("Shard {ShardId} logged in", Client.ShardId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Client_LeftGuild(SocketGuild arg)
|
||||||
|
{
|
||||||
|
Log.Information("Left server: {GuildName} [{GuildId}]", arg?.Name, arg?.Id);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Client_JoinedGuild(SocketGuild arg)
|
||||||
|
{
|
||||||
|
Log.Information("Joined server: {GuildName} [{GuildId}]", arg.Name, arg.Id);
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
GuildConfig gc;
|
||||||
|
await using (var uow = _db.GetDbContext())
|
||||||
|
{
|
||||||
|
gc = uow.GuildConfigsForId(arg.Id, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
await JoinedGuild.Invoke(gc);
|
||||||
|
});
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunAsync()
|
||||||
|
{
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
await LoginAsync(_creds.Token);
|
||||||
|
|
||||||
|
Mention = Client.CurrentUser.Mention;
|
||||||
|
Log.Information("Shard {ShardId} loading services...", Client.ShardId);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AddServices();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Error adding services");
|
||||||
|
Helpers.ReadErrorAndExit(9);
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, sw.Elapsed.TotalSeconds);
|
||||||
|
var commandHandler = Services.GetRequiredService<CommandHandler>();
|
||||||
|
|
||||||
|
// start handling messages received in commandhandler
|
||||||
|
await commandHandler.StartHandling();
|
||||||
|
|
||||||
|
await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
||||||
|
// await _interactionService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
||||||
|
IsReady = true;
|
||||||
|
_ = Task.Run(ExecuteReadySubscriptions);
|
||||||
|
Log.Information("Shard {ShardId} ready", Client.ShardId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ExecuteReadySubscriptions()
|
||||||
|
{
|
||||||
|
var readyExecutors = Services.GetServices<IReadyExecutor>();
|
||||||
|
var tasks = readyExecutors.Select(async toExec =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await toExec.OnReadyAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex,
|
||||||
|
"Failed running OnReadyAsync method on {Type} type: {Message}",
|
||||||
|
toExec.GetType().Name,
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tasks.WhenAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Client_Log(LogMessage arg)
|
||||||
|
{
|
||||||
|
if (arg.Message?.Contains("unknown dispatch", StringComparison.InvariantCultureIgnoreCase) ?? false)
|
||||||
|
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 all 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
|
||||||
|
Log.Warning("{ErrorSource} | {ErrorMessage}", arg.Source, arg.Message);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunAndBlockAsync()
|
||||||
|
{
|
||||||
|
await RunAsync();
|
||||||
|
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 AliasesAttribute([CallerMemberName] string memberName = "")
|
||||||
public sealed class AliasesAttribute : AliasAttribute
|
: base(CommandNameLoadHelper.GetAliasesFor(memberName))
|
||||||
{
|
{
|
||||||
public AliasesAttribute([CallerMemberName] string 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 readonly Lazy<Dictionary<string, string[]>> _lazyCommandAliases
|
||||||
|
= new(() => LoadAliases());
|
||||||
|
|
||||||
|
public static Dictionary<string, string[]> LoadAliases(string aliasesFilePath = "data/aliases.yml")
|
||||||
{
|
{
|
||||||
|
var text = File.ReadAllText(aliasesFilePath);
|
||||||
private static YamlDotNet.Serialization.IDeserializer _deserializer
|
return _deserializer.Deserialize<Dictionary<string, string[]>>(text);
|
||||||
= new YamlDotNet.Serialization.Deserializer();
|
}
|
||||||
|
|
||||||
public static Lazy<Dictionary<string, string[]>> LazyCommandAliases
|
|
||||||
= new Lazy<Dictionary<string, string[]>>(() => LoadCommandNames());
|
|
||||||
public static Dictionary<string, string[]> LoadCommandNames(string aliasesFilePath = "data/aliases.yml")
|
|
||||||
{
|
|
||||||
var text = File.ReadAllText(aliasesFilePath);
|
|
||||||
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 string MethodName { get; }
|
||||||
public sealed class NadekoCommandAttribute : CommandAttribute
|
|
||||||
{
|
|
||||||
public NadekoCommandAttribute([CallerMemberName] string memberName="")
|
|
||||||
: base(CommandNameLoadHelper.GetCommandNameFor(memberName))
|
|
||||||
{
|
|
||||||
this.MethodName = memberName.ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Type OptionType { get; set; }
|
||||||
public sealed class NadekoOptionsAttribute : Attribute
|
|
||||||
{
|
|
||||||
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<IBotCredsProvider>().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,38 +1,36 @@
|
|||||||
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 int Seconds { get; }
|
||||||
public sealed class RatelimitAttribute : PreconditionAttribute
|
|
||||||
|
public RatelimitAttribute(int seconds)
|
||||||
{
|
{
|
||||||
public int Seconds { get; }
|
if (seconds <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(seconds));
|
||||||
|
|
||||||
public RatelimitAttribute(int seconds)
|
Seconds = seconds;
|
||||||
{
|
|
||||||
if (seconds <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(seconds));
|
|
||||||
|
|
||||||
Seconds = seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
|
||||||
{
|
|
||||||
if (Seconds == 0)
|
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
|
||||||
|
|
||||||
var cache = services.GetRequiredService<IDataCache>();
|
|
||||||
var rem = cache.TryAddRatelimit(context.User.Id, command.Name, Seconds);
|
|
||||||
|
|
||||||
if(rem is null)
|
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
|
||||||
|
|
||||||
var msgContent = $"You can use this command again in {rem.Value.TotalSeconds:F1}s.";
|
|
||||||
|
|
||||||
return Task.FromResult(PreconditionResult.FromError(msgContent));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||||
|
ICommandContext context,
|
||||||
|
CommandInfo command,
|
||||||
|
IServiceProvider services)
|
||||||
|
{
|
||||||
|
if (Seconds == 0)
|
||||||
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
|
|
||||||
|
var cache = services.GetRequiredService<IDataCache>();
|
||||||
|
var rem = cache.TryAddRatelimit(context.User.Id, command.Name, Seconds);
|
||||||
|
|
||||||
|
if (rem is null)
|
||||||
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
|
|
||||||
|
var msgContent = $"You can use this command again in {rem.Value.TotalSeconds:F1}s.";
|
||||||
|
|
||||||
|
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)]
|
||||||
|
public class UserPermAttribute : RequireUserPermissionAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
public UserPermAttribute(GuildPerm permission)
|
||||||
public class UserPermAttribute : PreconditionAttribute
|
: base(permission)
|
||||||
{
|
{
|
||||||
public RequireUserPermissionAttribute UserPermissionAttribute { get; }
|
|
||||||
|
|
||||||
public UserPermAttribute(GuildPerm permission)
|
|
||||||
{
|
|
||||||
UserPermissionAttribute = new RequireUserPermissionAttribute((GuildPermission)permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserPermAttribute(ChannelPerm permission)
|
|
||||||
{
|
|
||||||
UserPermissionAttribute = new RequireUserPermissionAttribute((ChannelPermission)permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
|
||||||
{
|
|
||||||
var permService = services.GetRequiredService<DiscordPermOverrideService>();
|
|
||||||
if (permService.TryGetOverrides(context.Guild?.Id ?? 0, command.Name.ToUpperInvariant(), out var _))
|
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
|
||||||
|
|
||||||
return UserPermissionAttribute.CheckPermissionsAsync(context, command, services);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public UserPermAttribute(ChannelPerm permission)
|
||||||
|
: base(permission)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||||
|
ICommandContext context,
|
||||||
|
CommandInfo command,
|
||||||
|
IServiceProvider services)
|
||||||
|
{
|
||||||
|
var permService = services.GetRequiredService<DiscordPermOverrideService>();
|
||||||
|
if (permService.TryGetOverrides(context.Guild?.Id ?? 0, command.Name.ToUpperInvariant(), out _))
|
||||||
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
|
|
||||||
|
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 Description { get; }
|
||||||
public string[] Usages { 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;
|
||||||
)
|
Description = description;
|
||||||
{
|
|
||||||
Usages = usages;
|
|
||||||
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,141 +1,145 @@
|
|||||||
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 int Count
|
||||||
|
=> Source.Count;
|
||||||
|
|
||||||
|
public bool IsReadOnly
|
||||||
|
=> false;
|
||||||
|
|
||||||
|
public virtual T this[int index]
|
||||||
{
|
{
|
||||||
public List<T> Source { get; }
|
get => Source[index];
|
||||||
private readonly object _locker = new object();
|
set
|
||||||
|
|
||||||
public int Count => Source.Count;
|
|
||||||
public bool IsReadOnly => false;
|
|
||||||
public int IndexOf(T item) => item.Index;
|
|
||||||
|
|
||||||
public IndexedCollection()
|
|
||||||
{
|
|
||||||
Source = new List<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexedCollection(IEnumerable<T> source)
|
|
||||||
{
|
{
|
||||||
lock (_locker)
|
lock (_locker)
|
||||||
{
|
{
|
||||||
Source = source.OrderBy(x => x.Index).ToList();
|
value.Index = index;
|
||||||
UpdateIndexes();
|
Source[index] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateIndexes()
|
private readonly object _locker = new();
|
||||||
|
|
||||||
|
public IndexedCollection()
|
||||||
|
=> Source = new();
|
||||||
|
|
||||||
|
public IndexedCollection(IEnumerable<T> source)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
{
|
{
|
||||||
lock (_locker)
|
Source = source.OrderBy(x => x.Index).ToList();
|
||||||
|
UpdateIndexes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IndexOf(T item)
|
||||||
|
=> item?.Index ?? -1;
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
=> Source.GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
=> Source.GetEnumerator();
|
||||||
|
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
item.Index = Source.Count;
|
||||||
|
Source.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Clear()
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
Source.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(T item)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
return Source.Contains(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(T[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
Source.CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool Remove(T item)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
if (Source.Remove(item))
|
||||||
{
|
{
|
||||||
for (var 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator List<T>(IndexedCollection<T> x) =>
|
return false;
|
||||||
x.Source;
|
}
|
||||||
|
|
||||||
public List<T> ToList() => Source.ToList();
|
public virtual void Insert(int index, T item)
|
||||||
|
{
|
||||||
public IEnumerator<T> GetEnumerator() =>
|
lock (_locker)
|
||||||
Source.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() =>
|
|
||||||
Source.GetEnumerator();
|
|
||||||
|
|
||||||
public void Add(T item)
|
|
||||||
{
|
{
|
||||||
lock (_locker)
|
Source.Insert(index, item);
|
||||||
{
|
for (var i = index; i < Source.Count; i++)
|
||||||
item.Index = Source.Count;
|
Source[i].Index = i;
|
||||||
Source.Add(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void Clear()
|
public virtual void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
{
|
{
|
||||||
lock (_locker)
|
Source.RemoveAt(index);
|
||||||
{
|
for (var i = index; i < Source.Count; i++)
|
||||||
Source.Clear();
|
Source[i].Index = i;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool Contains(T item)
|
public void UpdateIndexes()
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
{
|
{
|
||||||
lock (_locker)
|
for (var i = 0; i < Source.Count; i++)
|
||||||
{
|
{
|
||||||
return Source.Contains(item);
|
if (Source[i].Index != i)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopyTo(T[] array, int arrayIndex)
|
|
||||||
{
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
Source.CopyTo(array, arrayIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool Remove(T item)
|
|
||||||
{
|
|
||||||
bool removed;
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
if (removed = Source.Remove(item))
|
|
||||||
{
|
|
||||||
for (int i = 0; i < Source.Count; i++)
|
|
||||||
{
|
|
||||||
if (Source[i].Index != i)
|
|
||||||
Source[i].Index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Insert(int index, T item)
|
|
||||||
{
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
Source.Insert(index, item);
|
|
||||||
for (int i = index; i < Source.Count; i++)
|
|
||||||
{
|
|
||||||
Source[i].Index = i;
|
Source[i].Index = i;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void RemoveAt(int index)
|
|
||||||
{
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
Source.RemoveAt(index);
|
|
||||||
for (int i = index; i < Source.Count; i++)
|
|
||||||
{
|
|
||||||
Source[i].Index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual T this[int index]
|
|
||||||
{
|
|
||||||
get { return Source[index]; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
value.Index = index;
|
|
||||||
Source[index] = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static implicit operator List<T>(IndexedCollection<T> x)
|
||||||
|
=> x.Source;
|
||||||
|
|
||||||
|
public List<T> ToList()
|
||||||
|
=> Source.ToList();
|
||||||
|
}
|
@@ -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 Desc { get; set; }
|
||||||
public string Cmd { get; set; }
|
public string[] Usage { get; set; }
|
||||||
public string Desc { get; set; }
|
}
|
||||||
public string[] Usage { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,69 +1,71 @@
|
|||||||
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>
|
|
||||||
{
|
|
||||||
[Comment(@"DO NOT CHANGE")]
|
|
||||||
public int Version { get; set; } = 2;
|
|
||||||
|
|
||||||
[Comment(@"Most commands, when executed, have a small colored line
|
[Cloneable]
|
||||||
|
public sealed partial class BotConfig : ICloneable<BotConfig>
|
||||||
|
{
|
||||||
|
[Comment(@"DO NOT CHANGE")]
|
||||||
|
public int Version { get; set; } = 2;
|
||||||
|
|
||||||
|
[Comment(@"Most commands, when executed, have a small colored line
|
||||||
next to the response. The color depends whether the command
|
next to the response. The color depends whether the command
|
||||||
is completed, errored or in progress (pending)
|
is completed, errored or in progress (pending)
|
||||||
Color settings below are for the color of those lines.
|
Color settings below are for the color of those lines.
|
||||||
To get color's hex, you can go here https://htmlcolorcodes.com/
|
To get color's hex, you can go here https://htmlcolorcodes.com/
|
||||||
and copy the hex code fo your selected color (marked as #)")]
|
and copy the hex code fo your selected color (marked as #)")]
|
||||||
public ColorConfig Color { get; set; }
|
public ColorConfig Color { get; set; }
|
||||||
|
|
||||||
[Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
|
[Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
|
||||||
public CultureInfo DefaultLocale { get; set; }
|
public CultureInfo DefaultLocale { get; set; }
|
||||||
|
|
||||||
[Comment(@"Style in which executed commands will show up in the console.
|
[Comment(@"Style in which executed commands will show up in the console.
|
||||||
Allowed values: Simple, Normal, None")]
|
Allowed values: Simple, Normal, None")]
|
||||||
public ConsoleOutputType ConsoleOutputType { get; set; }
|
public ConsoleOutputType ConsoleOutputType { get; set; }
|
||||||
|
|
||||||
// [Comment(@"For what kind of updates will the bot check.
|
// [Comment(@"For what kind of updates will the bot check.
|
||||||
// Allowed values: Release, Commit, None")]
|
// Allowed values: Release, Commit, None")]
|
||||||
// public UpdateCheckType CheckForUpdates { get; set; }
|
// public UpdateCheckType CheckForUpdates { get; set; }
|
||||||
|
|
||||||
// [Comment(@"How often will the bot check for updates, in hours")]
|
// [Comment(@"How often will the bot check for updates, in hours")]
|
||||||
// public int CheckUpdateInterval { get; set; }
|
// public int CheckUpdateInterval { get; set; }
|
||||||
|
|
||||||
[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; }
|
||||||
|
|
||||||
[Comment(@"When a user DMs the bot with a message which is not a command
|
[Comment(@"When a user DMs the bot with a message which is not a command
|
||||||
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.
|
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.
|
||||||
Supports embeds. How it looks: https://puu.sh/B0BLV.png")]
|
Supports embeds. How it looks: https://puu.sh/B0BLV.png")]
|
||||||
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
||||||
public string DmHelpText { get; set; }
|
public string DmHelpText { get; set; }
|
||||||
|
|
||||||
[Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
|
[Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
|
||||||
Case insensitive.
|
Case insensitive.
|
||||||
Leave empty to reply with DmHelpText to every DM.")]
|
Leave empty to reply with DmHelpText to every DM.")]
|
||||||
public List<string> DmHelpTextKeywords { get; set; }
|
public List<string> DmHelpTextKeywords { get; set; }
|
||||||
|
|
||||||
[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")]
|
|
||||||
public BlockedConfig Blocked { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"Which string will be used to recognize the commands")]
|
[Comment(@"List of modules and commands completely blocked on the bot")]
|
||||||
public string Prefix { get; set; }
|
public BlockedConfig Blocked { get; set; }
|
||||||
|
|
||||||
[Comment(@"Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
|
[Comment(@"Which string will be used to recognize the commands")]
|
||||||
|
public string Prefix { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
|
||||||
1st user who joins will get greeted immediately
|
1st user who joins will get greeted immediately
|
||||||
If more users join within the next 5 seconds, they will be greeted in groups of 5.
|
If more users join within the next 5 seconds, they will be greeted in groups of 5.
|
||||||
This will cause %user.mention% and other placeholders to be replaced with multiple users.
|
This will cause %user.mention% and other placeholders to be replaced with multiple users.
|
||||||
@@ -72,36 +74,23 @@ it will become invalid, as it will resolve to a list of avatars of grouped users
|
|||||||
note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
|
note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
|
||||||
servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited,
|
servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited,
|
||||||
and (slightly) reduce the greet spam in those servers.")]
|
and (slightly) reduce the greet spam in those servers.")]
|
||||||
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.
|
public BotConfig()
|
||||||
// For example, if your prefix is ! you will run a command called 'cash' by typing either
|
{
|
||||||
// '!cash @Someone' if your prefixIsSuffix: false or
|
var color = new ColorConfig();
|
||||||
// 'cash @Someone!' if your prefixIsSuffix: true")]
|
Color = color;
|
||||||
// public bool PrefixIsSuffix { get; set; }
|
DefaultLocale = new("en-US");
|
||||||
|
ConsoleOutputType = ConsoleOutputType.Normal;
|
||||||
// public string Prefixed(string text) => PrefixIsSuffix
|
ForwardMessages = false;
|
||||||
// ? text + Prefix
|
ForwardToAllOwners = false;
|
||||||
// : Prefix + text;
|
DmHelpText = @"{""description"": ""Type `%prefix%h` for help.""}";
|
||||||
|
HelpText = @"{
|
||||||
public string Prefixed(string text)
|
|
||||||
=> Prefix + text;
|
|
||||||
|
|
||||||
public BotConfig()
|
|
||||||
{
|
|
||||||
var color = new ColorConfig();
|
|
||||||
Color = color;
|
|
||||||
DefaultLocale = new CultureInfo("en-US");
|
|
||||||
ConsoleOutputType = ConsoleOutputType.Normal;
|
|
||||||
ForwardMessages = false;
|
|
||||||
ForwardToAllOwners = false;
|
|
||||||
DmHelpText = @"{""description"": ""Type `%prefix%h` for help.""}";
|
|
||||||
HelpText = @"{
|
|
||||||
""title"": ""To invite me to your server, use this link"",
|
""title"": ""To invite me to your server, use this link"",
|
||||||
""description"": ""https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=66186303"",
|
""description"": ""https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=66186303"",
|
||||||
""color"": 53380,
|
""color"": 53380,
|
||||||
@@ -126,59 +115,71 @@ See RotatingStatuses submodule in Administration.")]
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}";
|
}";
|
||||||
var blocked = new BlockedConfig();
|
var blocked = new BlockedConfig();
|
||||||
Blocked = blocked;
|
Blocked = blocked;
|
||||||
Prefix = ".";
|
Prefix = ".";
|
||||||
RotateStatuses = false;
|
RotateStatuses = false;
|
||||||
GroupGreets = false;
|
GroupGreets = false;
|
||||||
DmHelpTextKeywords = new List<string>()
|
DmHelpTextKeywords = new()
|
||||||
{
|
|
||||||
"help",
|
|
||||||
"commands",
|
|
||||||
"cmds",
|
|
||||||
"module",
|
|
||||||
"can you do"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cloneable]
|
|
||||||
public sealed partial class BlockedConfig
|
|
||||||
{
|
|
||||||
public HashSet<string> Commands { get; set; }
|
|
||||||
public HashSet<string> Modules { get; set; }
|
|
||||||
|
|
||||||
public BlockedConfig()
|
|
||||||
{
|
{
|
||||||
Modules = new HashSet<string>();
|
"help",
|
||||||
Commands = new HashSet<string>();
|
"commands",
|
||||||
}
|
"cmds",
|
||||||
|
"module",
|
||||||
|
"can you do"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
// [Comment(@"Whether the prefix will be a suffix, or prefix.
|
||||||
public partial class ColorConfig
|
// For example, if your prefix is ! you will run a command called 'cash' by typing either
|
||||||
{
|
// '!cash @Someone' if your prefixIsSuffix: false or
|
||||||
[Comment(@"Color used for embed responses when command successfully executes")]
|
// 'cash @Someone!' if your prefixIsSuffix: true")]
|
||||||
public Rgba32 Ok { get; set; }
|
// public bool PrefixIsSuffix { get; set; }
|
||||||
|
|
||||||
[Comment(@"Color used for embed responses when command has an error")]
|
|
||||||
public Rgba32 Error { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"Color used for embed responses while command is doing work or is in progress")]
|
|
||||||
public Rgba32 Pending { get; set; }
|
|
||||||
|
|
||||||
public ColorConfig()
|
// public string Prefixed(string text) => PrefixIsSuffix
|
||||||
{
|
// ? text + Prefix
|
||||||
Ok = Rgba32.ParseHex("00e584");
|
// : Prefix + text;
|
||||||
Error = Rgba32.ParseHex("ee281f");
|
|
||||||
Pending = Rgba32.ParseHex("faa61a");
|
public string Prefixed(string text)
|
||||||
}
|
=> Prefix + text;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ConsoleOutputType
|
[Cloneable]
|
||||||
|
public sealed partial class BlockedConfig
|
||||||
|
{
|
||||||
|
public HashSet<string> Commands { get; set; }
|
||||||
|
public HashSet<string> Modules { get; set; }
|
||||||
|
|
||||||
|
public BlockedConfig()
|
||||||
{
|
{
|
||||||
Normal = 0,
|
Modules = new();
|
||||||
Simple = 1,
|
Commands = new();
|
||||||
None = 2,
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cloneable]
|
||||||
|
public partial class ColorConfig
|
||||||
|
{
|
||||||
|
[Comment(@"Color used for embed responses when command successfully executes")]
|
||||||
|
public Rgba32 Ok { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Color used for embed responses when command has an error")]
|
||||||
|
public Rgba32 Error { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Color used for embed responses while command is doing work or is in progress")]
|
||||||
|
public Rgba32 Pending { get; set; }
|
||||||
|
|
||||||
|
public ColorConfig()
|
||||||
|
{
|
||||||
|
Ok = Rgba32.ParseHex("00e584");
|
||||||
|
Error = Rgba32.ParseHex("ee281f");
|
||||||
|
Pending = Rgba32.ParseHex("faa61a");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ConsoleOutputType
|
||||||
|
{
|
||||||
|
Normal = 0,
|
||||||
|
Simple = 1,
|
||||||
|
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>
|
/// <summary>
|
||||||
/// Base interface for available config serializers
|
/// Serialize the object to string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IConfigSeria
|
public string Serialize<T>(T obj)
|
||||||
{
|
where T : notnull;
|
||||||
/// <summary>
|
|
||||||
/// Serialize the object to string
|
/// <summary>
|
||||||
/// </summary>
|
/// Deserialize string data into an object of the specified type
|
||||||
public string Serialize<T>(T obj);
|
/// </summary>
|
||||||
|
public T Deserialize<T>(string data);
|
||||||
/// <summary>
|
|
||||||
/// Deserialize string data into an object of the specified type
|
|
||||||
/// </summary>
|
|
||||||
public T Deserialize<T>(string data);
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,97 +1,90 @@
|
|||||||
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
|
[Comment(@"DO NOT CHANGE")]
|
||||||
{
|
public int Version { get; set; }
|
||||||
public Creds()
|
|
||||||
{
|
|
||||||
Version = 1;
|
|
||||||
Token = string.Empty;
|
|
||||||
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";
|
[Comment(@"Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/")]
|
||||||
|
public string Token { get; set; }
|
||||||
|
|
||||||
RestartCommand = new()
|
[Comment(@"List of Ids of the users who have bot owner permissions
|
||||||
{
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[Comment(@"DO NOT CHANGE")]
|
|
||||||
public int Version { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/")]
|
|
||||||
public string Token { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"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**")]
|
||||||
public ICollection<ulong> OwnerIds { get; set; }
|
public ICollection<ulong> OwnerIds { get; set; }
|
||||||
|
|
||||||
[Comment(@"The number of shards that the bot will running on.
|
[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.
|
||||||
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; }
|
||||||
|
|
||||||
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
||||||
public VotesSettings Votes { get; set; }
|
public VotesSettings Votes { get; set; }
|
||||||
|
|
||||||
[Comment(@"Patreon auto reward system settings.
|
[Comment(@"Patreon auto reward system settings.
|
||||||
go to https://www.patreon.com/portal -> my clients -> create client")]
|
go to https://www.patreon.com/portal -> my clients -> create client")]
|
||||||
public PatreonSettings Patreon { get; set; }
|
public PatreonSettings Patreon { get; set; }
|
||||||
|
|
||||||
[Comment(@"Api key for sending stats to DiscordBotList.")]
|
|
||||||
public string BotListToken { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"Official cleverbot api key.")]
|
|
||||||
public string CleverbotApiKey { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"Redis connection string. Don't change if you don't know what you're doing.")]
|
|
||||||
public string RedisOptions { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")]
|
|
||||||
public DbOptions Db { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"Address and port of the coordinator endpoint. Leave empty for default.
|
[Comment(@"Api key for sending stats to DiscordBotList.")]
|
||||||
|
public string BotListToken { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Official cleverbot api key.")]
|
||||||
|
public string CleverbotApiKey { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Redis connection string. Don't change if you don't know what you're doing.")]
|
||||||
|
public string RedisOptions { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")]
|
||||||
|
public DbOptions Db { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Address and port of the coordinator endpoint. Leave empty for default.
|
||||||
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; }
|
||||||
|
|
||||||
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
|
||||||
public string RapidApiKey { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
|
[Comment(
|
||||||
|
@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
||||||
|
public string RapidApiKey { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
|
||||||
Used only for .time command.")]
|
Used only for .time command.")]
|
||||||
public string LocationIqApiKey { get; set; }
|
public string LocationIqApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
|
[Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
|
||||||
Used only for .time command")]
|
Used only for .time command")]
|
||||||
public string TimezoneDbApiKey { get; set; }
|
public string TimezoneDbApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
[Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
||||||
Used for cryptocurrency related commands.")]
|
Used for cryptocurrency related commands.")]
|
||||||
public string CoinmarketcapApiKey { get; set; }
|
public string CoinmarketcapApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
|
// [Comment(@"https://polygon.io/dashboard/api-keys api key. Free plan allows for 5 queries per minute.
|
||||||
public string OsuApiKey { get; set; }
|
// Used for stocks related commands.")]
|
||||||
|
// public string PolygonIoApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Command and args which will be used to restart the bot.
|
[Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
|
||||||
|
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.
|
||||||
Only used if bot is executed directly (NOT through the coordinator)
|
Only used if bot is executed directly (NOT through the coordinator)
|
||||||
placeholders:
|
placeholders:
|
||||||
{0} -> shard id
|
{0} -> shard id
|
||||||
@@ -102,118 +95,104 @@ Linux default
|
|||||||
Windows default
|
Windows default
|
||||||
cmd: NadekoBot.exe
|
cmd: NadekoBot.exe
|
||||||
args: {0}")]
|
args: {0}")]
|
||||||
public RestartConfig RestartCommand { get; set; }
|
public RestartConfig RestartCommand { get; set; }
|
||||||
|
|
||||||
|
public Creds()
|
||||||
public class DbOptions
|
{
|
||||||
|
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()
|
||||||
{
|
{
|
||||||
[Comment(@"Database type. Only sqlite supported atm")]
|
Type = "sqlite",
|
||||||
public string Type { get; set; }
|
ConnectionString = "Data Source=data/NadekoBot.db"
|
||||||
[Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
|
};
|
||||||
public string ConnectionString { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo fixup patreon
|
CoordinatorUrl = "http://localhost:3442";
|
||||||
public sealed record PatreonSettings
|
|
||||||
|
RestartCommand = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class DbOptions
|
||||||
|
{
|
||||||
|
[Comment(@"Database type. Only sqlite supported atm")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
|
||||||
|
public string ConnectionString { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record PatreonSettings
|
||||||
|
{
|
||||||
|
public string ClientId { get; set; }
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
public string RefreshToken { 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)")]
|
||||||
|
public string CampaignId { get; set; }
|
||||||
|
|
||||||
|
public PatreonSettings(
|
||||||
|
string accessToken,
|
||||||
|
string refreshToken,
|
||||||
|
string clientSecret,
|
||||||
|
string campaignId)
|
||||||
{
|
{
|
||||||
public string ClientId { get; set; }
|
AccessToken = accessToken;
|
||||||
public string AccessToken { get; set; }
|
RefreshToken = refreshToken;
|
||||||
public string RefreshToken { get; set; }
|
ClientSecret = clientSecret;
|
||||||
public string ClientSecret { get; set; }
|
CampaignId = campaignId;
|
||||||
|
|
||||||
[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 PatreonSettings(string accessToken, string refreshToken, string clientSecret, string campaignId)
|
|
||||||
{
|
|
||||||
AccessToken = accessToken;
|
|
||||||
RefreshToken = refreshToken;
|
|
||||||
ClientSecret = clientSecret;
|
|
||||||
CampaignId = campaignId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatreonSettings()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record VotesSettings
|
public PatreonSettings()
|
||||||
{
|
{
|
||||||
[Comment(@"top.gg votes service url
|
|
||||||
This is the url of your instance of the NadekoBot.Votes api
|
|
||||||
Example: https://votes.my.cool.bot.com")]
|
|
||||||
public string TopggServiceUrl { get; set; }
|
|
||||||
|
|
||||||
[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()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public VotesSettings(string topggServiceUrl, string topggKey, string discordsServiceUrl, string discordsKey)
|
|
||||||
{
|
|
||||||
TopggServiceUrl = topggServiceUrl;
|
|
||||||
TopggKey = topggKey;
|
|
||||||
DiscordsServiceUrl = discordsServiceUrl;
|
|
||||||
DiscordsKey = discordsKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Old
|
|
||||||
{
|
|
||||||
public string Token { get; set; } = string.Empty;
|
|
||||||
public ulong[] OwnerIds { get; set; } = new ulong[1];
|
|
||||||
public string LoLApiKey { get; set; } = string.Empty;
|
|
||||||
public string GoogleApiKey { get; set; } = string.Empty;
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public sealed record VotesSettings
|
||||||
|
{
|
||||||
|
[Comment(@"top.gg votes service url
|
||||||
|
This is the url of your instance of the NadekoBot.Votes api
|
||||||
|
Example: https://votes.my.cool.bot.com")]
|
||||||
|
public string TopggServiceUrl { get; set; }
|
||||||
|
|
||||||
|
[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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public VotesSettings(
|
||||||
|
string topggServiceUrl,
|
||||||
|
string topggKey,
|
||||||
|
string discordsServiceUrl,
|
||||||
|
string discordsKey)
|
||||||
|
{
|
||||||
|
TopggServiceUrl = topggServiceUrl;
|
||||||
|
TopggKey = topggKey;
|
||||||
|
DiscordsServiceUrl = discordsServiceUrl;
|
||||||
|
DiscordsKey = discordsKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,46 +1,38 @@
|
|||||||
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);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures all users on the specified guild were downloaded within the last hour.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guild">Guild to check and potentially download users from</param>
|
||||||
|
/// <returns>Task representing download state</returns>
|
||||||
|
public async Task EnsureUsersDownloadedAsync(IGuild guild)
|
||||||
{
|
{
|
||||||
private ConcurrentDictionary<ulong, DateTime> LastDownloads { get; } = new ConcurrentDictionary<ulong, DateTime>();
|
|
||||||
private SemaphoreSlim downloadUsersSemaphore = new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ensures all users on the specified guild were downloaded within the last hour.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="guild">Guild to check and potentially download users from</param>
|
|
||||||
/// <returns>Task representing download state</returns>
|
|
||||||
public async Task EnsureUsersDownloadedAsync(IGuild guild)
|
|
||||||
{
|
|
||||||
#if GLOBAL_NADEKO
|
#if GLOBAL_NADEKO
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
await downloadUsersSemaphore.WaitAsync();
|
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,
|
(_, old) => now - old > TimeSpan.FromHours(1) ? now : old);
|
||||||
(key, 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)
|
||||||
await guild.DownloadUsersAsync();
|
await guild.DownloadUsersAsync();
|
||||||
}
|
}
|
||||||
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,81 +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, int totalShards)
|
|
||||||
=> totalShards <= 1
|
|
||||||
? services
|
|
||||||
.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
|
||||||
.AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
|
|
||||||
.AddSingleton<IBotStrings, BotStrings>()
|
|
||||||
: services.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
|
||||||
.AddSingleton<IBotStringsProvider, RedisBotStringsProvider>()
|
|
||||||
.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,15 +1,13 @@
|
|||||||
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)
|
||||||
{
|
Console.ReadKey();
|
||||||
if (!Console.IsInputRedirected)
|
|
||||||
Console.ReadKey();
|
Environment.Exit(exitCode);
|
||||||
|
|
||||||
Environment.Exit(exitCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,36 +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 GoogleApiKey { get; }
|
||||||
string Token { get; }
|
ICollection<ulong> OwnerIds { get; }
|
||||||
string GoogleApiKey { get; }
|
bool UsePrivilegedIntents { get; }
|
||||||
ICollection<ulong> OwnerIds { get; }
|
string RapidApiKey { get; }
|
||||||
string RapidApiKey { get; }
|
|
||||||
|
|
||||||
Creds.DbOptions Db { get; }
|
Creds.DbOptions Db { get; }
|
||||||
string OsuApiKey { get; }
|
string OsuApiKey { get; }
|
||||||
int TotalShards { get; }
|
int TotalShards { get; }
|
||||||
Creds.PatreonSettings Patreon { get; }
|
Creds.PatreonSettings Patreon { get; }
|
||||||
string CleverbotApiKey { get; }
|
string CleverbotApiKey { get; }
|
||||||
RestartConfig RestartCommand { get; }
|
RestartConfig RestartCommand { get; }
|
||||||
Creds.VotesSettings Votes { get; }
|
Creds.VotesSettings Votes { 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 CoordinatorUrl { get; set; }
|
string TrovoClientId { get; }
|
||||||
}
|
string CoordinatorUrl { get; set; }
|
||||||
|
string TwitchClientId { get; set; }
|
||||||
public class RestartConfig
|
string TwitchClientSecret { get; set; }
|
||||||
{
|
|
||||||
public string Cmd { get; set; }
|
|
||||||
public string Args { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class RestartConfig
|
||||||
|
{
|
||||||
|
public string Cmd { 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,25 +1,23 @@
|
|||||||
using Discord;
|
#nullable disable
|
||||||
|
namespace NadekoBot;
|
||||||
|
|
||||||
namespace NadekoBot
|
public interface IEmbedBuilder
|
||||||
{
|
{
|
||||||
public interface IEmbedBuilder
|
IEmbedBuilder WithDescription(string desc);
|
||||||
{
|
IEmbedBuilder WithTitle(string title);
|
||||||
IEmbedBuilder WithDescription(string desc);
|
IEmbedBuilder AddField(string title, object value, bool isInline = false);
|
||||||
IEmbedBuilder WithTitle(string title);
|
IEmbedBuilder WithFooter(string text, string iconUrl = null);
|
||||||
IEmbedBuilder AddField(string title, object value, bool isInline = false);
|
IEmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null);
|
||||||
IEmbedBuilder WithFooter(string text, string iconUrl = null);
|
IEmbedBuilder WithColor(EmbedColor color);
|
||||||
IEmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null);
|
Embed Build();
|
||||||
IEmbedBuilder WithColor(EmbedColor color);
|
IEmbedBuilder WithUrl(string url);
|
||||||
Embed Build();
|
IEmbedBuilder WithImageUrl(string url);
|
||||||
IEmbedBuilder WithUrl(string url);
|
IEmbedBuilder WithThumbnailUrl(string url);
|
||||||
IEmbedBuilder WithImageUrl(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,50 +1,49 @@
|
|||||||
using System;
|
#nullable disable
|
||||||
using NadekoBot.Common.Yml;
|
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 CoinData Coins { get; set; }
|
||||||
|
public Uri[] Currency { get; set; }
|
||||||
|
public Uri[] Dice { get; set; }
|
||||||
|
public RategirlData Rategirl { get; set; }
|
||||||
|
public XpData Xp { get; set; }
|
||||||
|
|
||||||
|
//new
|
||||||
|
public RipData Rip { get; set; }
|
||||||
|
public SlotData Slots { get; set; }
|
||||||
|
|
||||||
|
public class RipData
|
||||||
{
|
{
|
||||||
[Comment("DO NOT CHANGE")]
|
public Uri Bg { get; set; }
|
||||||
public int Version { get; set; } = 3;
|
public Uri Overlay { get; set; }
|
||||||
|
|
||||||
public CoinData Coins { get; set; }
|
|
||||||
public Uri[] Currency { get; set; }
|
|
||||||
public Uri[] Dice { get; set; }
|
|
||||||
public RategirlData Rategirl { get; set; }
|
|
||||||
public XpData Xp { get; set; }
|
|
||||||
|
|
||||||
//new
|
|
||||||
public RipData Rip { get; set; }
|
|
||||||
public SlotData Slots { get; set; }
|
|
||||||
|
|
||||||
public class RipData
|
|
||||||
{
|
|
||||||
public Uri Bg { get; set; }
|
|
||||||
public Uri Overlay { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SlotData
|
|
||||||
{
|
|
||||||
public Uri[] Emojis { get; set; }
|
|
||||||
public Uri Bg { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CoinData
|
|
||||||
{
|
|
||||||
public Uri[] Heads { get; set; }
|
|
||||||
public Uri[] Tails { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RategirlData
|
|
||||||
{
|
|
||||||
public Uri Matrix { get; set; }
|
|
||||||
public Uri Dot { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class XpData
|
|
||||||
{
|
|
||||||
public Uri Bg { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public class SlotData
|
||||||
|
{
|
||||||
|
public Uri[] Emojis { get; set; }
|
||||||
|
public Uri Bg { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CoinData
|
||||||
|
{
|
||||||
|
public Uri[] Heads { get; set; }
|
||||||
|
public Uri[] Tails { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RategirlData
|
||||||
|
{
|
||||||
|
public Uri Matrix { get; set; }
|
||||||
|
public Uri Dot { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class XpData
|
||||||
|
{
|
||||||
|
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,98 +1,101 @@
|
|||||||
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
|
private readonly int _value;
|
||||||
public readonly struct kwum : IEquatable<kwum>
|
|
||||||
|
public kwum(int num)
|
||||||
|
=> _value = num;
|
||||||
|
|
||||||
|
public kwum(in char c)
|
||||||
{
|
{
|
||||||
private readonly int _value;
|
if (!IsValidChar(c))
|
||||||
private const string ValidCharacters = "23456789abcdefghijkmnpqrstuvwxyz";
|
throw new ArgumentException("Character needs to be a valid kwum character.", nameof(c));
|
||||||
|
|
||||||
public kwum(int num)
|
_value = InternalCharToValue(c);
|
||||||
=> _value = num;
|
}
|
||||||
|
|
||||||
public kwum(in char c)
|
public kwum(in ReadOnlySpan<char> input)
|
||||||
|
{
|
||||||
|
_value = 0;
|
||||||
|
for (var index = 0; index < input.Length; index++)
|
||||||
{
|
{
|
||||||
|
var c = input[index];
|
||||||
if (!IsValidChar(c))
|
if (!IsValidChar(c))
|
||||||
throw new ArgumentException("Character needs to be a valid kwum character.", nameof(c));
|
throw new ArgumentException("All characters need to be a valid kwum characters.", nameof(input));
|
||||||
|
|
||||||
_value = InternalCharToValue(c);
|
_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)
|
|
||||||
=> ValidCharacters.IndexOf(c);
|
|
||||||
|
|
||||||
public kwum(in ReadOnlySpan<char> input)
|
|
||||||
{;
|
|
||||||
_value = 0;
|
|
||||||
for (var index = 0; index < input.Length; index++)
|
|
||||||
{
|
|
||||||
var c = input[index];
|
|
||||||
if (!IsValidChar(c))
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryParse(in ReadOnlySpan<char> input, out kwum value)
|
|
||||||
{
|
|
||||||
value = default;
|
|
||||||
foreach(var c in input)
|
|
||||||
if (!IsValidChar(c))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
value = new kwum(input);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static kwum operator +(kwum left, kwum right)
|
|
||||||
=> new kwum(left._value + right._value);
|
|
||||||
|
|
||||||
public static bool operator ==(kwum left, kwum right)
|
|
||||||
=> left._value == right._value;
|
|
||||||
|
|
||||||
public static bool operator !=(kwum left, kwum right)
|
|
||||||
=> !(left == right);
|
|
||||||
|
|
||||||
public static implicit operator long(kwum kwum)
|
|
||||||
=> kwum._value;
|
|
||||||
|
|
||||||
public static implicit operator int(kwum kwum)
|
|
||||||
=> kwum._value;
|
|
||||||
public static implicit operator kwum(int num)
|
|
||||||
=> new kwum(num);
|
|
||||||
|
|
||||||
public static bool IsValidChar(char c)
|
|
||||||
=> ValidCharacters.Contains(c);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
var count = ValidCharacters.Length;
|
|
||||||
var localValue = _value;
|
|
||||||
var arrSize = (int)Math.Log(localValue, count) + 1;
|
|
||||||
Span<char> chars = new char[arrSize];
|
|
||||||
while (localValue > 0)
|
|
||||||
{
|
|
||||||
localValue = Math.DivRem(localValue, count, out var rem);
|
|
||||||
chars[--arrSize] = ValidCharacters[(int)rem];
|
|
||||||
}
|
|
||||||
|
|
||||||
return new string(chars);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
=> obj is kwum kw && kw == this;
|
|
||||||
|
|
||||||
public bool Equals(kwum other)
|
|
||||||
=> other == this;
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return _value.GetHashCode();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[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)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
foreach (var c in input)
|
||||||
|
{
|
||||||
|
if (!IsValidChar(c))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = new(input);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static kwum operator +(kwum left, kwum right)
|
||||||
|
=> new(left._value + right._value);
|
||||||
|
|
||||||
|
public static bool operator ==(kwum left, kwum right)
|
||||||
|
=> left._value == right._value;
|
||||||
|
|
||||||
|
public static bool operator !=(kwum left, kwum right)
|
||||||
|
=> !(left == right);
|
||||||
|
|
||||||
|
public static implicit operator long(kwum kwum)
|
||||||
|
=> kwum._value;
|
||||||
|
|
||||||
|
public static implicit operator int(kwum kwum)
|
||||||
|
=> kwum._value;
|
||||||
|
|
||||||
|
public static implicit operator kwum(int num)
|
||||||
|
=> new(num);
|
||||||
|
|
||||||
|
public static bool IsValidChar(char c)
|
||||||
|
=> VALID_CHARACTERS.Contains(c);
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var count = VALID_CHARACTERS.Length;
|
||||||
|
var localValue = _value;
|
||||||
|
var arrSize = (int)Math.Log(localValue, count) + 1;
|
||||||
|
Span<char> chars = new char[arrSize];
|
||||||
|
while (localValue > 0)
|
||||||
|
{
|
||||||
|
localValue = Math.DivRem(localValue, count, out var rem);
|
||||||
|
chars[--arrSize] = VALID_CHARACTERS[rem];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
=> obj is kwum kw && kw == this;
|
||||||
|
|
||||||
|
public bool Equals(kwum other)
|
||||||
|
=> other == this;
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
=> _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.")]
|
||||||
{
|
public bool Clean { get; set; }
|
||||||
[Option('c', "clean", Default = false, HelpText = "Only show users who are on the server.")]
|
|
||||||
public bool Clean { get; set; }
|
|
||||||
public void NormalizeOptions()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
public void NormalizeOptions()
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,57 +1,52 @@
|
|||||||
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)]
|
||||||
|
public static void Handle(Exception ex)
|
||||||
|
=> Log.Fatal(ex, "A fatal error has occurred while attempting to connect to Discord");
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Handle(HttpException ex)
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
switch (ex.HttpCode)
|
||||||
public static void Handle(Exception ex)
|
|
||||||
{
|
{
|
||||||
Log.Fatal(ex, "A fatal error has occurred while attempting to connect to Discord");
|
case HttpStatusCode.Unauthorized:
|
||||||
|
Log.Error("Your bot token is wrong.\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");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpStatusCode.BadRequest:
|
||||||
|
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");
|
||||||
|
Log.Error("If you are on Linux, make sure Redis is installed and running");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpStatusCode.RequestTimeout:
|
||||||
|
Log.Error("The request timed out. Make sure you have no external program blocking the bot "
|
||||||
|
+ "from connecting to the internet");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpStatusCode.ServiceUnavailable:
|
||||||
|
case HttpStatusCode.InternalServerError:
|
||||||
|
Log.Error("Discord is having internal issues. Please, try again later");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpStatusCode.TooManyRequests:
|
||||||
|
Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n"
|
||||||
|
+ "Global ratelimits usually last for an hour");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Log.Warning("An error occurred while attempting to connect to Discord");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void Handle(HttpException ex)
|
|
||||||
{
|
|
||||||
switch (ex.HttpCode)
|
|
||||||
{
|
|
||||||
case HttpStatusCode.Unauthorized:
|
|
||||||
Log.Error("Your bot token is wrong.\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");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HttpStatusCode.BadRequest:
|
Log.Fatal(ex, "Fatal error occurred while loading credentials");
|
||||||
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.");
|
|
||||||
Log.Error("If you are on Linux, make sure Redis is installed and running");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HttpStatusCode.RequestTimeout:
|
|
||||||
Log.Error("The request timed out. Make sure you have no external program blocking the bot " +
|
|
||||||
"from connecting to the internet");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HttpStatusCode.ServiceUnavailable:
|
|
||||||
case HttpStatusCode.InternalServerError:
|
|
||||||
Log.Error("Discord is having internal issues. Please, try again later");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HttpStatusCode.TooManyRequests:
|
|
||||||
Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n" +
|
|
||||||
"Global ratelimits usually last for an hour");
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
Log.Warning("An error occurred while attempting to connect to Discord");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Fatal(ex.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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>
|
int Priority { get; }
|
||||||
/// Implemented by modules which block execution before anything is executed
|
Task<bool> RunBehavior(IGuild guild, IUserMessage msg);
|
||||||
/// </summary>
|
}
|
||||||
public interface IEarlyBehavior
|
|
||||||
{
|
|
||||||
int Priority { get; }
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
10
src/NadekoBot/Common/ModuleBehaviors/IInputTransformer.cs
Normal file
10
src/NadekoBot/Common/ModuleBehaviors/IInputTransformer.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
|
public interface IInputTransformer
|
||||||
|
{
|
||||||
|
Task<string> TransformInput(
|
||||||
|
IGuild guild,
|
||||||
|
IMessageChannel channel,
|
||||||
|
IUser user,
|
||||||
|
string input);
|
||||||
|
}
|
@@ -1,13 +1,8 @@
|
|||||||
using System.Threading.Tasks;
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
using Discord.Commands;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
public interface ILateBlocker
|
||||||
{
|
{
|
||||||
public interface ILateBlocker
|
public int Priority { get; }
|
||||||
{
|
|
||||||
public int Priority { get; }
|
Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command);
|
||||||
|
}
|
||||||
Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +1,9 @@
|
|||||||
using System.Threading.Tasks;
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
using Discord;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
/// <summary>
|
||||||
|
/// Last thing to be executed, won't stop further executions
|
||||||
|
/// </summary>
|
||||||
|
public interface ILateExecutor
|
||||||
{
|
{
|
||||||
/// <summary>
|
Task LateExecute(IGuild guild, IUserMessage msg);
|
||||||
/// Last thing to be executed, won't stop further executions
|
}
|
||||||
/// </summary>
|
|
||||||
public interface ILateExecutor
|
|
||||||
{
|
|
||||||
Task LateExecute(IGuild guild, IUserMessage msg);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,34 +0,0 @@
|
|||||||
using Discord;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
|
||||||
{
|
|
||||||
public struct ModuleBehaviorResult
|
|
||||||
{
|
|
||||||
public bool Blocked { get; set; }
|
|
||||||
public string NewInput { get; set; }
|
|
||||||
|
|
||||||
public static ModuleBehaviorResult None() => new ModuleBehaviorResult
|
|
||||||
{
|
|
||||||
Blocked = false,
|
|
||||||
NewInput = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static ModuleBehaviorResult FromBlocked(bool blocked) => new ModuleBehaviorResult
|
|
||||||
{
|
|
||||||
Blocked = blocked,
|
|
||||||
NewInput = null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IModuleBehavior
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Negative priority means it will try to apply as early as possible
|
|
||||||
/// Positive priority menas it will try to apply as late as possible
|
|
||||||
/// </summary>
|
|
||||||
int Priority { get; }
|
|
||||||
Task<ModuleBehaviorResult> ApplyBehavior(DiscordSocketClient client, IGuild guild, IUserMessage msg);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,16 +1,13 @@
|
|||||||
using System.Threading.Tasks;
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
/// <summary>
|
||||||
|
/// All services which need to execute something after
|
||||||
|
/// the bot is ready should implement this interface
|
||||||
|
/// </summary>
|
||||||
|
public interface IReadyExecutor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All services which need to execute something after
|
/// Executed when bot is ready
|
||||||
/// the bot is ready should implement this interface
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IReadyExecutor
|
public Task OnReadyAsync();
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Executed when bot is ready
|
|
||||||
/// </summary>
|
|
||||||
public Task OnReadyAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,157 +1,137 @@
|
|||||||
using Discord;
|
#nullable disable
|
||||||
using Discord.Commands;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
using NadekoBot.Extensions;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules;
|
||||||
|
|
||||||
|
[UsedImplicitly(ImplicitUseTargetFlags.Default
|
||||||
|
| ImplicitUseTargetFlags.WithInheritors
|
||||||
|
| ImplicitUseTargetFlags.WithMembers)]
|
||||||
|
public abstract class NadekoModule : ModuleBase
|
||||||
{
|
{
|
||||||
public abstract class NadekoModule : ModuleBase
|
protected CultureInfo Culture { get; set; }
|
||||||
|
|
||||||
|
// Injected by Discord.net
|
||||||
|
public IBotStrings Strings { get; set; }
|
||||||
|
public CommandHandler _cmdHandler { get; set; }
|
||||||
|
public ILocalization _localization { get; set; }
|
||||||
|
public IEmbedBuilderService _eb { get; set; }
|
||||||
|
|
||||||
|
protected string prefix
|
||||||
|
=> _cmdHandler.GetPrefix(ctx.Guild);
|
||||||
|
|
||||||
|
protected ICommandContext ctx
|
||||||
|
=> Context;
|
||||||
|
|
||||||
|
protected override void BeforeExecute(CommandInfo command)
|
||||||
|
=> Culture = _localization.GetCultureInfo(ctx.Guild?.Id);
|
||||||
|
|
||||||
|
protected string GetText(in LocStr data)
|
||||||
|
=> Strings.GetText(data, Culture);
|
||||||
|
|
||||||
|
public Task<IUserMessage> SendErrorAsync(string error)
|
||||||
|
=> ctx.Channel.SendErrorAsync(_eb, error);
|
||||||
|
|
||||||
|
public Task<IUserMessage> SendErrorAsync(
|
||||||
|
string title,
|
||||||
|
string error,
|
||||||
|
string url = null,
|
||||||
|
string footer = null)
|
||||||
|
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
|
||||||
|
|
||||||
|
public Task<IUserMessage> SendConfirmAsync(string text)
|
||||||
|
=> ctx.Channel.SendConfirmAsync(_eb, text);
|
||||||
|
|
||||||
|
public Task<IUserMessage> SendConfirmAsync(
|
||||||
|
string title,
|
||||||
|
string text,
|
||||||
|
string url = null,
|
||||||
|
string footer = null)
|
||||||
|
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
|
||||||
|
|
||||||
|
public Task<IUserMessage> SendPendingAsync(string text)
|
||||||
|
=> ctx.Channel.SendPendingAsync(_eb, text);
|
||||||
|
|
||||||
|
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str)
|
||||||
|
=> SendErrorAsync(GetText(str));
|
||||||
|
|
||||||
|
public Task<IUserMessage> PendingLocalizedAsync(LocStr str)
|
||||||
|
=> SendPendingAsync(GetText(str));
|
||||||
|
|
||||||
|
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str)
|
||||||
|
=> SendConfirmAsync(GetText(str));
|
||||||
|
|
||||||
|
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str)
|
||||||
|
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||||
|
|
||||||
|
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str)
|
||||||
|
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||||
|
|
||||||
|
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str)
|
||||||
|
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||||
|
|
||||||
|
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
||||||
{
|
{
|
||||||
protected CultureInfo _cultureInfo { get; set; }
|
embed.WithPendingColor().WithFooter("yes/no");
|
||||||
public IBotStrings Strings { get; set; }
|
|
||||||
public CommandHandler CmdHandler { get; set; }
|
|
||||||
public ILocalization Localization { get; set; }
|
|
||||||
public IEmbedBuilderService _eb { get; set; }
|
|
||||||
|
|
||||||
public string Prefix => CmdHandler.GetPrefix(ctx.Guild);
|
var msg = await ctx.Channel.EmbedAsync(embed);
|
||||||
|
try
|
||||||
protected ICommandContext ctx => Context;
|
|
||||||
|
|
||||||
protected NadekoModule()
|
|
||||||
{
|
{
|
||||||
|
var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id);
|
||||||
|
input = input?.ToUpperInvariant();
|
||||||
|
|
||||||
|
if (input != "YES" && input != "Y")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_ = Task.Run(() => msg.DeleteAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ?
|
||||||
|
public async Task<string> GetUserInputAsync(ulong userId, ulong channelId)
|
||||||
|
{
|
||||||
|
var userInputTask = new TaskCompletionSource<string>();
|
||||||
|
var dsc = (DiscordSocketClient)ctx.Client;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dsc.MessageReceived += MessageReceived;
|
||||||
|
|
||||||
|
if (await Task.WhenAny(userInputTask.Task, Task.Delay(10000)) != userInputTask.Task)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return await userInputTask.Task;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
dsc.MessageReceived -= MessageReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void BeforeExecute(CommandInfo cmd)
|
Task MessageReceived(SocketMessage arg)
|
||||||
{
|
{
|
||||||
_cultureInfo = Localization.GetCultureInfo(ctx.Guild?.Id);
|
_ = Task.Run(() =>
|
||||||
}
|
|
||||||
|
|
||||||
protected string GetText(in LocStr data) =>
|
|
||||||
Strings.GetText(data, _cultureInfo);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendErrorAsync(string error)
|
|
||||||
=> ctx.Channel.SendErrorAsync(_eb, error);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendErrorAsync(string title, string error, string url = null, string footer = null)
|
|
||||||
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendConfirmAsync(string text)
|
|
||||||
=> ctx.Channel.SendConfirmAsync(_eb, text);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendConfirmAsync(string title, string text, string url = null, string footer = null)
|
|
||||||
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendPendingAsync(string text)
|
|
||||||
=> ctx.Channel.SendPendingAsync(_eb, text);
|
|
||||||
|
|
||||||
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str)
|
|
||||||
=> SendErrorAsync(GetText(str));
|
|
||||||
|
|
||||||
public Task<IUserMessage> PendingLocalizedAsync(LocStr str)
|
|
||||||
=> SendPendingAsync(GetText(str));
|
|
||||||
|
|
||||||
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str)
|
|
||||||
=> SendConfirmAsync(GetText(str));
|
|
||||||
|
|
||||||
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str)
|
|
||||||
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
|
||||||
|
|
||||||
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str)
|
|
||||||
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
|
||||||
|
|
||||||
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str)
|
|
||||||
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
|
||||||
|
|
||||||
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
|
||||||
{
|
|
||||||
embed
|
|
||||||
.WithPendingColor()
|
|
||||||
.WithFooter("yes/no");
|
|
||||||
|
|
||||||
var msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id).ConfigureAwait(false);
|
if (arg is not SocketUserMessage userMsg
|
||||||
input = input?.ToUpperInvariant();
|
|| userMsg.Channel is not ITextChannel
|
||||||
|
|| userMsg.Author.Id != userId
|
||||||
if (input != "YES" && input != "Y")
|
|| userMsg.Channel.Id != channelId)
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
var _ = Task.Run(() => msg.DeleteAsync());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ?
|
|
||||||
public async Task<string> GetUserInputAsync(ulong userId, ulong channelId)
|
|
||||||
{
|
|
||||||
var userInputTask = new TaskCompletionSource<string>();
|
|
||||||
var dsc = (DiscordSocketClient)ctx.Client;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
dsc.MessageReceived += MessageReceived;
|
|
||||||
|
|
||||||
if ((await Task.WhenAny(userInputTask.Task, Task.Delay(10000)).ConfigureAwait(false)) != userInputTask.Task)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await userInputTask.Task.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
dsc.MessageReceived -= MessageReceived;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task MessageReceived(SocketMessage arg)
|
|
||||||
{
|
|
||||||
var _ = Task.Run(() =>
|
|
||||||
{
|
|
||||||
if (!(arg is SocketUserMessage userMsg) ||
|
|
||||||
!(userMsg.Channel is ITextChannel chan) ||
|
|
||||||
userMsg.Author.Id != userId ||
|
|
||||||
userMsg.Channel.Id != channelId)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userInputTask.TrySetResult(arg.Content))
|
|
||||||
{
|
|
||||||
userMsg.DeleteAfter(1);
|
|
||||||
}
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
});
|
|
||||||
|
if (userInputTask.TrySetResult(arg.Content))
|
||||||
|
userMsg.DeleteAfter(1);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
});
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class NadekoModule<TService> : NadekoModule
|
public abstract class NadekoModule<TService> : NadekoModule
|
||||||
{
|
{
|
||||||
public TService _service { get; set; }
|
public TService _service { get; set; }
|
||||||
|
|
||||||
protected NadekoModule() : base()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class NadekoSubmodule : NadekoModule
|
|
||||||
{
|
|
||||||
protected NadekoSubmodule() : base() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class NadekoSubmodule<TService> : NadekoModule<TService>
|
|
||||||
{
|
|
||||||
protected NadekoSubmodule() : base()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,7 +0,0 @@
|
|||||||
namespace NadekoBot.Modules
|
|
||||||
{
|
|
||||||
public static class NadekoModuleExtensions
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,74 +1,69 @@
|
|||||||
using System;
|
#nullable disable
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class NadekoRandom : Random
|
||||||
{
|
{
|
||||||
public class NadekoRandom : Random
|
private readonly RandomNumberGenerator _rng;
|
||||||
|
|
||||||
|
public NadekoRandom()
|
||||||
|
=> _rng = RandomNumberGenerator.Create();
|
||||||
|
|
||||||
|
public override int Next()
|
||||||
{
|
{
|
||||||
readonly RandomNumberGenerator _rng;
|
var bytes = new byte[sizeof(int)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
public NadekoRandom() : base()
|
return Math.Abs(BitConverter.ToInt32(bytes, 0));
|
||||||
{
|
|
||||||
_rng = RandomNumberGenerator.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Next()
|
|
||||||
{
|
|
||||||
var bytes = new byte[sizeof(int)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
return Math.Abs(BitConverter.ToInt32(bytes, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Next(int maxValue)
|
|
||||||
{
|
|
||||||
if (maxValue <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
|
||||||
var bytes = new byte[sizeof(int)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Next(int minValue, int maxValue)
|
|
||||||
{
|
|
||||||
if (minValue > maxValue)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
|
||||||
if (minValue == maxValue)
|
|
||||||
return minValue;
|
|
||||||
var bytes = new byte[sizeof(int)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
var sign = Math.Sign(BitConverter.ToInt32(bytes, 0));
|
|
||||||
return (sign * BitConverter.ToInt32(bytes, 0)) % (maxValue - minValue) + minValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long NextLong(long minValue, long maxValue)
|
|
||||||
{
|
|
||||||
if (minValue > maxValue)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
|
||||||
if (minValue == maxValue)
|
|
||||||
return minValue;
|
|
||||||
var bytes = new byte[sizeof(long)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
var sign = Math.Sign(BitConverter.ToInt64(bytes, 0));
|
|
||||||
return (sign * BitConverter.ToInt64(bytes, 0)) % (maxValue - minValue) + minValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void NextBytes(byte[] buffer)
|
|
||||||
{
|
|
||||||
_rng.GetBytes(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double Sample()
|
|
||||||
{
|
|
||||||
var bytes = new byte[sizeof(double)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
return Math.Abs(BitConverter.ToDouble(bytes, 0) / double.MaxValue + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override double NextDouble()
|
|
||||||
{
|
|
||||||
var bytes = new byte[sizeof(double)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
return BitConverter.ToDouble(bytes, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public override int Next(int maxValue)
|
||||||
|
{
|
||||||
|
if (maxValue <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
||||||
|
var bytes = new byte[sizeof(int)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Next(int minValue, int maxValue)
|
||||||
|
{
|
||||||
|
if (minValue > maxValue)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
||||||
|
if (minValue == maxValue)
|
||||||
|
return minValue;
|
||||||
|
var bytes = new byte[sizeof(int)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
var sign = Math.Sign(BitConverter.ToInt32(bytes, 0));
|
||||||
|
return (sign * BitConverter.ToInt32(bytes, 0) % (maxValue - minValue)) + minValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long NextLong(long minValue, long maxValue)
|
||||||
|
{
|
||||||
|
if (minValue > maxValue)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
||||||
|
if (minValue == maxValue)
|
||||||
|
return minValue;
|
||||||
|
var bytes = new byte[sizeof(long)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
var sign = Math.Sign(BitConverter.ToInt64(bytes, 0));
|
||||||
|
return (sign * BitConverter.ToInt64(bytes, 0) % (maxValue - minValue)) + minValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NextBytes(byte[] buffer)
|
||||||
|
=> _rng.GetBytes(buffer);
|
||||||
|
|
||||||
|
protected override double Sample()
|
||||||
|
{
|
||||||
|
var bytes = new byte[sizeof(double)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
return Math.Abs((BitConverter.ToDouble(bytes, 0) / double.MaxValue) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override double NextDouble()
|
||||||
|
{
|
||||||
|
var bytes = new byte[sizeof(double)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
return BitConverter.ToDouble(bytes, 0);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,19 +1,21 @@
|
|||||||
using System;
|
#nullable disable
|
||||||
using System.Threading.Tasks;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Discord.Commands;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||||
|
[SuppressMessage("Style", "IDE0022:Use expression body for methods")]
|
||||||
|
public sealed class NoPublicBotAttribute : PreconditionAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||||
public sealed class NoPublicBotAttribute : PreconditionAttribute
|
ICommandContext context,
|
||||||
|
CommandInfo command,
|
||||||
|
IServiceProvider services)
|
||||||
{
|
{
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
|
||||||
{
|
|
||||||
#if GLOBAL_NADEKO
|
#if GLOBAL_NADEKO
|
||||||
return Task.FromResult(PreconditionResult.FromError("Not available on the public bot. To learn how to selfhost a private bot, click [here](https://nadekobot.readthedocs.io/en/latest/)."));
|
return Task.FromResult(PreconditionResult.FromError("Not available on the public bot. To learn how to selfhost a private bot, click [here](https://nadekobot.readthedocs.io/en/latest/)."));
|
||||||
#else
|
#else
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
46
src/NadekoBot/Common/OldCreds.cs
Normal file
46
src/NadekoBot/Common/OldCreds.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#nullable disable
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class OldCreds
|
||||||
|
{
|
||||||
|
public string Token { get; set; } = string.Empty;
|
||||||
|
public ulong[] OwnerIds { get; set; } = new ulong[1];
|
||||||
|
public string LoLApiKey { get; set; } = string.Empty;
|
||||||
|
public string GoogleApiKey { get; set; } = string.Empty;
|
||||||
|
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; }
|
||||||
|
|
||||||
|
public string ShardRunCommand { get; set; } = string.Empty;
|
||||||
|
public string ShardRunArguments { get; set; } = string.Empty;
|
||||||
|
public int? ShardRunPort { get; set; }
|
||||||
|
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 string Cmd { get; set; }
|
||||||
|
public string Args { get; set; }
|
||||||
|
|
||||||
|
public RestartConfig(string cmd, string args)
|
||||||
|
{
|
||||||
|
Cmd = cmd;
|
||||||
|
Args = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,49 +1,47 @@
|
|||||||
using System;
|
#nullable disable
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
public class OldImageUrls
|
||||||
{
|
{
|
||||||
public class OldImageUrls
|
public int Version { get; set; } = 2;
|
||||||
|
|
||||||
|
public CoinData Coins { get; set; }
|
||||||
|
public Uri[] Currency { get; set; }
|
||||||
|
public Uri[] Dice { get; set; }
|
||||||
|
public RategirlData Rategirl { get; set; }
|
||||||
|
public XpData Xp { get; set; }
|
||||||
|
|
||||||
|
//new
|
||||||
|
public RipData Rip { get; set; }
|
||||||
|
public SlotData Slots { get; set; }
|
||||||
|
|
||||||
|
public class RipData
|
||||||
{
|
{
|
||||||
public int Version { get; set; } = 2;
|
public Uri Bg { get; set; }
|
||||||
|
public Uri Overlay { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public CoinData Coins { get; set; }
|
public class SlotData
|
||||||
public Uri[] Currency { get; set; }
|
{
|
||||||
public Uri[] Dice { get; set; }
|
public Uri[] Emojis { get; set; }
|
||||||
public RategirlData Rategirl { get; set; }
|
public Uri[] Numbers { get; set; }
|
||||||
public XpData Xp { get; set; }
|
public Uri Bg { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
//new
|
public class CoinData
|
||||||
public RipData Rip { get; set; }
|
{
|
||||||
public SlotData Slots { get; set; }
|
public Uri[] Heads { get; set; }
|
||||||
|
public Uri[] Tails { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class RipData
|
public class RategirlData
|
||||||
{
|
{
|
||||||
public Uri Bg { get; set; }
|
public Uri Matrix { get; set; }
|
||||||
public Uri Overlay { get; set; }
|
public Uri Dot { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SlotData
|
public class XpData
|
||||||
{
|
{
|
||||||
public Uri[] Emojis { get; set; }
|
public Uri Bg { get; set; }
|
||||||
public Uri[] Numbers { get; set; }
|
|
||||||
public Uri Bg { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CoinData
|
|
||||||
{
|
|
||||||
public Uri[] Heads { get; set; }
|
|
||||||
public Uri[] Tails { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RategirlData
|
|
||||||
{
|
|
||||||
public Uri Matrix { get; set; }
|
|
||||||
public Uri Dot { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class XpData
|
|
||||||
{
|
|
||||||
public Uri Bg { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,24 +1,24 @@
|
|||||||
using CommandLine;
|
#nullable disable
|
||||||
|
using CommandLine;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public static class OptionsParser
|
||||||
{
|
{
|
||||||
public static class OptionsParser
|
public static T ParseFrom<T>(string[] args)
|
||||||
{
|
where T : INadekoCommandOptions, new()
|
||||||
public static T ParseFrom<T>(string[] args) where T : INadekoCommandOptions, new()
|
=> ParseFrom(new T(), args).Item1;
|
||||||
=> ParseFrom(new T(), args).Item1;
|
|
||||||
|
|
||||||
public static (T, bool) ParseFrom<T>(T options, string[] args) where T : INadekoCommandOptions
|
public static (T, bool) ParseFrom<T>(T options, string[] args)
|
||||||
|
where T : INadekoCommandOptions
|
||||||
|
{
|
||||||
|
using var p = new Parser(x =>
|
||||||
{
|
{
|
||||||
using (var p = new Parser(x =>
|
x.HelpWriter = null;
|
||||||
{
|
});
|
||||||
x.HelpWriter = null;
|
var res = p.ParseArguments<T>(args);
|
||||||
}))
|
var output = res.MapResult(x => x, _ => options);
|
||||||
{
|
output.NormalizeOptions();
|
||||||
var res = p.ParseArguments<T>(args);
|
return (output, res.Tag == ParserResultType.Parsed);
|
||||||
options = res.MapResult(x => x, x => options);
|
|
||||||
options.NormalizeOptions();
|
|
||||||
return (options, res.Tag == ParserResultType.Parsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user