mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Compare commits
105 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f616364d8a | ||
|
4294f8efd5 | ||
|
69eb5f2c56 | ||
|
8d26d16fff | ||
|
bdde065209 | ||
|
e2477638b5 | ||
|
f398cddaf0 | ||
|
dc846965ae | ||
|
dbbdc66dca | ||
|
df85b3b250 | ||
|
f1d9db699f | ||
|
210da263ad | ||
|
1716c69132 | ||
|
14bfcb54dc | ||
|
9f445c0866 | ||
|
3343fd2f6e | ||
|
9103dd9fdb | ||
|
1a8c9a6cba | ||
|
9d2f251923 | ||
|
3744dd287c | ||
|
f65ba100af | ||
|
cc52605c90 | ||
|
3d3dc532dc | ||
|
6c58a6a72d | ||
|
cefd81d810 | ||
|
34c96c697a | ||
|
1cc5e0e1d8 | ||
|
deaedce6c7 | ||
|
91e4d9dffc | ||
|
a826f4245f | ||
|
780eec62b3 | ||
|
dbeb83561a | ||
|
6c11d11645 | ||
|
e9923a7691 | ||
|
5fbe93d898 | ||
|
65995bdca4 | ||
|
f7c333b671 | ||
|
f9d18aa086 | ||
|
571e1c801f | ||
|
f922543d33 | ||
|
6bec67006c | ||
|
050eaa48eb | ||
|
248ce8b3d2 | ||
|
04a488cdf2 | ||
|
6bc2fc88f9 | ||
|
69b6ed6a49 | ||
|
e30b126726 | ||
|
a5e2321c5b | ||
|
322e9a329d | ||
|
7ca6ab8562 | ||
|
8a27dcc481 | ||
|
bed61c521f | ||
|
46ea1698eb | ||
|
c47417024d | ||
|
eedc2d05ff | ||
|
d24dba7ed0 | ||
|
9bdf58ec27 | ||
|
5de9c5d067 | ||
|
64b2a46c95 | ||
|
f42deda3e2 | ||
|
a464e7c643 | ||
|
1f36fa75c4 | ||
|
ad6d732687 | ||
|
1f51c54449 | ||
|
7b5145f116 | ||
|
18b10b8c6f | ||
|
f05435f864 | ||
|
7cbedc82bf | ||
|
3be208f1b3 | ||
|
2606bda8df | ||
|
ab1272b491 | ||
|
43047c0ab0 | ||
|
34471abd64 | ||
|
fa259384f1 | ||
|
cb865d5012 | ||
|
1db97decd1 | ||
|
b02768a08e | ||
|
e55d60f1aa | ||
|
b009438e0e | ||
|
4b5d27d963 | ||
|
91ee0d121c | ||
|
a8767f1136 | ||
|
44478e0f47 | ||
|
c73c2da6a4 | ||
|
5ed005211e | ||
|
d80cbb4647 | ||
|
9a96ef76ba | ||
|
5b5bc278ff | ||
|
5cb95cf94d | ||
|
f132aa2624 | ||
|
3b6b3bcf07 | ||
|
78d97db224 | ||
|
35ddd150ba | ||
|
39ae070c9d | ||
|
24a9a02cc3 | ||
|
0f68abcac9 | ||
|
908c61633d | ||
|
054fc30672 | ||
|
11ffdd84a3 | ||
|
5d2d74b92a | ||
|
18400dc53a | ||
|
29d94640af | ||
|
f6a53b96c7 | ||
|
1aa95a5dd0 | ||
|
fcfeb152c9 |
173
.gitlab-ci.yml
173
.gitlab-ci.yml
@@ -7,6 +7,7 @@ stages:
|
||||
- release
|
||||
- publish-windows
|
||||
- upload-windows-updater-release
|
||||
- publish-medusa-package
|
||||
|
||||
variables:
|
||||
project: "NadekoBot"
|
||||
@@ -18,41 +19,41 @@ variables:
|
||||
PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/NadekoBot-build/${CI_COMMIT_TAG}"
|
||||
INSTALLER_OUTPUT_DIR: "nadeko-installers/${CI_COMMIT_TAG}"
|
||||
INSTALLER_FILE_NAME: "nadeko-setup-${CI_COMMIT_TAG}.exe"
|
||||
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- "dotnet publish -c Release -r linux-x64 -o $LINUX_X64_OUTPUT_DIR src/NadekoBot/NadekoBot.csproj"
|
||||
- "dotnet publish -c Release -r win7-x64 -o $WIN_X64_OUTPUT_DIR src/NadekoBot/NadekoBot.csproj"
|
||||
- "dotnet publish -c Release -r linux-x64 --self-contained -o $LINUX_X64_OUTPUT_DIR src/NadekoBot/NadekoBot.csproj"
|
||||
- "dotnet publish -c Release -r win7-x64 --self-contained -o $WIN_X64_OUTPUT_DIR src/NadekoBot/NadekoBot.csproj"
|
||||
artifacts:
|
||||
paths:
|
||||
- "$LINUX_X64_OUTPUT_DIR/"
|
||||
- "$WIN_X64_OUTPUT_DIR/"
|
||||
|
||||
upload-builds:
|
||||
stage: upload-builds
|
||||
image: alpine:latest
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
script:
|
||||
- apk add --no-cache curl tar zip
|
||||
- "tar cvf $LINUX_X64_RELEASE $LINUX_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 $WIN_X64_RELEASE $PACKAGE_REGISTRY_URL/$WIN_X64_RELEASE
|
||||
stage: upload-builds
|
||||
image: alpine:latest
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
script:
|
||||
- apk add --no-cache curl tar zip
|
||||
- "tar cvf $LINUX_X64_RELEASE $LINUX_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 $WIN_X64_RELEASE $PACKAGE_REGISTRY_URL/$WIN_X64_RELEASE
|
||||
|
||||
release:
|
||||
stage: release
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
script:
|
||||
- |
|
||||
release-cli create --name "NadekoBot v$CI_COMMIT_TAG" --description "## [Changelog](https://gitlab.com/Kwoth/nadekobot/-/blob/v4/CHANGELOG.md#$(echo "$CI_COMMIT_TAG" | sed "s/\.//g")-$(date +%d%m%Y))" --tag-name $CI_COMMIT_TAG \
|
||||
--assets-link "{\"name\":\"${LINUX_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_X64_RELEASE}\"}" \
|
||||
--assets-link "{\"name\":\"${WIN_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${WIN_X64_RELEASE}\"}"
|
||||
stage: release
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
script:
|
||||
- |
|
||||
release-cli create --name "NadekoBot v$CI_COMMIT_TAG" --description "## [Changelog](https://gitlab.com/Kwoth/nadekobot/-/blob/v4/CHANGELOG.md#$(echo "$CI_COMMIT_TAG" | sed "s/\.//g")-$(date +%d%m%Y))" --tag-name $CI_COMMIT_TAG \
|
||||
--assets-link "{\"name\":\"${LINUX_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_X64_RELEASE}\"}" \
|
||||
--assets-link "{\"name\":\"${WIN_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${WIN_X64_RELEASE}\"}"
|
||||
|
||||
test:
|
||||
stage: test
|
||||
@@ -63,63 +64,75 @@ test:
|
||||
- "dotnet test"
|
||||
|
||||
publish-windows:
|
||||
stage: publish-windows
|
||||
rules:
|
||||
- if: '$CI_COMMIT_TAG'
|
||||
image: scottyhardy/docker-wine
|
||||
before_script:
|
||||
- choco install dotnet-6.0-runtime -y
|
||||
- choco install dotnet-6.0-sdk -y
|
||||
- choco install innosetup -y
|
||||
artifacts:
|
||||
paths:
|
||||
- "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
||||
script:
|
||||
- dotnet clean
|
||||
- dotnet restore
|
||||
- dotnet publish -c Release --runtime win7-x64 /p:Version=$CI_COMMIT_TAG src/NadekoBot
|
||||
- $env:NADEKOBOT_INSTALL_VERSION = $CI_COMMIT_TAG
|
||||
- iscc.exe "/O+" ".\exe_builder.iss"
|
||||
tags:
|
||||
- windows
|
||||
stage: publish-windows
|
||||
rules:
|
||||
- if: "$CI_COMMIT_TAG"
|
||||
image: scottyhardy/docker-wine
|
||||
before_script:
|
||||
- choco install dotnet-6.0-runtime --version=6.0.4 -y
|
||||
- choco install dotnet-6.0-sdk --version=6.0.202 -y
|
||||
- choco install innosetup -y
|
||||
artifacts:
|
||||
paths:
|
||||
- "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
||||
script:
|
||||
- dotnet clean
|
||||
- dotnet restore -f --no-cache -v n
|
||||
- dotnet publish -c Release --runtime win7-x64 /p:Version=$CI_COMMIT_TAG src/NadekoBot
|
||||
- $env:NADEKOBOT_INSTALL_VERSION = $CI_COMMIT_TAG
|
||||
- iscc.exe "/O+" ".\exe_builder.iss"
|
||||
tags:
|
||||
- windows
|
||||
|
||||
upload-windows-updater-release:
|
||||
stage: upload-windows-updater-release
|
||||
rules:
|
||||
- if: '$CI_COMMIT_TAG'
|
||||
image:
|
||||
name: amazon/aws-cli
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- sed -i "s/_INSTALLER_FILE_NAME_/$INSTALLER_FILE_NAME/g" releases-v3.json
|
||||
- sed -i "s/_VERSION_/$CI_COMMIT_TAG/g" releases-v3.json
|
||||
- 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/releases-v3.json" --acl public-read --body "releases-v3.json"
|
||||
stage: upload-windows-updater-release
|
||||
rules:
|
||||
- if: "$CI_COMMIT_TAG"
|
||||
image:
|
||||
name: amazon/aws-cli
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- sed -i "s/_INSTALLER_FILE_NAME_/$INSTALLER_FILE_NAME/g" releases-v3.json
|
||||
- sed -i "s/_VERSION_/$CI_COMMIT_TAG/g" releases-v3.json
|
||||
- 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/releases-v3.json" --acl public-read --body "releases-v3.json"
|
||||
|
||||
publish-medusa-package:
|
||||
stage: publish-medusa-package
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
|
||||
script:
|
||||
- LAST_TAG=$(git describe --tags --abbrev=0)
|
||||
- if [ $CI_COMMIT_TAG ];then MEDUSA_VERSION="$CI_COMMIT_TAG"; else MEDUSA_VERSION="$LAST_TAG-$CI_COMMIT_SHA"; fi
|
||||
- cd src/Nadeko.Medusa/
|
||||
- dotnet pack -c Release /p:Version=$MEDUSA_VERSION -o bin/Release/packed
|
||||
- dotnet nuget push bin/Release/packed/ --source https://www.myget.org/F/nadeko/api/v2/package --api-key "$MYGET_API_KEY"
|
||||
|
||||
docker-build:
|
||||
# Use the official docker image.
|
||||
image: docker:latest
|
||||
stage: build
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
# Default branch leaves tag empty (= latest tag)
|
||||
# All other branches are tagged with the escaped branch name (commit ref slug)
|
||||
script:
|
||||
- |
|
||||
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
|
||||
tag=""
|
||||
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
||||
else
|
||||
tag=":$CI_COMMIT_REF_SLUG"
|
||||
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
||||
fi
|
||||
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
|
||||
- docker push "$CI_REGISTRY_IMAGE${tag}"
|
||||
# Run this job in a branch where a Dockerfile exists
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
exists:
|
||||
- Dockerfile
|
||||
# Use the official docker image.
|
||||
image: docker:latest
|
||||
stage: build
|
||||
allow_failure: true
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
# Default branch leaves tag empty (= latest tag)
|
||||
# All other branches are tagged with the escaped branch name (commit ref slug)
|
||||
script:
|
||||
- |
|
||||
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
|
||||
tag=""
|
||||
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
||||
else
|
||||
tag=":$CI_COMMIT_SHA"
|
||||
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
||||
fi
|
||||
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
|
||||
- docker push "$CI_REGISTRY_IMAGE${tag}"
|
||||
# Run this job in a branch where a Dockerfile exists
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
|
||||
exists:
|
||||
- Dockerfile
|
||||
|
278
CHANGELOG.md
278
CHANGELOG.md
@@ -1,8 +1,280 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||
|
||||
## Unreleased
|
||||
## [4.2.9] - 25.06.202
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `creds_example.yml` misssing from output directory
|
||||
|
||||
## [4.2.8] - 24.06.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.timely` should be fixed
|
||||
|
||||
## [4.2.7] - 24.06.2022
|
||||
|
||||
### Changed
|
||||
|
||||
- New cache abstraction added
|
||||
- 2 implemenations: redis and memory
|
||||
- All current bots will stay on redis cache, all new bots will use **in-process memory cache by default**
|
||||
- This change removes bot's hard dependency on redis
|
||||
- Configurable in `creds.yml` (please read the comments)
|
||||
- You **MUST** use 'redis' if your bot runs on more than 1 shard (2000+ servers)
|
||||
- [dev] Using new non-locking ConcurrentDictionary
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.xp` will now show default user avatars too
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `.imagesreload` as images are now lazily loaded on request and then cached
|
||||
|
||||
## [4.2.6] - 22.06.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Patron system should now properly by disabled on selfhosts by default.
|
||||
|
||||
## [4.2.5] - 18.06.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.crypto`, you will still need coinmarketcapApiKey in `creds.yml` in order to make it run consistently as the key is shared
|
||||
|
||||
## [4.2.3] - 17.06.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.timely` nullref bug and made it nicer
|
||||
- Fixed `.streamrole` not updating in real time!
|
||||
- Disabling specific Global Expressions should now work with `.sc` (and other permission commands)
|
||||
|
||||
## [4.2.2] - 15.06.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Added missing Patron Tiers and fixed Patron pledge update bugs
|
||||
- Prevented creds_example.yml error in docker containers from crashing it
|
||||
|
||||
### Changed
|
||||
|
||||
- Rss feeds will now show error counter before deletion
|
||||
|
||||
## [4.2.1] - 14.06.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Localized strings updated
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.exexport`, `.savechat`, and `.quoteexport`
|
||||
- Fixed plaintext-only embeds
|
||||
- Fixed greet message footer not showing origin server
|
||||
|
||||
## [4.2.0] - 14.06.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Added `data/searches.yml` file which configures some of the new search functionality
|
||||
The file comments explaining what each property does.
|
||||
Explained briefly here:
|
||||
```yml
|
||||
# what will be used for .google command. Either google (official api) or searx
|
||||
webSearchEngine: Google
|
||||
# what will be used for .img command. Either google (official api) or searx
|
||||
imgSearchEngine: Google
|
||||
# how will yt results be retrieved: ytdataapi or ytdl or ytdlp
|
||||
ytProvider: YtDataApiv3
|
||||
# in case web or img search is set to searx, the following instances will be used:
|
||||
searxInstances: []
|
||||
# in case ytProvider is set to invidious, the following instances will be used
|
||||
invidiousInstances: []
|
||||
```
|
||||
- Added new properties to `creds.yml`. google -> searchId and google -> searchImageId.
|
||||
- These properties are used as `cx` (google api query parameter) in case you've setup your `data/searches.yml` to use the official google api.
|
||||
`searchId` is used for web search
|
||||
`searchimageId` is used for image search
|
||||
```yml
|
||||
google:
|
||||
searchId: ""
|
||||
searchImageId: ""
|
||||
```
|
||||
- Check `creds_example.yml` for comments explaining how to obtain them.
|
||||
|
||||
#### Patronage system added
|
||||
- Added `data/patron.yml` for configuration
|
||||
- Implemented only for patreon so far
|
||||
- Patreon subscription code completely rewritten
|
||||
- Users who pledge on patreon get benefits based on the amount they pledged
|
||||
- Public nadeko only. But selfhosters can adapt it to their own patreon pages by configuring their patreon credentials in `creds.yml` and enabling the system in `data/patron.yml` file.
|
||||
- Most of the patronage system strings are hardcoded atm, so if you wish to use this system on selfhosts, you will have to modify the source
|
||||
- Pledge amounts are split into tiers. This is not configurable atm.
|
||||
- Tier I - 1$ - 4.99$ a month
|
||||
- Tier V - 5$ - 9.99$ a month
|
||||
- Tier X - 10$ - 19.99$ a month
|
||||
- Tier XX - 20$ - 49.99$ a month
|
||||
- Tier L - 50$ - 99.99$ a month
|
||||
- Tier C - 100$+ a month
|
||||
- Rewards and command quotas for each of the tiers are configurable
|
||||
- Limitations to certain features are also configurable. ex:
|
||||
```yml
|
||||
quotas:
|
||||
features:
|
||||
"rero:max_count":
|
||||
x: 50
|
||||
```
|
||||
- ^ this setting would set the maximum number of reaction roles to be 50 for a user who is in Patron Tier X
|
||||
- Read the comments in the .yml file for (much) more info
|
||||
- Quota system allows the owner to set up hourly, daily and monthly quota usage for each tier
|
||||
- Quota system applies to entire server owner by a patron
|
||||
- Patron spends own quota by using the commands on any server
|
||||
- Any user on *any* server owned by a patron spends that patron's quota
|
||||
- When users subscribe to patreon they will receive a welcome message
|
||||
- If you're enabling patron system for a selfhost, you will want to edit it
|
||||
|
||||
Added `.patron` and `.patronmessage` commands
|
||||
- `.patron` checks your patronage status, and quotas. Requires patron system to be enabled.
|
||||
- `.patronmessage` (owner only) sends message to all patrons with the specified tier or higher. Supports embeds
|
||||
|
||||
- Added a fake `.cmdcd` command `cleverbot:response` which can be used to limit how often users can talk to the cleverbot.
|
||||
|
||||
### Changed
|
||||
|
||||
- CurrencyReward now support adding additional flowers to patrons.
|
||||
- `.donate` command completely reworked.
|
||||
- Works only on public bot (OnlyPublicBotAttribute)
|
||||
- Guides user on how to donate to support the project
|
||||
- Added interaction explaining selfhosting
|
||||
|
||||
- `.google` reimplemented. It now has 2 modes configurable in `data/searches.yml` under the `webSearchengine` property
|
||||
- If set to `google`, official custom search api will be used. You will need to set googleapikey and google.searchId in `creds.yml`
|
||||
- if set to `searx` one of the instances specified in the `searxInstances:` property will be randomly chosen for each request
|
||||
- instances must have `format=json` allowed (public ones usually don't allow it)
|
||||
- instances are specified as a fully qualified url, example: `https://my.cool.searx.instance.io`
|
||||
- `.image` reimplemented. Same as `.google` - it uses either `google` official api (in which case it uses `google.searchImageId` from `creds.yml`) or `searx`
|
||||
|
||||
- `.youtube` reimplemented. It will use a `ytProvider:` property from `data/searches.yml` to determine how to retrieve results
|
||||
- `ytdataapi` will use the official google api (requires `GoogleApiKey` specified in `creds.yml`) and YoutubeDataApi enabled in the dev console
|
||||
- `ytdl` will use `youtube-dl` program from the host machine. It must be downloaded and it's location must be added to path env variable.
|
||||
- `ytdlp` will use `yt-dlp` program from the host machine. Same as `youtube-dl` - must be in path env variable.
|
||||
- `invidious` will use one of invidious instances specified in the `invidiousInstances` property. Very good.
|
||||
|
||||
- `.google`, `.youtube` and `.image` moved to the new Search group
|
||||
|
||||
Note: Results of each `.youtube` query will be cached for 1 hour to improve perfomance
|
||||
- Removed 30 second `.ping` ratelimit on public nadeko
|
||||
|
||||
- xp image generation changes
|
||||
- In case you have default settings, your xp image will look slightly different
|
||||
- If you've modified xp_template.json, your xp image might look broken. Your old template will be saved in xp_template.json.old
|
||||
- Xp number outline is now slightly thicker
|
||||
- Xp number will now have Center vertical and horizontal alignment
|
||||
- LastLevelUp no longer supported
|
||||
|
||||
- Some commands will now use timestamp tags for better user experience
|
||||
- `.prune` was slightly slowed down to avoid ratelimits
|
||||
- `.wof` moved from it's own group to the default Gambling group
|
||||
- `.feed` urls which error for more than 100 times will be automatically removed.
|
||||
- `.ve` is now enabled by default
|
||||
|
||||
- [dev] nadeko interaction slightly improved to make it less nonsense (they still don't make sense)
|
||||
- [dev] RewardedUsers table slightly changed to make it more general
|
||||
- [dev] renamed `// todo`s which aren't planned soon to `// FUTURE`
|
||||
- [dev] currency rewards have been reimplemented and moved to a separate service
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.rh` no longer needs quotes for multi word roles
|
||||
- `.deletexp` will now properly delete server xp too
|
||||
- Fixed `.crypto` sparklines
|
||||
- [dev] added support for configs to properly parse enums without case sensitivity (ConfigParsers.InsensitiveEnum)
|
||||
- [dev] Fixed a bug in .gencmdlist
|
||||
- [dev] small fixes to creds provider
|
||||
|
||||
### Removed
|
||||
|
||||
- `.ddg` removed.
|
||||
- [dev] removed some dead code and comments
|
||||
|
||||
## [4.1.6] - 14.05.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed windows release and updated packages
|
||||
|
||||
## [4.1.5] - 11.05.2022
|
||||
|
||||
### Changed
|
||||
|
||||
- `.clubdesc <msg>` will now have a nicer response
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.give` DM will once again show an amount
|
||||
- Fixed an issue with filters not working and with custom reactions no longer being able to override commands.
|
||||
- Fixed `.stock` command
|
||||
|
||||
## [4.1.4] - 06.05.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.yun`
|
||||
|
||||
## [4.1.3] - 06.05.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for embed arrays in commands such as .say, .greet, .bye, etc...
|
||||
- Website to create them is live at eb.nadeko.bot (old one is moved to oldeb.nadeko.bot)
|
||||
- Embed arrays don't have a plainText property (it's renamed to 'content')
|
||||
- Embed arrays use color hex values instead of an integer
|
||||
- Old embed format will still work
|
||||
- There shouldn't be any breaking changes
|
||||
- Added `.stondel` command which, when toggled, will make the bot delete online stream messages on the server when the stream goes offline
|
||||
- Added a simple bank system.
|
||||
- Users can deposit, withdraw and check the balance of their currency in the bank.
|
||||
- Users can't check other user's bank balances.
|
||||
- Added a button on a .$ command which, when clicked, sends you a message with your bank balance that only you can see.
|
||||
- Added `.h <command group>`
|
||||
- Using this command will list all commands in the specified group
|
||||
- Atm only .bank is a proper group (`.h bank`)
|
||||
- Added "Bank Accounts" entry to `.economy`
|
||||
|
||||
### Changed
|
||||
|
||||
- Reaction roles rewritten completely
|
||||
- Supports multiple exclusivity groups per message
|
||||
- Supports level requirements
|
||||
- However they can only be added one by one
|
||||
- Use the following commands for more information
|
||||
- `.h .reroa`
|
||||
- `.h .reroli`
|
||||
- `.h .rerot`
|
||||
- `.h .rerorm`
|
||||
- `.h .rerodela`
|
||||
- Pagination is now using buttons instead of reactions
|
||||
- Bot will now support much higher XP values for global and server levels
|
||||
- [dev] Small change and generation perf improvement for the localized response strings
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.deletexp` command
|
||||
- `.give` command should send DMs again
|
||||
- `.modules` command now has a medusa module description
|
||||
|
||||
## [4.1.2] - 16.04.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue with missing `.dll` files in release versions
|
||||
|
||||
## [4.1.0] - 16.04.2022
|
||||
|
||||
@@ -30,14 +302,14 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
|
||||
|
||||
## [4.0.6] - 21.03.2022
|
||||
|
||||
### Fixes
|
||||
### Fixed
|
||||
|
||||
- Fixed voice presence logging
|
||||
- Fixed .clubaccept, .clubban, .clubkick and .clubunban commands
|
||||
|
||||
## [4.0.5] - 21.03.2022
|
||||
|
||||
### Fixes
|
||||
### Fixed
|
||||
|
||||
- Fixed several bugs in the currency code
|
||||
- Fixed some potential memory leaks
|
||||
|
10
Dockerfile
10
Dockerfile
@@ -25,12 +25,14 @@ WORKDIR /app
|
||||
RUN set -xe; \
|
||||
useradd -m nadeko; \
|
||||
apt-get update; \
|
||||
apt-get install -y libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 python3-pip sudo; \
|
||||
apt-get install -y --no-install-recommends libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 python3-pip sudo; \
|
||||
update-alternatives --install /usr/bin/python python /usr/bin/python3.9 1; \
|
||||
echo 'Defaults>nadeko env_keep+="ASPNETCORE_* DOTNET_* NadekoBot_* shard_id total_shards TZ"' > /etc/sudoers.d/nadeko; \
|
||||
pip3 install --upgrade youtube-dl; \
|
||||
apt-get remove -y python3-pip; \
|
||||
chmod +x /usr/local/bin/youtube-dl
|
||||
pip3 install --no-cache-dir --upgrade youtube-dl; \
|
||||
apt-get purge -y python3-pip; \
|
||||
chmod +x /usr/local/bin/youtube-dl; \
|
||||
apt-get autoremove -y; \
|
||||
apt-get autoclean -y
|
||||
|
||||
COPY --from=build /app ./
|
||||
COPY docker-entrypoint.sh /usr/local/sbin
|
||||
|
@@ -1,23 +1,23 @@
|
||||
## Custom Reactions / Expressions
|
||||
## Expressions
|
||||
|
||||
### Important
|
||||
|
||||
- For modifying **global** custom reactions, the ones which will work across all the servers your bot is connected to, you **must** be a Bot Owner.
|
||||
You must also use the commands for adding, deleting and listing these reactions in a direct message with the bot.
|
||||
- For modifying **local** custom reactions, the ones which will only work on the server that they are added on, it is required to have the **Administrator** permission.
|
||||
You must also use the commands for adding, deleting and listing these reactions in the server you want the custom reactions to work on.
|
||||
- For modifying **global** expressions, the ones which will work across all the servers your bot is connected to, you **must** be a Bot Owner.
|
||||
You must also use the commands for adding, deleting and listing these expressions in a direct message with the bot.
|
||||
- For modifying **local** expressions, the ones which will only work on the server that they are added on, it is required to have the **Administrator** permission.
|
||||
You must also use the commands for adding, deleting and listing these expressions in the server you want the expressions to work on.
|
||||
|
||||
### Commands and Their Use
|
||||
|
||||
| Command Name | Description | Example |
|
||||
| :----------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- |
|
||||
| `.acr` | Add a custom reaction with a trigger and a response. Running this command in a server requries the Administrator permission. Running this command in DM is Bot Owner only, and adds a new global custom reaction. | `.acr "hello" Hi there, %user%!` |
|
||||
| `.lcr` | Lists a page of global or server custom reactions (15 reactions per page). Running this command in a DM will list the global custom reactions, while running it in a server will list that server's custom reactions. | `.lcr 1` |
|
||||
| `.dcr` | Deletes a custom reaction based on the provided index. Running this command in a server requires the Administrator permission. Running this command in DM is Bot Owner only, and will delete a global custom reaction. | `.dcr 5` |
|
||||
| `.exa` | Add an expression with a trigger and a response. Running this command in a server requries the Administrator permission. Running this command in DM is Bot Owner only, and adds a new global expression. | `.exadd "hello" Hi there, %user%!` |
|
||||
| `exl` | Lists a page of global or server expression(15 expressions per page). Running this command in a DM will list the global expression, while running it in a server will list that server's expression. | `.exl 1` |
|
||||
| `.exd` | Deletes an expression based on the provided index. Running this command in a server requires the Administrator permission. Running this command in DM is Bot Owner only, and will delete a global expression. | `.exd 5` |
|
||||
|
||||
#### Now that we know the commands let's take a look at an example of adding a command with `.acr`,
|
||||
#### Now that we know the commands let's take a look at an example of adding a command with `.exa`,
|
||||
|
||||
`.acr "Nice Weather" It sure is, %user%!`
|
||||
`.exadd "Nice Weather" It sure is, %user%!`
|
||||
|
||||
This command can be split into two different arguments:
|
||||
|
||||
@@ -28,16 +28,16 @@ An important thing to note about the triger is that, to be more than one word, w
|
||||
|
||||
There's no special requirement for the formatting of the response, so we could just write it in exactly the same way we want it to respond, albeit with a placeholder - which will be explained in this next section.
|
||||
|
||||
Now, if that command was ran in a server, anyone on that server can make the bot mention them, saying `It sure is, @Username` anytime they say "Nice Weather". If the command is ran in a direct message with the bot, then the custom reaction can be used on every server the bot is connected to.
|
||||
Now, if that command was ran in a server, anyone on that server can make the bot mention them, saying `It sure is, @Username` anytime they say "Nice Weather". If the command is ran in a direct message with the bot, then the expression can be used on every server the bot is connected to.
|
||||
|
||||
### Block global Custom Reactions
|
||||
### Block global Expressions
|
||||
|
||||
If you want to disable a global custom reaction which you do not like, and you do not want to remove it, or you are not the bot owner, you can do so by adding a new Custom Reaction with the same trigger on your server, and set the response to `-`.
|
||||
If you want to disable a global expression which you do not like, and you do not want to remove it, or you are not the bot owner, you can do so by adding a new expression with the same trigger on your server, and set the response to `-`.
|
||||
|
||||
For example:
|
||||
`.acr /o/ -`
|
||||
`.exa /o/ -`
|
||||
|
||||
Now if you try to trigger `/o/`, it won't print anything even if there is a global custom reaction with the same name.
|
||||
Now if you try to trigger `/o/`, it won't print anything even if there is a global expression with the same name.
|
||||
|
||||
### Placeholders!
|
||||
|
||||
|
@@ -10,7 +10,7 @@ Donating to us also gives you the following benefits:
|
||||
- A hoisted **Donators role** in our [Discord server][discord-server]
|
||||
- Access to exclusive **#noticed** text and voice channels
|
||||
- **1000 flowers** on the public bot per dollar donated (after fees)
|
||||
- **Custom Reactions** on the public bot for [Patreon pledges][patreon] of $5 or higher
|
||||
- **Expressions** on the public bot for [Patreon pledges][patreon] of $5 or higher
|
||||
|
||||
## Patreon
|
||||
|
||||
|
@@ -17,7 +17,7 @@ It is recommended that you use **Ubuntu 20.04**, as there have been nearly no pr
|
||||
|
||||
##### Compatible operating systems:
|
||||
|
||||
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10
|
||||
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10 22.04
|
||||
- Mint: 19, 20
|
||||
- Debian: 9, 10
|
||||
- CentOS: 7
|
||||
@@ -63,9 +63,20 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
4. Run the bot (type `3` and press enter)
|
||||
5. 🎉
|
||||
|
||||
## **⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠**
|
||||
|
||||
## Linux Release
|
||||
|
||||
**⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠**
|
||||
###### Prerequisites
|
||||
|
||||
1. Nadeko requires redis to function
|
||||
- ubuntu installation command: `sudo apt-get install redis-server`
|
||||
2. Playing music requires `ffmpeg`, `libopus`, `libsodium` and `youtube-dl` (which in turn requires python3)
|
||||
- ubuntu installation command: `sudo apt-get install ffmpeg libopus0 opus-tools libopus-dev libsodium-dev -y`
|
||||
3. Make sure your python is version 3+ with `python --version`
|
||||
- if it's not, you can install python 3 and make it the default with: `sudo apt-get install python3.8 python-is-python3`
|
||||
|
||||
*You can use nadeko bash script [prerequisites installer](https://gitlab.com/Kwoth/nadeko-bash-installer/-/blob/v4/n-prereq.sh) as a reference*
|
||||
|
||||
##### Installation Instructions
|
||||
|
||||
|
@@ -63,9 +63,9 @@ You can still install them manually:
|
||||
- [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, then just move the `ffmpeg.exe` file to NadekoBot/system
|
||||
- [youtube-dl] - Click to download the file. Then put `youtube-dl.exe` in a path that's in your PATH environment variable. If you don't know what that is, then just move the `youtube-dl.exe` file to NadekoBot/system
|
||||
|
||||
### Windows From Source
|
||||
## **⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠**
|
||||
|
||||
⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠
|
||||
### Windows From Source
|
||||
|
||||
##### Prerequisites
|
||||
|
||||
|
@@ -17,8 +17,6 @@ To self-host your own Nadeko, use the guides below:
|
||||
- [:material-linux: Linux guide][linux-guide]
|
||||
- [:material-apple: Mac OS guide][macos-guide]
|
||||
|
||||
Alternatively, you may also setup the bot [from source][from-source-guide] if you want to modify the code.
|
||||
|
||||
In case you need any help, join our [Discord server][discord-server] where we may provide support.
|
||||
|
||||
---
|
||||
|
@@ -73,14 +73,14 @@ Say you want to only enable NSFW commands for a specific role, just do the follo
|
||||
2. `.rm NSFW enable Lewd`
|
||||
- Enables usage of the NSFW module for the Lewd role
|
||||
|
||||
#### How do I disable custom reactions from triggering?
|
||||
#### How do I disable Expressions from triggering?
|
||||
|
||||
If you don't want server or global custom reactions, just block the module that controls their usage:
|
||||
If you don't want server or global Expressions, just block the module that controls their usage:
|
||||
|
||||
1. `.sm ActualCustomReactions disable`
|
||||
- Disables the ActualCustomReactions module from being used
|
||||
1. `.sm ActualExpressions disable`
|
||||
- Disables the ActualExpression module from being used
|
||||
|
||||
**Note**: The `ActualCustomReactions` module controls the usage of custom reactions. The `CustomReactions` module controls commands related to custom reactions (such as `.acr`, `.lcr`, `.crca`, etc).
|
||||
**Note**: The `Expressions` module controls the usage of Expressions. The `Expressions` module controls commands related to Expressions (such as `.acr`, `.lcr`, `.crca`, etc).
|
||||
|
||||
#### I've broken permissions and am stuck, can I reset permissions?
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Placeholders
|
||||
|
||||
Placeholders are used in Quotes, Custom Reactions, Greet/Bye messages, playing statuses, and a few other places.
|
||||
Placeholders are used in Quotes, Expressions, Greet/Bye messages, playing statuses, and a few other places.
|
||||
|
||||
They can be used to make the message more user friendly, generate random numbers or pictures, etc.
|
||||
|
||||
@@ -88,7 +88,7 @@ Some features have their own specific placeholders which are noted in that featu
|
||||
### Miscellaneous placeholders
|
||||
|
||||
- `%rngX-Y%` - Returns a random number between X and Y
|
||||
- `%target%` - Returns anything the user has written after the trigger (only works on custom reactions)
|
||||
- `%img:stuff%` - Returns an `imgur.com` search for "stuff" (only works on custom reactions)
|
||||
- `%target%` - Returns anything the user has written after the trigger (only works on Expressions)
|
||||
- `%img:stuff%` - Returns an `imgur.com` search for "stuff" (only works on Expressions)
|
||||
|
||||

|
||||
|
@@ -92,4 +92,3 @@ nav:
|
||||
- medusa/snek-lifecycle.md
|
||||
- Contribution Guide: contribution-guide.md
|
||||
- Donate: donate.md
|
||||
- License: license.md
|
||||
|
@@ -5,7 +5,7 @@ namespace NadekoBot;
|
||||
public interface IEmbedBuilder
|
||||
{
|
||||
IEmbedBuilder WithDescription(string? desc);
|
||||
IEmbedBuilder WithTitle(string title);
|
||||
IEmbedBuilder WithTitle(string? title);
|
||||
IEmbedBuilder AddField(string title, object value, bool isInline = false);
|
||||
IEmbedBuilder WithFooter(string text, string? iconUrl = null);
|
||||
IEmbedBuilder WithAuthor(string name, string? iconUrl = null, string? url = null);
|
||||
|
@@ -9,12 +9,11 @@
|
||||
<RootNamespace>Nadeko.Snake</RootNamespace>
|
||||
|
||||
<Authors>The NadekoBot Team</Authors>
|
||||
<Version>1.0.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net.Core" Version="3.5.0" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Discord.Net.Core" Version="3.6.1" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -9,10 +9,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.44.0" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.45.0" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -97,13 +97,12 @@ public class CmdAttribute : System.Attribute
|
||||
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);
|
||||
Console.WriteLine($"Error writing source file {name}\n" + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -62,6 +62,7 @@ namespace NadekoBot.Generators
|
||||
sw.WriteLine("{");
|
||||
sw.Indent++;
|
||||
|
||||
var typedParamStrings = new List<string>(10);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var matches = Regex.Matches(field.Value, @"{(?<num>\d)[}:]");
|
||||
@@ -71,20 +72,30 @@ namespace NadekoBot.Generators
|
||||
max = Math.Max(max, int.Parse(match.Groups["num"].Value) + 1);
|
||||
}
|
||||
|
||||
List<string> typedParamStrings = new List<string>();
|
||||
var paramStrings = string.Empty;
|
||||
typedParamStrings.Clear();
|
||||
var typeParams = new string[max];
|
||||
var passedParamString = string.Empty;
|
||||
for (var i = 0; i < max; i++)
|
||||
{
|
||||
typedParamStrings.Add($"object p{i}");
|
||||
paramStrings += $", p{i}";
|
||||
typedParamStrings.Add($"in T{i} p{i}");
|
||||
passedParamString += $", p{i}";
|
||||
typeParams[i] = $"T{i}";
|
||||
}
|
||||
|
||||
|
||||
var sig = string.Empty;
|
||||
if(max > 0)
|
||||
var typeParamStr = string.Empty;
|
||||
if (max > 0)
|
||||
{
|
||||
sig = $"({string.Join(", ", typedParamStrings)})";
|
||||
|
||||
sw.WriteLine($"public static LocStr {field.Name}{sig} => new LocStr(\"{field.Name}\"{paramStrings});");
|
||||
typeParamStr = $"<{string.Join(", ", typeParams)}>";
|
||||
}
|
||||
|
||||
sw.WriteLine("public static LocStr {0}{1}{2} => new LocStr(\"{3}\"{4});",
|
||||
field.Name,
|
||||
typeParamStr,
|
||||
sig,
|
||||
field.Name,
|
||||
passedParamString);
|
||||
}
|
||||
|
||||
sw.Indent--;
|
||||
|
@@ -21,7 +21,7 @@ namespace NadekoBot.Tests
|
||||
var stringsSource = new LocalFileStringsSource(
|
||||
responsesPath,
|
||||
commandsPath);
|
||||
var strings = new LocalBotStringsProvider(stringsSource);
|
||||
var strings = new MemoryBotStringsProvider(stringsSource);
|
||||
|
||||
var culture = new CultureInfo("en-US");
|
||||
|
||||
|
93
src/NadekoBot.Tests/ConcurrentHashSetTests.cs
Normal file
93
src/NadekoBot.Tests/ConcurrentHashSetTests.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace NadekoBot.Tests;
|
||||
|
||||
public class ConcurrentHashSetTests
|
||||
{
|
||||
private ConcurrentHashSet<(int?, int?)> _set;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_set = new();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddTest()
|
||||
{
|
||||
var result = _set.Add((1, 2));
|
||||
|
||||
Assert.AreEqual(true, result);
|
||||
|
||||
result = _set.Add((1, 2));
|
||||
|
||||
Assert.AreEqual(false, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryRemoveTest()
|
||||
{
|
||||
_set.Add((1, 2));
|
||||
var result = _set.TryRemove((1, 2));
|
||||
|
||||
Assert.AreEqual(true, result);
|
||||
|
||||
result = _set.TryRemove((1, 2));
|
||||
Assert.AreEqual(false, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CountTest()
|
||||
{
|
||||
_set.Add((1, 2)); // 1
|
||||
_set.Add((1, 2)); // 1
|
||||
|
||||
_set.Add((2, 2)); // 2
|
||||
|
||||
_set.Add((3, 2)); // 3
|
||||
_set.Add((3, 2)); // 3
|
||||
|
||||
Assert.AreEqual(3, _set.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClearTest()
|
||||
{
|
||||
_set.Add((1, 2));
|
||||
_set.Add((1, 3));
|
||||
_set.Add((1, 4));
|
||||
|
||||
_set.Clear();
|
||||
|
||||
Assert.AreEqual(0, _set.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ContainsTest()
|
||||
{
|
||||
_set.Add((1, 2));
|
||||
_set.Add((3, 2));
|
||||
|
||||
Assert.AreEqual(true, _set.Contains((1, 2)));
|
||||
Assert.AreEqual(true, _set.Contains((3, 2)));
|
||||
Assert.AreEqual(false, _set.Contains((2, 1)));
|
||||
Assert.AreEqual(false, _set.Contains((2, 3)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveWhereTest()
|
||||
{
|
||||
_set.Add((1, 2));
|
||||
_set.Add((1, 3));
|
||||
_set.Add((1, 4));
|
||||
_set.Add((2, 5));
|
||||
|
||||
// remove tuples which have even second item
|
||||
_set.RemoveWhere(static x => x.Item2 % 2 == 0);
|
||||
|
||||
Assert.AreEqual(2, _set.Count);
|
||||
Assert.AreEqual(true, _set.Contains((1, 3)));
|
||||
Assert.AreEqual(true, _set.Contains((2, 5)));
|
||||
}
|
||||
}
|
@@ -8,9 +8,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -4,9 +4,11 @@ using NadekoBot.Common.Configs;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Administration;
|
||||
using NadekoBot.Modules.Utility;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using RunMode = Discord.Commands.RunMode;
|
||||
|
||||
@@ -67,12 +69,13 @@ public sealed class Bot
|
||||
? GatewayIntents.All
|
||||
: GatewayIntents.AllUnprivileged,
|
||||
LogGatewayIntentWarnings = false,
|
||||
FormatUsersInBidirectionalUnicode = false,
|
||||
});
|
||||
|
||||
_commandService = new(new()
|
||||
{
|
||||
CaseSensitiveCommands = false,
|
||||
DefaultRunMode = RunMode.Sync
|
||||
DefaultRunMode = RunMode.Sync,
|
||||
});
|
||||
|
||||
// _interactionService = new(Client.Rest);
|
||||
@@ -99,20 +102,20 @@ public sealed class Bot
|
||||
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();
|
||||
.AddMusic()
|
||||
// cache
|
||||
.AddCache(_creds);
|
||||
|
||||
// admin
|
||||
#if GLOBAL_NADEKO
|
||||
svcs.AddSingleton<ILogCommandService, DummyLogCommandService>();
|
||||
@@ -124,6 +127,12 @@ public sealed class Bot
|
||||
{
|
||||
AllowAutoRedirect = false
|
||||
});
|
||||
|
||||
svcs.AddHttpClient("google:search")
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
|
||||
});
|
||||
|
||||
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
|
||||
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
|
||||
@@ -134,13 +143,6 @@ public sealed class Bot
|
||||
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
|
||||
}
|
||||
|
||||
svcs.AddSingleton<RedisLocalDataCache>()
|
||||
.AddSingleton<ILocalDataCache>(x => x.GetRequiredService<RedisLocalDataCache>())
|
||||
.AddSingleton<RedisImagesCache>()
|
||||
.AddSingleton<IImageCache>(x => x.GetRequiredService<RedisImagesCache>())
|
||||
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RedisImagesCache>())
|
||||
.AddSingleton<IDataCache, RedisCache>();
|
||||
|
||||
svcs.Scan(scan => scan.FromAssemblyOf<IReadyExecutor>()
|
||||
.AddClasses(classes => classes.AssignableToAny(
|
||||
// services
|
||||
@@ -163,6 +165,7 @@ public sealed class Bot
|
||||
//initialize Services
|
||||
Services = svcs.BuildServiceProvider();
|
||||
Services.GetRequiredService<IBehaviorHandler>().Initialize();
|
||||
Services.GetRequiredService<CurrencyRewardService>();
|
||||
|
||||
if (Client.ShardId == 0)
|
||||
ApplyConfigMigrations();
|
||||
|
@@ -0,0 +1,11 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Classed marked with this attribute will not be added to the service provider
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DontAddToIocContainerAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
@@ -20,11 +20,19 @@ public sealed class NoPublicBotAttribute : PreconditionAttribute
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Classed marked with this attribute will not be added to the service provider
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DontAddToIocContainerAttribute : Attribute
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||
[SuppressMessage("Style", "IDE0022:Use expression body for methods")]
|
||||
public sealed class OnlyPublicBotAttribute : PreconditionAttribute
|
||||
{
|
||||
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||
ICommandContext context,
|
||||
CommandInfo command,
|
||||
IServiceProvider services)
|
||||
{
|
||||
#if GLOBAL_NADEKO || DEBUG
|
||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||
#else
|
||||
return Task.FromResult(PreconditionResult.FromError("Only available on the public bot."));
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -15,22 +15,24 @@ public sealed class RatelimitAttribute : PreconditionAttribute
|
||||
Seconds = seconds;
|
||||
}
|
||||
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||
public override async Task<PreconditionResult> CheckPermissionsAsync(
|
||||
ICommandContext context,
|
||||
CommandInfo command,
|
||||
IServiceProvider services)
|
||||
{
|
||||
if (Seconds == 0)
|
||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||
return PreconditionResult.FromSuccess();
|
||||
|
||||
var cache = services.GetRequiredService<IDataCache>();
|
||||
var rem = cache.TryAddRatelimit(context.User.Id, command.Name, Seconds);
|
||||
var cache = services.GetRequiredService<IBotCache>();
|
||||
var rem = await cache.GetRatelimitAsync(
|
||||
new($"precondition:{context.User.Id}:{command.Name}"),
|
||||
Seconds.Seconds());
|
||||
|
||||
if (rem is null)
|
||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||
return PreconditionResult.FromSuccess();
|
||||
|
||||
var msgContent = $"You can use this command again in {rem.Value.TotalSeconds:F1}s.";
|
||||
|
||||
return Task.FromResult(PreconditionResult.FromError(msgContent));
|
||||
return PreconditionResult.FromError(msgContent);
|
||||
}
|
||||
}
|
46
src/NadekoBot/Common/Cache/BotCacheExtensions.cs
Normal file
46
src/NadekoBot/Common/Cache/BotCacheExtensions.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using OneOf;
|
||||
using OneOf.Types;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public static class BotCacheExtensions
|
||||
{
|
||||
public static async ValueTask<T?> GetOrDefaultAsync<T>(this IBotCache cache, TypedKey<T> key)
|
||||
{
|
||||
var result = await cache.GetAsync(key);
|
||||
if (result.TryGetValue(out var val))
|
||||
return val;
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static TypedKey<byte[]> GetImgKey(Uri uri)
|
||||
=> new($"image:{uri}");
|
||||
|
||||
public static ValueTask SetImageDataAsync(this IBotCache c, string key, byte[] data)
|
||||
=> c.SetImageDataAsync(new Uri(key), data);
|
||||
public static async ValueTask SetImageDataAsync(this IBotCache c, Uri key, byte[] data)
|
||||
=> await c.AddAsync(GetImgKey(key), data, expiry: TimeSpan.FromHours(48));
|
||||
|
||||
public static async ValueTask<OneOf<byte[], None>> GetImageDataAsync(this IBotCache c, Uri key)
|
||||
=> await c.GetAsync(GetImgKey(key));
|
||||
|
||||
public static async Task<TimeSpan?> GetRatelimitAsync(
|
||||
this IBotCache c,
|
||||
TypedKey<long> key,
|
||||
TimeSpan length)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var nowB = now.ToBinary();
|
||||
|
||||
var cachedValue = await c.GetOrAddAsync(key,
|
||||
() => Task.FromResult(now.ToBinary()),
|
||||
expiry: length);
|
||||
|
||||
if (cachedValue == nowB)
|
||||
return null;
|
||||
|
||||
var diff = now - DateTime.FromBinary(cachedValue);
|
||||
return length - diff;
|
||||
}
|
||||
}
|
47
src/NadekoBot/Common/Cache/IBotCache.cs
Normal file
47
src/NadekoBot/Common/Cache/IBotCache.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using OneOf;
|
||||
using OneOf.Types;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public interface IBotCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds an item to the cache
|
||||
/// </summary>
|
||||
/// <param name="key">Key to add</param>
|
||||
/// <param name="value">Value to add to the cache</param>
|
||||
/// <param name="expiry">Optional expiry</param>
|
||||
/// <param name="overwrite">Whether old value should be overwritten</param>
|
||||
/// <typeparam name="T">Type of the value</typeparam>
|
||||
/// <returns>Returns whether add was sucessful. Always true unless ovewrite = false</returns>
|
||||
ValueTask<bool> AddAsync<T>(TypedKey<T> key, T value, TimeSpan? expiry = null, bool overwrite = true);
|
||||
|
||||
/// <summary>
|
||||
/// Get an element from the cache
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <typeparam name="T">Type of the value</typeparam>
|
||||
/// <returns>Either a value or <see cref="None"/></returns>
|
||||
ValueTask<OneOf<T, None>> GetAsync<T>(TypedKey<T> key);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a key from the cache
|
||||
/// </summary>
|
||||
/// <param name="key">Key to remove</param>
|
||||
/// <typeparam name="T">Type of the value</typeparam>
|
||||
/// <returns>Whether there was item</returns>
|
||||
ValueTask<bool> RemoveAsync<T>(TypedKey<T> key);
|
||||
|
||||
/// <summary>
|
||||
/// Get the key if it exists or add a new one
|
||||
/// </summary>
|
||||
/// <param name="key">Key to get and potentially add</param>
|
||||
/// <param name="createFactory">Value creation factory</param>
|
||||
/// <param name="expiry">Optional expiry</param>
|
||||
/// <typeparam name="T">Type of the value</typeparam>
|
||||
/// <returns>The retrieved or newly added value</returns>
|
||||
ValueTask<T?> GetOrAddAsync<T>(
|
||||
TypedKey<T> key,
|
||||
Func<Task<T?>> createFactory,
|
||||
TimeSpan? expiry = null);
|
||||
}
|
71
src/NadekoBot/Common/Cache/MemoryBotCache.cs
Normal file
71
src/NadekoBot/Common/Cache/MemoryBotCache.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using OneOf;
|
||||
using OneOf.Types;
|
||||
|
||||
// ReSharper disable InconsistentlySynchronizedField
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public sealed class MemoryBotCache : IBotCache
|
||||
{
|
||||
// needed for overwrites and Delete return value
|
||||
private readonly object _cacheLock = new object();
|
||||
private readonly MemoryCache _cache;
|
||||
|
||||
public MemoryBotCache()
|
||||
{
|
||||
_cache = new MemoryCache(new MemoryCacheOptions());
|
||||
}
|
||||
|
||||
public ValueTask<bool> AddAsync<T>(TypedKey<T> key, T value, TimeSpan? expiry = null, bool overwrite = true)
|
||||
{
|
||||
if (overwrite)
|
||||
{
|
||||
using var item = _cache.CreateEntry(key.Key);
|
||||
item.Value = value;
|
||||
item.AbsoluteExpirationRelativeToNow = expiry;
|
||||
return new(true);
|
||||
}
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (_cache.TryGetValue(key.Key, out var old) && old is not null)
|
||||
return new(false);
|
||||
|
||||
using var item = _cache.CreateEntry(key.Key);
|
||||
item.Value = value;
|
||||
item.AbsoluteExpirationRelativeToNow = expiry;
|
||||
return new(true);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<T?> GetOrAddAsync<T>(
|
||||
TypedKey<T> key,
|
||||
Func<Task<T?>> createFactory,
|
||||
TimeSpan? expiry = null)
|
||||
=> await _cache.GetOrCreateAsync(key.Key,
|
||||
async ce =>
|
||||
{
|
||||
ce.AbsoluteExpirationRelativeToNow = expiry;
|
||||
var val = await createFactory();
|
||||
return val;
|
||||
});
|
||||
|
||||
public ValueTask<OneOf<T, None>> GetAsync<T>(TypedKey<T> key)
|
||||
{
|
||||
if (!_cache.TryGetValue(key.Key, out var val) || val is null)
|
||||
return new(new None());
|
||||
|
||||
return new((T)val);
|
||||
}
|
||||
|
||||
public ValueTask<bool> RemoveAsync<T>(TypedKey<T> key)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
var toReturn = _cache.TryGetValue(key.Key, out var old ) && old is not null;
|
||||
_cache.Remove(key.Key);
|
||||
return new(toReturn);
|
||||
}
|
||||
}
|
||||
}
|
119
src/NadekoBot/Common/Cache/RedisBotCache.cs
Normal file
119
src/NadekoBot/Common/Cache/RedisBotCache.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using OneOf;
|
||||
using OneOf.Types;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public sealed class RedisBotCache : IBotCache
|
||||
{
|
||||
private static readonly Type[] _supportedTypes = new []
|
||||
{
|
||||
typeof(bool), typeof(int), typeof(uint), typeof(long),
|
||||
typeof(ulong), typeof(float), typeof(double),
|
||||
typeof(string), typeof(byte[]), typeof(ReadOnlyMemory<byte>), typeof(Memory<byte>),
|
||||
typeof(RedisValue),
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions _opts = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
AllowTrailingCommas = true,
|
||||
IgnoreReadOnlyProperties = false,
|
||||
};
|
||||
private readonly ConnectionMultiplexer _conn;
|
||||
|
||||
public RedisBotCache(ConnectionMultiplexer conn)
|
||||
{
|
||||
_conn = conn;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> AddAsync<T>(TypedKey<T> key, T value, TimeSpan? expiry = null, bool overwrite = true)
|
||||
{
|
||||
// if a null value is passed, remove the key
|
||||
if (value is null)
|
||||
{
|
||||
await RemoveAsync(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
var db = _conn.GetDatabase();
|
||||
RedisValue val = IsSupportedType(typeof(T))
|
||||
? RedisValue.Unbox(value)
|
||||
: JsonSerializer.Serialize(value, _opts);
|
||||
|
||||
var success = await db.StringSetAsync(key.Key,
|
||||
val,
|
||||
expiry: expiry,
|
||||
when: overwrite ? When.Always : When.NotExists);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool IsSupportedType(Type type)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var typeDef = type.GetGenericTypeDefinition();
|
||||
if (typeDef == typeof(Nullable<>))
|
||||
return IsSupportedType(type.GenericTypeArguments[0]);
|
||||
}
|
||||
|
||||
foreach (var t in _supportedTypes)
|
||||
{
|
||||
if (type == t)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async ValueTask<OneOf<T, None>> GetAsync<T>(TypedKey<T> key)
|
||||
{
|
||||
var db = _conn.GetDatabase();
|
||||
var val = await db.StringGetAsync(key.Key);
|
||||
if (val == default)
|
||||
return new None();
|
||||
|
||||
if (IsSupportedType(typeof(T)))
|
||||
return (T)((IConvertible)val).ToType(typeof(T), null);
|
||||
|
||||
return JsonSerializer.Deserialize<T>(val.ToString(), _opts)!;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> RemoveAsync<T>(TypedKey<T> key)
|
||||
{
|
||||
var db = _conn.GetDatabase();
|
||||
|
||||
return await db.KeyDeleteAsync(key.Key);
|
||||
}
|
||||
|
||||
public async ValueTask<T?> GetOrAddAsync<T>(TypedKey<T> key, Func<Task<T?>> createFactory, TimeSpan? expiry = null)
|
||||
{
|
||||
var result = await GetAsync(key);
|
||||
|
||||
return await result.Match<Task<T?>>(
|
||||
v => Task.FromResult<T?>(v),
|
||||
async _ =>
|
||||
{
|
||||
var factoryValue = await createFactory();
|
||||
|
||||
if (factoryValue is null)
|
||||
return default;
|
||||
|
||||
await AddAsync(key, factoryValue, expiry);
|
||||
|
||||
// get again to make sure it's the cached value
|
||||
// and not the late factory value, in case there's a race condition
|
||||
|
||||
var newResult = await GetAsync(key);
|
||||
|
||||
// it's fine to do this, it should blow up if something went wrong.
|
||||
return newResult.Match<T?>(
|
||||
v => v,
|
||||
_ => default);
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
#nullable disable
|
||||
#nullable enable
|
||||
#pragma warning disable
|
||||
// License MIT
|
||||
// Source: https://github.com/i3arnon/ConcurrentHashSet
|
||||
@@ -7,433 +7,23 @@ using System.Diagnostics;
|
||||
|
||||
namespace System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a thread-safe hash-based unique collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the items in the collection.</typeparam>
|
||||
/// <remarks>
|
||||
/// All public members of <see cref="ConcurrentHashSet{T}" /> are thread-safe and may be used
|
||||
/// concurrently from multiple threads.
|
||||
/// </remarks>
|
||||
[DebuggerDisplay("Count = {Count}")]
|
||||
[DebuggerDisplay("{_backingStore.Count}")]
|
||||
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T>
|
||||
{
|
||||
private const int DEFAULT_CAPACITY = 31;
|
||||
private const int MAX_LOCK_NUMBER = 1024;
|
||||
|
||||
private static int DefaultConcurrencyLevel
|
||||
=> PlatformHelper.ProcessorCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether the <see cref="ConcurrentHashSet{T}" /> is empty.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// true if the <see cref="ConcurrentHashSet{T}" /> is empty; otherwise,
|
||||
/// false.
|
||||
/// </value>
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
var acquiredLocks = 0;
|
||||
try
|
||||
{
|
||||
AcquireAllLocks(ref acquiredLocks);
|
||||
|
||||
for (var i = 0; i < tables.CountPerLock.Length; i++)
|
||||
{
|
||||
if (tables.CountPerLock[i] != 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLocks(0, acquiredLocks);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<T>.IsReadOnly
|
||||
=> false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items contained in the
|
||||
/// <see
|
||||
/// cref="ConcurrentHashSet{T}" />
|
||||
/// .
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The number of items contained in the
|
||||
/// <see
|
||||
/// cref="ConcurrentHashSet{T}" />
|
||||
/// .
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Count has snapshot semantics and represents the number of items in the
|
||||
/// <see
|
||||
/// cref="ConcurrentHashSet{T}" />
|
||||
/// at the moment when Count was accessed.
|
||||
/// </remarks>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = 0;
|
||||
var acquiredLocks = 0;
|
||||
try
|
||||
{
|
||||
AcquireAllLocks(ref acquiredLocks);
|
||||
|
||||
for (var i = 0; i < tables.CountPerLock.Length; i++)
|
||||
count += tables.CountPerLock[i];
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLocks(0, acquiredLocks);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IEqualityComparer<T> _comparer;
|
||||
private readonly bool _growLockArray;
|
||||
|
||||
private int budget;
|
||||
private volatile Tables tables;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the
|
||||
/// <see
|
||||
/// cref="ConcurrentHashSet{T}" />
|
||||
/// class that is empty, has the default concurrency level, has the default initial capacity, and
|
||||
/// uses the default comparer for the item type.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<T, bool> _backingStore;
|
||||
|
||||
public ConcurrentHashSet()
|
||||
: this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, EqualityComparer<T>.Default)
|
||||
{
|
||||
}
|
||||
=> _backingStore = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the
|
||||
/// <see
|
||||
/// cref="ConcurrentHashSet{T}" />
|
||||
/// class that is empty, has the specified concurrency level and capacity, and uses the default
|
||||
/// comparer for the item type.
|
||||
/// </summary>
|
||||
/// <param name="concurrencyLevel">
|
||||
/// The estimated number of threads that will update the
|
||||
/// <see cref="ConcurrentHashSet{T}" /> concurrently.
|
||||
/// </param>
|
||||
/// <param name="capacity">
|
||||
/// The initial number of elements that the
|
||||
/// <see
|
||||
/// cref="ConcurrentHashSet{T}" />
|
||||
/// can contain.
|
||||
/// </param>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||
/// <paramref name="concurrencyLevel" /> is
|
||||
/// less than 1.
|
||||
/// </exception>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||
/// <paramref name="capacity" /> is less than
|
||||
/// 0.
|
||||
/// </exception>
|
||||
public ConcurrentHashSet(int concurrencyLevel, int capacity)
|
||||
: this(concurrencyLevel, capacity, false, EqualityComparer<T>.Default)
|
||||
{
|
||||
}
|
||||
public ConcurrentHashSet(IEnumerable<T> values, IEqualityComparer<T>? comparer = null)
|
||||
=> _backingStore = new(values.Select(x => new KeyValuePair<T, bool>(x, true)), comparer);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" />
|
||||
/// class that contains elements copied from the specified
|
||||
/// <see
|
||||
/// cref="T:System.Collections.IEnumerable{T}" />
|
||||
/// , has the default concurrency
|
||||
/// level, has the default initial capacity, and uses the default comparer for the item type.
|
||||
/// </summary>
|
||||
/// <param name="collection">
|
||||
/// The
|
||||
/// <see
|
||||
/// cref="T:System.Collections.IEnumerable{T}" />
|
||||
/// whose elements are copied to
|
||||
/// the new
|
||||
/// <see cref="ConcurrentHashSet{T}" />.
|
||||
/// </param>
|
||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="collection" /> is a null reference.</exception>
|
||||
public ConcurrentHashSet(IEnumerable<T> collection)
|
||||
: this(collection, EqualityComparer<T>.Default)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" />
|
||||
/// class that is empty, has the specified concurrency level and capacity, and uses the specified
|
||||
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />.
|
||||
/// </summary>
|
||||
/// <param name="comparer">
|
||||
/// The <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />
|
||||
/// implementation to use when comparing items.
|
||||
/// </param>
|
||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer" /> is a null reference.</exception>
|
||||
public ConcurrentHashSet(IEqualityComparer<T> comparer)
|
||||
: this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, comparer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" />
|
||||
/// class that contains elements copied from the specified
|
||||
/// <see
|
||||
/// cref="T:System.Collections.IEnumerable" />
|
||||
/// , has the default concurrency level, has the default
|
||||
/// initial capacity, and uses the specified
|
||||
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />.
|
||||
/// </summary>
|
||||
/// <param name="collection">
|
||||
/// The
|
||||
/// <see
|
||||
/// cref="T:System.Collections.IEnumerable{T}" />
|
||||
/// whose elements are copied to
|
||||
/// the new
|
||||
/// <see cref="ConcurrentHashSet{T}" />.
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// The <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />
|
||||
/// implementation to use when comparing items.
|
||||
/// </param>
|
||||
/// <exception cref="T:System.ArgumentNullException">
|
||||
/// <paramref name="collection" /> is a null reference
|
||||
/// (Nothing in Visual Basic). -or-
|
||||
/// <paramref name="comparer" /> is a null reference (Nothing in Visual Basic).
|
||||
/// </exception>
|
||||
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
|
||||
: this(comparer)
|
||||
{
|
||||
if (collection is null)
|
||||
throw new ArgumentNullException(nameof(collection));
|
||||
|
||||
InitializeFromCollection(collection);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" />
|
||||
/// class that contains elements copied from the specified <see cref="T:System.Collections.IEnumerable" />,
|
||||
/// has the specified concurrency level, has the specified initial capacity, and uses the specified
|
||||
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />.
|
||||
/// </summary>
|
||||
/// <param name="concurrencyLevel">
|
||||
/// The estimated number of threads that will update the
|
||||
/// <see cref="ConcurrentHashSet{T}" /> concurrently.
|
||||
/// </param>
|
||||
/// <param name="collection">
|
||||
/// The <see cref="T:System.Collections.IEnumerable{T}" /> whose elements are copied to the new
|
||||
/// <see cref="ConcurrentHashSet{T}" />.
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// The <see cref="T:System.Collections.Generic.IEqualityComparer{T}" /> implementation to use
|
||||
/// when comparing items.
|
||||
/// </param>
|
||||
/// <exception cref="T:System.ArgumentNullException">
|
||||
/// <paramref name="collection" /> is a null reference.
|
||||
/// -or-
|
||||
/// <paramref name="comparer" /> is a null reference.
|
||||
/// </exception>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||
/// <paramref name="concurrencyLevel" /> is less than 1.
|
||||
/// </exception>
|
||||
public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T> comparer)
|
||||
: this(concurrencyLevel, DEFAULT_CAPACITY, false, comparer)
|
||||
{
|
||||
if (collection is null)
|
||||
throw new ArgumentNullException(nameof(collection));
|
||||
if (comparer is null)
|
||||
throw new ArgumentNullException(nameof(comparer));
|
||||
|
||||
InitializeFromCollection(collection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" />
|
||||
/// class that is empty, has the specified concurrency level, has the specified initial capacity, and
|
||||
/// uses the specified <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />.
|
||||
/// </summary>
|
||||
/// <param name="concurrencyLevel">
|
||||
/// The estimated number of threads that will update the
|
||||
/// <see cref="ConcurrentHashSet{T}" /> concurrently.
|
||||
/// </param>
|
||||
/// <param name="capacity">
|
||||
/// The initial number of elements that the
|
||||
/// <see
|
||||
/// cref="ConcurrentHashSet{T}" />
|
||||
/// can contain.
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// The <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />
|
||||
/// implementation to use when comparing items.
|
||||
/// </param>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||
/// <paramref name="concurrencyLevel" /> is less than 1. -or-
|
||||
/// <paramref name="capacity" /> is less than 0.
|
||||
/// </exception>
|
||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer" /> is a null reference.</exception>
|
||||
public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer<T> comparer)
|
||||
: this(concurrencyLevel, capacity, false, comparer)
|
||||
{
|
||||
}
|
||||
|
||||
private ConcurrentHashSet(
|
||||
int concurrencyLevel,
|
||||
int capacity,
|
||||
bool growLockArray,
|
||||
IEqualityComparer<T> comparer)
|
||||
{
|
||||
if (concurrencyLevel < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(concurrencyLevel));
|
||||
if (capacity < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
|
||||
// The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard
|
||||
// any buckets.
|
||||
if (capacity < concurrencyLevel)
|
||||
capacity = concurrencyLevel;
|
||||
|
||||
var locks = new object[concurrencyLevel];
|
||||
for (var i = 0; i < locks.Length; i++)
|
||||
locks[i] = new();
|
||||
|
||||
var countPerLock = new int[locks.Length];
|
||||
var buckets = new Node[capacity];
|
||||
tables = new(buckets, locks, countPerLock);
|
||||
|
||||
_growLockArray = growLockArray;
|
||||
budget = buckets.Length / locks.Length;
|
||||
_comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the <see cref="ConcurrentHashSet{T}" />.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
var locksAcquired = 0;
|
||||
try
|
||||
{
|
||||
AcquireAllLocks(ref locksAcquired);
|
||||
|
||||
var newTables = new Tables(new Node[DEFAULT_CAPACITY], tables.Locks, new int[tables.CountPerLock.Length]);
|
||||
tables = newTables;
|
||||
budget = Math.Max(1, newTables.Buckets.Length / newTables.Locks.Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLocks(0, locksAcquired);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the <see cref="ConcurrentHashSet{T}" /> contains the specified
|
||||
/// item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to locate in the <see cref="ConcurrentHashSet{T}" />.</param>
|
||||
/// <returns>true if the <see cref="ConcurrentHashSet{T}" /> contains the item; otherwise, false.</returns>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
var hashcode = _comparer.GetHashCode(item!);
|
||||
|
||||
// We must capture the _buckets field in a local variable. It is set to a new table on each table resize.
|
||||
var localTables = tables;
|
||||
|
||||
var bucketNo = GetBucket(hashcode, localTables.Buckets.Length);
|
||||
|
||||
// We can get away w/out a lock here.
|
||||
// The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i].
|
||||
var current = Volatile.Read(ref localTables.Buckets[bucketNo]);
|
||||
|
||||
while (current is not null)
|
||||
{
|
||||
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
|
||||
return true;
|
||||
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
=> Add(item);
|
||||
|
||||
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
if (array is null)
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
if (arrayIndex < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
|
||||
var locksAcquired = 0;
|
||||
try
|
||||
{
|
||||
AcquireAllLocks(ref locksAcquired);
|
||||
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < tables.Locks.Length && count >= 0; i++)
|
||||
count += tables.CountPerLock[i];
|
||||
|
||||
if (array.Length - count < arrayIndex || count < 0) //"count" itself or "count + arrayIndex" can overflow
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The index is equal to or greater than the length of the array, or the number of elements in the set is greater than the available space from index to the end of the destination array.");
|
||||
}
|
||||
|
||||
CopyToItems(array, arrayIndex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLocks(0, locksAcquired);
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item)
|
||||
=> TryRemove(item);
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
=> _backingStore.Keys.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the
|
||||
/// <see
|
||||
/// cref="ConcurrentHashSet{T}" />
|
||||
/// .
|
||||
/// </summary>
|
||||
/// <returns>An enumerator for the <see cref="ConcurrentHashSet{T}" />.</returns>
|
||||
/// <remarks>
|
||||
/// The enumerator returned from the collection is safe to use concurrently with
|
||||
/// reads and writes to the collection, however it does not represent a moment-in-time snapshot
|
||||
/// of the collection. The contents exposed through the enumerator may contain modifications
|
||||
/// made to the collection after <see cref="GetEnumerator" /> was called.
|
||||
/// </remarks>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
var buckets = tables.Buckets;
|
||||
|
||||
for (var i = 0; i < buckets.Length; i++)
|
||||
{
|
||||
// The Volatile.Read ensures that the load of the fields of 'current' doesn't move before the load from buckets[i].
|
||||
var current = Volatile.Read(ref buckets[i]);
|
||||
|
||||
while (current is not null)
|
||||
{
|
||||
yield return current.Item;
|
||||
current = current.Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified item to the <see cref="ConcurrentHashSet{T}" />.
|
||||
/// </summary>
|
||||
@@ -447,375 +37,57 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
||||
/// contains too many items.
|
||||
/// </exception>
|
||||
public bool Add(T item)
|
||||
=> AddInternal(item, _comparer.GetHashCode(item), true);
|
||||
=> _backingStore.TryAdd(item, true);
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
=> Add(item);
|
||||
|
||||
public void Clear()
|
||||
=> _backingStore.Clear();
|
||||
|
||||
public bool Contains(T item)
|
||||
=> _backingStore.ContainsKey(item);
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
|
||||
if (arrayIndex < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
|
||||
if (arrayIndex >= array.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
|
||||
CopyToInternal(array, arrayIndex);
|
||||
}
|
||||
|
||||
private void CopyToInternal(T[] array, int arrayIndex)
|
||||
{
|
||||
var len = array.Length;
|
||||
foreach (var (k, _) in _backingStore)
|
||||
{
|
||||
if (arrayIndex >= len)
|
||||
throw new IndexOutOfRangeException(nameof(arrayIndex));
|
||||
|
||||
array[arrayIndex++] = k;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item)
|
||||
=> TryRemove(item);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove the item from the <see cref="ConcurrentHashSet{T}" />.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
/// <returns>true if an item was removed successfully; otherwise, false.</returns>
|
||||
public bool TryRemove(T item)
|
||||
=> _backingStore.TryRemove(item, out _);
|
||||
|
||||
public void RemoveWhere(Func<T, bool> predicate)
|
||||
{
|
||||
var hashcode = _comparer.GetHashCode(item);
|
||||
while (true)
|
||||
{
|
||||
var localTables = tables;
|
||||
|
||||
GetBucketAndLockNo(hashcode,
|
||||
out var bucketNo,
|
||||
out var lockNo,
|
||||
localTables.Buckets.Length,
|
||||
localTables.Locks.Length);
|
||||
|
||||
lock (localTables.Locks[lockNo])
|
||||
{
|
||||
// If the table just got resized, we may not be holding the right lock, and must retry.
|
||||
// This should be a rare occurrence.
|
||||
if (localTables != tables)
|
||||
continue;
|
||||
|
||||
Node previous = null;
|
||||
for (var current = localTables.Buckets[bucketNo]; current is not null; current = current.Next)
|
||||
{
|
||||
Debug.Assert((previous is null && current == localTables.Buckets[bucketNo])
|
||||
|| previous!.Next == current);
|
||||
|
||||
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
|
||||
{
|
||||
if (previous is null)
|
||||
Volatile.Write(ref localTables.Buckets[bucketNo], current.Next);
|
||||
else
|
||||
previous.Next = current.Next;
|
||||
|
||||
localTables.CountPerLock[lockNo]--;
|
||||
return true;
|
||||
}
|
||||
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
foreach (var elem in this.Where(predicate))
|
||||
TryRemove(elem);
|
||||
}
|
||||
|
||||
private void InitializeFromCollection(IEnumerable<T> collection)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
AddInternal(item, _comparer.GetHashCode(item), false);
|
||||
public int Count
|
||||
=> _backingStore.Count;
|
||||
|
||||
if (budget == 0)
|
||||
budget = tables.Buckets.Length / tables.Locks.Length;
|
||||
}
|
||||
|
||||
private bool AddInternal(T item, int hashcode, bool acquireLock)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var localTables = tables;
|
||||
GetBucketAndLockNo(hashcode,
|
||||
out var bucketNo,
|
||||
out var lockNo,
|
||||
localTables.Buckets.Length,
|
||||
localTables.Locks.Length);
|
||||
|
||||
var resizeDesired = false;
|
||||
var lockTaken = false;
|
||||
try
|
||||
{
|
||||
if (acquireLock)
|
||||
Monitor.Enter(localTables.Locks[lockNo], ref lockTaken);
|
||||
|
||||
// If the table just got resized, we may not be holding the right lock, and must retry.
|
||||
// This should be a rare occurrence.
|
||||
if (localTables != tables)
|
||||
continue;
|
||||
|
||||
// Try to find this item in the bucket
|
||||
Node previous = null;
|
||||
for (var current = localTables.Buckets[bucketNo]; current is not null; current = current.Next)
|
||||
{
|
||||
Debug.Assert((previous is null && current == localTables.Buckets[bucketNo])
|
||||
|| previous!.Next == current);
|
||||
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
|
||||
return false;
|
||||
|
||||
previous = current;
|
||||
}
|
||||
|
||||
// The item was not found in the bucket. Insert the new item.
|
||||
Volatile.Write(ref localTables.Buckets[bucketNo], new(item, hashcode, localTables.Buckets[bucketNo]));
|
||||
checked
|
||||
{
|
||||
localTables.CountPerLock[lockNo]++;
|
||||
}
|
||||
|
||||
//
|
||||
// If the number of elements guarded by this lock has exceeded the budget, resize the bucket table.
|
||||
// It is also possible that GrowTable will increase the budget but won't resize the bucket table.
|
||||
// That happens if the bucket table is found to be poorly utilized due to a bad hash function.
|
||||
//
|
||||
if (localTables.CountPerLock[lockNo] > budget)
|
||||
resizeDesired = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
Monitor.Exit(localTables.Locks[lockNo]);
|
||||
}
|
||||
|
||||
//
|
||||
// The fact that we got here means that we just performed an insertion. If necessary, we will grow the table.
|
||||
//
|
||||
// Concurrency notes:
|
||||
// - Notice that we are not holding any locks at when calling GrowTable. This is necessary to prevent deadlocks.
|
||||
// - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0
|
||||
// and then verify that the table we passed to it as the argument is still the current table.
|
||||
//
|
||||
if (resizeDesired)
|
||||
GrowTable(localTables);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetBucket(int hashcode, int bucketCount)
|
||||
{
|
||||
var bucketNo = (hashcode & 0x7fffffff) % bucketCount;
|
||||
Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount);
|
||||
return bucketNo;
|
||||
}
|
||||
|
||||
private static void GetBucketAndLockNo(
|
||||
int hashcode,
|
||||
out int bucketNo,
|
||||
out int lockNo,
|
||||
int bucketCount,
|
||||
int lockCount)
|
||||
{
|
||||
bucketNo = (hashcode & 0x7fffffff) % bucketCount;
|
||||
lockNo = bucketNo % lockCount;
|
||||
|
||||
Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount);
|
||||
Debug.Assert(lockNo >= 0 && lockNo < lockCount);
|
||||
}
|
||||
|
||||
private void GrowTable(Tables localTables)
|
||||
{
|
||||
const int maxArrayLength = 0X7FEFFFFF;
|
||||
var locksAcquired = 0;
|
||||
try
|
||||
{
|
||||
// The thread that first obtains _locks[0] will be the one doing the resize operation
|
||||
AcquireLocks(0, 1, ref locksAcquired);
|
||||
|
||||
// Make sure nobody resized the table while we were waiting for lock 0:
|
||||
if (localTables != tables)
|
||||
// We assume that since the table reference is different, it was already resized (or the budget
|
||||
// was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons,
|
||||
// we will have to revisit this logic.
|
||||
return;
|
||||
|
||||
// Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow.
|
||||
long approxCount = 0;
|
||||
for (var i = 0; i < localTables.CountPerLock.Length; i++)
|
||||
approxCount += localTables.CountPerLock[i];
|
||||
|
||||
//
|
||||
// If the bucket array is too empty, double the budget instead of resizing the table
|
||||
//
|
||||
if (approxCount < localTables.Buckets.Length / 4)
|
||||
{
|
||||
budget = 2 * budget;
|
||||
if (budget < 0)
|
||||
budget = int.MaxValue;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by
|
||||
// 2,3,5 or 7. We can consider a different table-sizing policy in the future.
|
||||
var newLength = 0;
|
||||
var maximizeTableSize = false;
|
||||
try
|
||||
{
|
||||
checked
|
||||
{
|
||||
// Double the size of the buckets table and add one, so that we have an odd integer.
|
||||
newLength = (localTables.Buckets.Length * 2) + 1;
|
||||
|
||||
// Now, we only need to check odd integers, and find the first that is not divisible
|
||||
// by 3, 5 or 7.
|
||||
while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0)
|
||||
newLength += 2;
|
||||
|
||||
Debug.Assert(newLength % 2 != 0);
|
||||
|
||||
if (newLength > maxArrayLength)
|
||||
maximizeTableSize = true;
|
||||
}
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
maximizeTableSize = true;
|
||||
}
|
||||
|
||||
if (maximizeTableSize)
|
||||
{
|
||||
newLength = maxArrayLength;
|
||||
|
||||
// We want to make sure that GrowTable will not be called again, since table is at the maximum size.
|
||||
// To achieve that, we set the budget to int.MaxValue.
|
||||
//
|
||||
// (There is one special case that would allow GrowTable() to be called in the future:
|
||||
// calling Clear() on the ConcurrentHashSet will shrink the table and lower the budget.)
|
||||
budget = int.MaxValue;
|
||||
}
|
||||
|
||||
// Now acquire all other locks for the table
|
||||
AcquireLocks(1, localTables.Locks.Length, ref locksAcquired);
|
||||
|
||||
var newLocks = localTables.Locks;
|
||||
|
||||
// Add more locks
|
||||
if (_growLockArray && localTables.Locks.Length < MAX_LOCK_NUMBER)
|
||||
{
|
||||
newLocks = new object[localTables.Locks.Length * 2];
|
||||
Array.Copy(localTables.Locks, 0, newLocks, 0, localTables.Locks.Length);
|
||||
for (var i = localTables.Locks.Length; i < newLocks.Length; i++)
|
||||
newLocks[i] = new();
|
||||
}
|
||||
|
||||
var newBuckets = new Node[newLength];
|
||||
var newCountPerLock = new int[newLocks.Length];
|
||||
|
||||
// Copy all data into a new table, creating new nodes for all elements
|
||||
for (var i = 0; i < localTables.Buckets.Length; i++)
|
||||
{
|
||||
var current = localTables.Buckets[i];
|
||||
while (current is not null)
|
||||
{
|
||||
var next = current.Next;
|
||||
GetBucketAndLockNo(current.Hashcode,
|
||||
out var newBucketNo,
|
||||
out var newLockNo,
|
||||
newBuckets.Length,
|
||||
newLocks.Length);
|
||||
|
||||
newBuckets[newBucketNo] = new(current.Item, current.Hashcode, newBuckets[newBucketNo]);
|
||||
|
||||
checked
|
||||
{
|
||||
newCountPerLock[newLockNo]++;
|
||||
}
|
||||
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the budget
|
||||
budget = Math.Max(1, newBuckets.Length / newLocks.Length);
|
||||
|
||||
// Replace tables with the new versions
|
||||
tables = new(newBuckets, newLocks, newCountPerLock);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Release all locks that we took earlier
|
||||
ReleaseLocks(0, locksAcquired);
|
||||
}
|
||||
}
|
||||
|
||||
public int RemoveWhere(Func<T, bool> predicate)
|
||||
{
|
||||
var elems = this.Where(predicate);
|
||||
var removed = 0;
|
||||
foreach (var elem in elems)
|
||||
{
|
||||
if (TryRemove(elem))
|
||||
removed++;
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
private void AcquireAllLocks(ref int locksAcquired)
|
||||
{
|
||||
// First, acquire lock 0
|
||||
AcquireLocks(0, 1, ref locksAcquired);
|
||||
|
||||
// Now that we have lock 0, the _locks array will not change (i.e., grow),
|
||||
// and so we can safely read _locks.Length.
|
||||
AcquireLocks(1, tables.Locks.Length, ref locksAcquired);
|
||||
Debug.Assert(locksAcquired == tables.Locks.Length);
|
||||
}
|
||||
|
||||
private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired)
|
||||
{
|
||||
Debug.Assert(fromInclusive <= toExclusive);
|
||||
var locks = tables.Locks;
|
||||
|
||||
for (var i = fromInclusive; i < toExclusive; i++)
|
||||
{
|
||||
var lockTaken = false;
|
||||
try
|
||||
{
|
||||
Monitor.Enter(locks[i], ref lockTaken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
locksAcquired++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseLocks(int fromInclusive, int toExclusive)
|
||||
{
|
||||
Debug.Assert(fromInclusive <= toExclusive);
|
||||
|
||||
for (var i = fromInclusive; i < toExclusive; i++)
|
||||
Monitor.Exit(tables.Locks[i]);
|
||||
}
|
||||
|
||||
private void CopyToItems(T[] array, int index)
|
||||
{
|
||||
var buckets = tables.Buckets;
|
||||
for (var i = 0; i < buckets.Length; i++)
|
||||
for (var current = buckets[i]; current is not null; current = current.Next)
|
||||
{
|
||||
array[index] = current.Item;
|
||||
index++; //this should never flow, CopyToItems is only called when there's no overflow risk
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Tables
|
||||
{
|
||||
public readonly Node[] Buckets;
|
||||
public readonly object[] Locks;
|
||||
|
||||
public volatile int[] CountPerLock;
|
||||
|
||||
public Tables(Node[] buckets, object[] locks, int[] countPerLock)
|
||||
{
|
||||
Buckets = buckets;
|
||||
Locks = locks;
|
||||
CountPerLock = countPerLock;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Node
|
||||
{
|
||||
public readonly int Hashcode;
|
||||
public readonly T Item;
|
||||
|
||||
public volatile Node Next;
|
||||
|
||||
public Node(T item, int hashcode, Node next)
|
||||
{
|
||||
Item = item;
|
||||
Hashcode = hashcode;
|
||||
Next = next;
|
||||
}
|
||||
}
|
||||
public bool IsReadOnly
|
||||
=> false;
|
||||
}
|
@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Configs;
|
||||
public sealed partial class BotConfig : ICloneable<BotConfig>
|
||||
{
|
||||
[Comment(@"DO NOT CHANGE")]
|
||||
public int Version { get; set; } = 2;
|
||||
public int Version { get; set; } = 3;
|
||||
|
||||
[Comment(@"Most commands, when executed, have a small colored line
|
||||
next to the response. The color depends whether the command
|
||||
@@ -182,4 +182,4 @@ public enum ConsoleOutputType
|
||||
Normal = 0,
|
||||
Simple = 1,
|
||||
None = 2
|
||||
}
|
||||
}
|
||||
|
@@ -18,8 +18,11 @@ public sealed class Creds : IBotCredentials
|
||||
[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.")]
|
||||
[Comment(@"The number of shards that the bot will be running on.
|
||||
Leave at 1 if you don't know what you're doing.
|
||||
|
||||
note: If you are planning to have more than one shard, then you must change botCache to 'redis'.
|
||||
Also, in that case you should be using NadekoBot.Coordinator to start the bot, and it will correctly override this value.")]
|
||||
public int TotalShards { get; set; }
|
||||
|
||||
[Comment(
|
||||
@@ -27,6 +30,16 @@ Leave at 1 if you don't know what you're doing.")]
|
||||
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
||||
Used only for Youtube Data Api (at the moment).")]
|
||||
public string GoogleApiKey { get; set; }
|
||||
|
||||
[Comment(
|
||||
@"Create a new custom search here https://programmablesearchengine.google.com/cse/create/new
|
||||
Enable SafeSearch
|
||||
Remove all Sites to Search
|
||||
Enable Search the entire web
|
||||
Copy the 'Search Engine ID' to the SearchId field
|
||||
|
||||
Do all steps again but enable image search for the ImageSearchId")]
|
||||
public GoogleApiConfig Google { get; set; }
|
||||
|
||||
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
||||
public VotesSettings Votes { get; set; }
|
||||
@@ -40,8 +53,14 @@ go to https://www.patreon.com/portal -> my clients -> create client")]
|
||||
|
||||
[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.")]
|
||||
|
||||
[Comment(@"Which cache implementation should bot use.
|
||||
'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset.
|
||||
'redis' - Uses redis (which needs to be separately downloaded and installed). The cache will persist through bot restarts. You can configure connection string in creds.yml")]
|
||||
public BotCacheImplemenation BotCache { get; set; }
|
||||
|
||||
[Comment(@"Redis connection string. Don't change if you don't know what you're doing.
|
||||
Only used if botCache is set to 'redis'")]
|
||||
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")]
|
||||
@@ -94,12 +113,12 @@ Linux default
|
||||
args: ""NadekoBot.dll -- {0}""
|
||||
Windows default
|
||||
cmd: NadekoBot.exe
|
||||
args: {0}")]
|
||||
args: ""{0}""")]
|
||||
public RestartConfig RestartCommand { get; set; }
|
||||
|
||||
public Creds()
|
||||
{
|
||||
Version = 5;
|
||||
Version = 6;
|
||||
Token = string.Empty;
|
||||
UsePrivilegedIntents = true;
|
||||
OwnerIds = new List<ulong>();
|
||||
@@ -109,6 +128,7 @@ Windows default
|
||||
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||
BotListToken = string.Empty;
|
||||
CleverbotApiKey = string.Empty;
|
||||
BotCache = BotCacheImplemenation.Memory;
|
||||
RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=";
|
||||
Db = new()
|
||||
{
|
||||
@@ -119,6 +139,7 @@ Windows default
|
||||
CoordinatorUrl = "http://localhost:3442";
|
||||
|
||||
RestartCommand = new();
|
||||
Google = new();
|
||||
}
|
||||
|
||||
|
||||
@@ -200,4 +221,16 @@ This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsett
|
||||
DiscordsKey = discordsKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GoogleApiConfig
|
||||
{
|
||||
public string SearchId { get; init; }
|
||||
public string ImageSearchId { get; init; }
|
||||
}
|
||||
|
||||
public enum BotCacheImplemenation
|
||||
{
|
||||
Memory,
|
||||
Redis
|
||||
}
|
@@ -25,6 +25,8 @@ public interface IBotCredentials
|
||||
string CoordinatorUrl { get; set; }
|
||||
string TwitchClientId { get; set; }
|
||||
string TwitchClientSecret { get; set; }
|
||||
GoogleApiConfig Google { get; set; }
|
||||
BotCacheImplemenation BotCache { get; set; }
|
||||
}
|
||||
|
||||
public class RestartConfig
|
||||
|
@@ -1,9 +1,11 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.Yml;
|
||||
using Cloneable;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public class ImageUrls
|
||||
[Cloneable]
|
||||
public partial class ImageUrls : ICloneable<ImageUrls>
|
||||
{
|
||||
[Comment("DO NOT CHANGE")]
|
||||
public int Version { get; set; } = 3;
|
||||
|
29
src/NadekoBot/Common/Interaction/NadekoActionInteraction.cs
Normal file
29
src/NadekoBot/Common/Interaction/NadekoActionInteraction.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
public sealed class NadekoButtonActionInteraction : NadekoButtonOwnInteraction
|
||||
{
|
||||
private readonly NadekoInteractionData _data;
|
||||
private readonly Func<SocketMessageComponent, Task> _action;
|
||||
|
||||
public NadekoButtonActionInteraction(
|
||||
DiscordSocketClient client,
|
||||
ulong authorId,
|
||||
NadekoInteractionData data,
|
||||
Func<SocketMessageComponent, Task> action
|
||||
)
|
||||
: base(client, authorId)
|
||||
{
|
||||
_data = data;
|
||||
_action = action;
|
||||
}
|
||||
|
||||
protected override string Name
|
||||
=> _data.CustomId;
|
||||
protected override IEmote Emote
|
||||
=> _data.Emote;
|
||||
protected override string? Text
|
||||
=> _data.Text;
|
||||
|
||||
public override Task ExecuteOnActionAsync(SocketMessageComponent smc)
|
||||
=> _action(smc);
|
||||
}
|
83
src/NadekoBot/Common/Interaction/NadekoInteraction.cs
Normal file
83
src/NadekoBot/Common/Interaction/NadekoInteraction.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
public abstract class NadekoButtonInteraction
|
||||
{
|
||||
// improvements:
|
||||
// - state in OnAction
|
||||
// - configurable delay
|
||||
// -
|
||||
protected abstract string Name { get; }
|
||||
protected abstract IEmote Emote { get; }
|
||||
protected virtual string? Text { get; } = null;
|
||||
|
||||
public DiscordSocketClient Client { get; }
|
||||
|
||||
protected readonly TaskCompletionSource<bool> _interactionCompletedSource;
|
||||
|
||||
protected IUserMessage message = null!;
|
||||
|
||||
protected NadekoButtonInteraction(DiscordSocketClient client)
|
||||
{
|
||||
Client = client;
|
||||
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
}
|
||||
|
||||
public async Task RunAsync(IUserMessage msg)
|
||||
{
|
||||
message = msg;
|
||||
|
||||
Client.InteractionCreated += OnInteraction;
|
||||
await Task.WhenAny(Task.Delay(10_000), _interactionCompletedSource.Task);
|
||||
Client.InteractionCreated -= OnInteraction;
|
||||
|
||||
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
|
||||
}
|
||||
|
||||
protected abstract ValueTask<bool> Validate(SocketMessageComponent smc);
|
||||
private async Task OnInteraction(SocketInteraction arg)
|
||||
{
|
||||
if (arg is not SocketMessageComponent smc)
|
||||
return;
|
||||
|
||||
if (smc.Message.Id != message.Id)
|
||||
return;
|
||||
|
||||
if (smc.Data.CustomId != Name)
|
||||
return;
|
||||
|
||||
if (!await Validate(smc))
|
||||
{
|
||||
await smc.DeferAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await ExecuteOnActionAsync(smc);
|
||||
|
||||
// this should only be a thing on single-response buttons
|
||||
_interactionCompletedSource.TrySetResult(true);
|
||||
|
||||
if (!smc.HasResponded)
|
||||
{
|
||||
await smc.DeferAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public virtual MessageComponent CreateComponent()
|
||||
{
|
||||
var comp = new ComponentBuilder()
|
||||
.WithButton(GetButtonBuilder());
|
||||
|
||||
return comp.Build();
|
||||
}
|
||||
|
||||
public ButtonBuilder GetButtonBuilder()
|
||||
=> new ButtonBuilder(style: ButtonStyle.Secondary, emote: Emote, customId: Name, label: Text);
|
||||
|
||||
public abstract Task ExecuteOnActionAsync(SocketMessageComponent smc);
|
||||
}
|
||||
|
||||
// this is all so wrong ...
|
43
src/NadekoBot/Common/Interaction/NadekoInteractionArray.cs
Normal file
43
src/NadekoBot/Common/Interaction/NadekoInteractionArray.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// namespace NadekoBot;
|
||||
//
|
||||
// public class NadekoButtonInteractionArray : NadekoButtonInteraction
|
||||
// {
|
||||
// private readonly ButtonBuilder[] _bbs;
|
||||
// private readonly NadekoButtonInteraction[] _inters;
|
||||
//
|
||||
// public NadekoButtonInteractionArray(params NadekoButtonInteraction[] inters)
|
||||
// : base(inters[0].Client)
|
||||
// {
|
||||
// _inters = inters;
|
||||
// _bbs = inters.Map(x => x.GetButtonBuilder());
|
||||
// }
|
||||
//
|
||||
// protected override string Name
|
||||
// => throw new NotSupportedException();
|
||||
// protected override IEmote Emote
|
||||
// => throw new NotSupportedException();
|
||||
//
|
||||
// protected override ValueTask<bool> Validate(SocketMessageComponent smc)
|
||||
// => new(true);
|
||||
//
|
||||
// public override Task ExecuteOnActionAsync(SocketMessageComponent smc)
|
||||
// {
|
||||
// for (var i = 0; i < _bbs.Length; i++)
|
||||
// {
|
||||
// if (_bbs[i].CustomId == smc.Data.CustomId)
|
||||
// return _inters[i].ExecuteOnActionAsync(smc);
|
||||
// }
|
||||
//
|
||||
// return Task.CompletedTask;
|
||||
// }
|
||||
//
|
||||
// public override MessageComponent CreateComponent()
|
||||
// {
|
||||
// var comp = new ComponentBuilder();
|
||||
//
|
||||
// foreach (var bb in _bbs)
|
||||
// comp.WithButton(bb);
|
||||
//
|
||||
// return comp.Build();
|
||||
// }
|
||||
// }
|
42
src/NadekoBot/Common/Interaction/NadekoInteractionBuilder.cs
Normal file
42
src/NadekoBot/Common/Interaction/NadekoInteractionBuilder.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
/// <summary>
|
||||
/// Builder class for NadekoInteractions
|
||||
/// </summary>
|
||||
public class NadekoInteractionBuilder
|
||||
{
|
||||
private NadekoInteractionData? iData;
|
||||
private Func<SocketMessageComponent, Task>? action;
|
||||
// private bool isOwn;
|
||||
|
||||
public NadekoInteractionBuilder WithData<T>(in T data)
|
||||
where T : NadekoInteractionData
|
||||
{
|
||||
iData = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
// public NadekoOwnInteractionBuiler WithIsOwn(bool isOwn = true)
|
||||
// {
|
||||
// this.isOwn = isOwn;
|
||||
// return this;
|
||||
|
||||
// }
|
||||
|
||||
public NadekoInteractionBuilder WithAction(in Func<SocketMessageComponent, Task> fn)
|
||||
{
|
||||
this.action = fn;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NadekoButtonActionInteraction Build(DiscordSocketClient client, ulong userId)
|
||||
{
|
||||
if (iData is null)
|
||||
throw new InvalidOperationException("You have to specify the data before building the interaction");
|
||||
|
||||
if (action is null)
|
||||
throw new InvalidOperationException("You have to specify the action before building the interaction");
|
||||
|
||||
return new(client, userId, iData, action);
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
/// <summary>
|
||||
/// Represents essential interacation data
|
||||
/// </summary>
|
||||
/// <param name="Emote">Emote which will show on a button</param>
|
||||
/// <param name="CustomId">Custom interaction id</param>
|
||||
public record NadekoInteractionData(IEmote Emote, string CustomId, string? Text = null);
|
15
src/NadekoBot/Common/Interaction/NadekoOwnInteraction.cs
Normal file
15
src/NadekoBot/Common/Interaction/NadekoOwnInteraction.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction which only the author can use
|
||||
/// </summary>
|
||||
public abstract class NadekoButtonOwnInteraction : NadekoButtonInteraction
|
||||
{
|
||||
protected readonly ulong _authorId;
|
||||
|
||||
protected NadekoButtonOwnInteraction(DiscordSocketClient client, ulong authorId) : base(client)
|
||||
=> _authorId = authorId;
|
||||
|
||||
protected override ValueTask<bool> Validate(SocketMessageComponent smc)
|
||||
=> new(smc.User.Id == _authorId);
|
||||
}
|
16
src/NadekoBot/Common/Linq2DbExpressions.cs
Normal file
16
src/NadekoBot/Common/Linq2DbExpressions.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public static class Linq2DbExpressions
|
||||
{
|
||||
[ExpressionMethod(nameof(GuildOnShardExpression))]
|
||||
public static bool GuildOnShard(ulong guildId, int totalShards, int shardId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
private static Expression<Func<ulong, int, int, bool>> GuildOnShardExpression()
|
||||
=> (guildId, totalShards, shardId)
|
||||
=> guildId / 4194304 % (ulong)totalShards == (ulong)shardId;
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using Cloneable;
|
||||
#nullable enable
|
||||
using Cloneable;
|
||||
using NadekoBot.Common.Yml;
|
||||
|
||||
namespace Nadeko.Medusa;
|
||||
@@ -10,7 +11,7 @@ public sealed partial class MedusaConfig : ICloneable<MedusaConfig>
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
[Comment("List of medusae automatically loaded at startup")]
|
||||
public List<string> Loaded { get; set; }
|
||||
public List<string>? Loaded { get; set; }
|
||||
|
||||
public MedusaConfig()
|
||||
{
|
||||
|
@@ -18,7 +18,7 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<string> GetLoadedMedusae()
|
||||
=> Data.Loaded.ToList();
|
||||
=> Data.Loaded?.ToList() ?? new List<string>();
|
||||
|
||||
public void AddLoadedMedusa(string name)
|
||||
{
|
||||
@@ -26,6 +26,9 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
|
||||
|
||||
ModifyConfig(conf =>
|
||||
{
|
||||
if (conf.Loaded is null)
|
||||
conf.Loaded = new();
|
||||
|
||||
if(!conf.Loaded.Contains(name))
|
||||
conf.Loaded.Add(name);
|
||||
});
|
||||
@@ -37,6 +40,9 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
|
||||
|
||||
ModifyConfig(conf =>
|
||||
{
|
||||
if (conf.Loaded is null)
|
||||
conf.Loaded = new();
|
||||
|
||||
conf.Loaded.Remove(name);
|
||||
});
|
||||
}
|
||||
|
@@ -191,14 +191,12 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
||||
await _lock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var success = LoadAssemblyInternal(safeName,
|
||||
out var ctx,
|
||||
out var snekData,
|
||||
out var services,
|
||||
out var strings,
|
||||
out var typeReaders);
|
||||
|
||||
if (success)
|
||||
if (LoadAssemblyInternal(safeName,
|
||||
out var ctx,
|
||||
out var snekData,
|
||||
out var services,
|
||||
out var strings,
|
||||
out var typeReaders))
|
||||
{
|
||||
var moduleInfos = new List<ModuleInfo>();
|
||||
|
||||
@@ -769,7 +767,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
||||
var paramName = pi.Name ?? "unnamed";
|
||||
var isContext = paramCounter == 0 && pi.ParameterType.IsAssignableTo(typeof(AnyContext));
|
||||
|
||||
var leftoverAttribute = pi.GetCustomAttribute<Nadeko.Snake.leftoverAttribute>(true);
|
||||
var leftoverAttribute = pi.GetCustomAttribute<leftoverAttribute>(true);
|
||||
var hasDefaultValue = pi.HasDefaultValue;
|
||||
var isLeftover = leftoverAttribute != null;
|
||||
var isParams = pi.GetCustomAttribute<ParamArrayAttribute>() is not null;
|
||||
|
26
src/NadekoBot/Common/NInteraction.cs
Normal file
26
src/NadekoBot/Common/NInteraction.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public abstract class NInteraction
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ulong _userId;
|
||||
private readonly Func<SocketMessageComponent, Task> _action;
|
||||
|
||||
protected abstract NadekoInteractionData Data { get; }
|
||||
|
||||
public NInteraction(
|
||||
DiscordSocketClient client,
|
||||
ulong userId,
|
||||
Func<SocketMessageComponent, Task> action)
|
||||
{
|
||||
_client = client;
|
||||
_userId = userId;
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public NadekoButtonInteraction GetInteraction()
|
||||
=> new NadekoInteractionBuilder()
|
||||
.WithData(Data)
|
||||
.WithAction(_action)
|
||||
.Build(_client, _userId);
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
#nullable disable
|
||||
using System.Globalization;
|
||||
using MessageType = NadekoBot.Extensions.MessageType;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
@@ -29,20 +30,15 @@ public abstract class NadekoModule : ModuleBase
|
||||
|
||||
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)
|
||||
string footer = null,
|
||||
NadekoButtonInteraction inter = 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,
|
||||
@@ -50,25 +46,33 @@ public abstract class NadekoModule : ModuleBase
|
||||
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> SendErrorAsync(string text, NadekoButtonInteraction inter = null)
|
||||
=> ctx.Channel.SendAsync(_eb, text, MessageType.Error, inter);
|
||||
public Task<IUserMessage> SendConfirmAsync(string text, NadekoButtonInteraction inter = null)
|
||||
=> ctx.Channel.SendAsync(_eb, text, MessageType.Ok, inter);
|
||||
public Task<IUserMessage> SendPendingAsync(string text, NadekoButtonInteraction inter = null)
|
||||
=> ctx.Channel.SendAsync(_eb, text, MessageType.Pending, inter);
|
||||
|
||||
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str)
|
||||
=> SendErrorAsync(GetText(str));
|
||||
|
||||
// localized normal
|
||||
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||
=> SendErrorAsync(GetText(str), inter);
|
||||
|
||||
public Task<IUserMessage> PendingLocalizedAsync(LocStr str)
|
||||
=> SendPendingAsync(GetText(str));
|
||||
public Task<IUserMessage> PendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||
=> SendPendingAsync(GetText(str), inter);
|
||||
|
||||
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str)
|
||||
=> SendConfirmAsync(GetText(str));
|
||||
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||
=> SendConfirmAsync(GetText(str), inter);
|
||||
|
||||
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str)
|
||||
// localized replies
|
||||
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||
|
||||
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str)
|
||||
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||
|
||||
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str)
|
||||
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||
|
||||
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
||||
|
10
src/NadekoBot/Common/OneOfExtensions.cs
Normal file
10
src/NadekoBot/Common/OneOfExtensions.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using OneOf.Types;
|
||||
using OneOf;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public static class OneOfExtensions
|
||||
{
|
||||
public static bool TryGetValue<T>(this OneOf<T, None> oneOf, out T value)
|
||||
=> oneOf.TryPickT0(out value, out _);
|
||||
}
|
@@ -1,11 +1,12 @@
|
||||
#nullable disable
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Common.Pokemon;
|
||||
|
||||
public class SearchPokemon
|
||||
{
|
||||
[JsonProperty("num")]
|
||||
[JsonPropertyName("num")]
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Species { get; set; }
|
||||
|
62
src/NadekoBot/Common/QueueRunner.cs
Normal file
62
src/NadekoBot/Common/QueueRunner.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public sealed class QueueRunner
|
||||
{
|
||||
private readonly Channel<Func<Task>> _channel;
|
||||
private readonly int _delayMs;
|
||||
|
||||
public QueueRunner(int delayMs = 0, int maxCapacity = -1)
|
||||
{
|
||||
if (delayMs < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(delayMs));
|
||||
|
||||
_delayMs = delayMs;
|
||||
_channel = maxCapacity switch
|
||||
{
|
||||
0 or < -1 => throw new ArgumentOutOfRangeException(nameof(maxCapacity)),
|
||||
-1 => Channel.CreateUnbounded<Func<Task>>(new UnboundedChannelOptions()
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = false,
|
||||
AllowSynchronousContinuations = true,
|
||||
}),
|
||||
_ => Channel.CreateBounded<Func<Task>>(new BoundedChannelOptions(maxCapacity)
|
||||
{
|
||||
Capacity = maxCapacity,
|
||||
FullMode = BoundedChannelFullMode.DropOldest,
|
||||
SingleReader = true,
|
||||
SingleWriter = false,
|
||||
AllowSynchronousContinuations = true
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
public async Task RunAsync(CancellationToken cancel = default)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var func = await _channel.Reader.ReadAsync(cancel);
|
||||
|
||||
try
|
||||
{
|
||||
await func();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Exception executing a staggered func: {ErrorMessage}", ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_delayMs != 0)
|
||||
{
|
||||
await Task.Delay(_delayMs, cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask Enqueue(Func<Task> action)
|
||||
=> _channel.Writer.WriteAsync(action);
|
||||
}
|
@@ -34,61 +34,59 @@ public class Replacer
|
||||
public SmartText Replace(SmartText data)
|
||||
=> data switch
|
||||
{
|
||||
SmartEmbedText embedData => Replace(embedData),
|
||||
SmartEmbedText embedData => Replace(embedData) with
|
||||
{
|
||||
PlainText = Replace(embedData.PlainText),
|
||||
Color = embedData.Color
|
||||
},
|
||||
SmartPlainText plain => Replace(plain),
|
||||
SmartEmbedTextArray arr => Replace(arr),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data), "Unsupported argument type")
|
||||
};
|
||||
|
||||
public SmartPlainText Replace(SmartPlainText plainText)
|
||||
=> Replace(plainText.Text);
|
||||
|
||||
public SmartEmbedText Replace(SmartEmbedText embedData)
|
||||
{
|
||||
var newEmbedData = new SmartEmbedText
|
||||
private SmartEmbedTextArray Replace(SmartEmbedTextArray embedArr)
|
||||
=> new()
|
||||
{
|
||||
Embeds = embedArr.Embeds.Map(e => Replace(e) with
|
||||
{
|
||||
Color = e.Color
|
||||
}),
|
||||
Content = Replace(embedArr.Content)
|
||||
};
|
||||
|
||||
private SmartPlainText Replace(SmartPlainText plain)
|
||||
=> Replace(plain.Text);
|
||||
|
||||
private T Replace<T>(T embedData) where T: SmartEmbedTextBase, new()
|
||||
{
|
||||
var newEmbedData = new T
|
||||
{
|
||||
PlainText = Replace(embedData.PlainText),
|
||||
Description = Replace(embedData.Description),
|
||||
Title = Replace(embedData.Title),
|
||||
Thumbnail = Replace(embedData.Thumbnail),
|
||||
Image = Replace(embedData.Image),
|
||||
Url = Replace(embedData.Url)
|
||||
};
|
||||
if (embedData.Author is not null)
|
||||
{
|
||||
newEmbedData.Author = new()
|
||||
{
|
||||
Name = Replace(embedData.Author.Name),
|
||||
IconUrl = Replace(embedData.Author.IconUrl)
|
||||
};
|
||||
}
|
||||
|
||||
if (embedData.Fields is not null)
|
||||
{
|
||||
var fields = new List<SmartTextEmbedField>();
|
||||
foreach (var f in embedData.Fields)
|
||||
{
|
||||
var newF = new SmartTextEmbedField
|
||||
Url = Replace(embedData.Url),
|
||||
Author = embedData.Author is null
|
||||
? null
|
||||
: new()
|
||||
{
|
||||
Name = Replace(f.Name),
|
||||
Value = Replace(f.Value),
|
||||
Inline = f.Inline
|
||||
};
|
||||
fields.Add(newF);
|
||||
}
|
||||
|
||||
newEmbedData.Fields = fields.ToArray();
|
||||
}
|
||||
|
||||
if (embedData.Footer is not null)
|
||||
{
|
||||
newEmbedData.Footer = new()
|
||||
Name = Replace(embedData.Author.Name),
|
||||
IconUrl = Replace(embedData.Author.IconUrl)
|
||||
},
|
||||
Fields = embedData.Fields?.Map(f => new SmartTextEmbedField
|
||||
{
|
||||
Text = Replace(embedData.Footer.Text),
|
||||
IconUrl = Replace(embedData.Footer.IconUrl)
|
||||
};
|
||||
}
|
||||
|
||||
newEmbedData.Color = embedData.Color;
|
||||
Name = Replace(f.Name),
|
||||
Value = Replace(f.Value),
|
||||
Inline = f.Inline
|
||||
}),
|
||||
Footer = embedData.Footer is null
|
||||
? null
|
||||
: new()
|
||||
{
|
||||
Text = Replace(embedData.Footer.Text),
|
||||
IconUrl = Replace(embedData.Footer.IconUrl)
|
||||
}
|
||||
};
|
||||
|
||||
return newEmbedData;
|
||||
}
|
||||
|
@@ -1,20 +1,67 @@
|
||||
#nullable disable
|
||||
#nullable disable warnings
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace NadekoBot;
|
||||
|
||||
public sealed record SmartEmbedText : SmartText
|
||||
public sealed record SmartEmbedArrayElementText : SmartEmbedTextBase
|
||||
{
|
||||
public string PlainText { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string Thumbnail { get; set; }
|
||||
public string Image { get; set; }
|
||||
public string Color { get; init; } = string.Empty;
|
||||
|
||||
public SmartTextEmbedAuthor Author { get; set; }
|
||||
public SmartTextEmbedFooter Footer { get; set; }
|
||||
public SmartTextEmbedField[] Fields { get; set; }
|
||||
public SmartEmbedArrayElementText() : base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SmartEmbedArrayElementText(IEmbed eb) : base(eb)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public uint Color { get; set; } = 7458112;
|
||||
protected override EmbedBuilder GetEmbedInternal()
|
||||
{
|
||||
var embed = base.GetEmbedInternal();
|
||||
if (Rgba32.TryParseHex(Color, out var color))
|
||||
return embed.WithColor(color.ToDiscordColor());
|
||||
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record SmartEmbedText : SmartEmbedTextBase
|
||||
{
|
||||
public string PlainText { get; init; }
|
||||
|
||||
public uint Color { get; init; } = 7458112;
|
||||
|
||||
public SmartEmbedText()
|
||||
{
|
||||
}
|
||||
|
||||
private SmartEmbedText(IEmbed eb, string? plainText = null)
|
||||
: base(eb)
|
||||
=> (PlainText, Color) = (plainText, eb.Color?.RawValue ?? 0);
|
||||
|
||||
public static SmartEmbedText FromEmbed(IEmbed eb, string? plainText = null)
|
||||
=> new(eb, plainText);
|
||||
|
||||
protected override EmbedBuilder GetEmbedInternal()
|
||||
{
|
||||
var embed = base.GetEmbedInternal();
|
||||
return embed.WithColor(Color);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract record SmartEmbedTextBase : SmartText
|
||||
{
|
||||
public string Title { get; init; }
|
||||
public string Description { get; init; }
|
||||
public string Url { get; init; }
|
||||
public string Thumbnail { get; init; }
|
||||
public string Image { get; init; }
|
||||
|
||||
public SmartTextEmbedAuthor Author { get; init; }
|
||||
public SmartTextEmbedFooter Footer { get; init; }
|
||||
public SmartTextEmbedField[] Fields { get; init; }
|
||||
|
||||
public bool IsValid
|
||||
=> !string.IsNullOrWhiteSpace(Title)
|
||||
@@ -26,36 +73,37 @@ public sealed record SmartEmbedText : SmartText
|
||||
&& (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl)))
|
||||
|| Fields is { Length: > 0 };
|
||||
|
||||
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
|
||||
protected SmartEmbedTextBase()
|
||||
{
|
||||
var set = new SmartEmbedText
|
||||
{
|
||||
PlainText = plainText,
|
||||
Title = eb.Title,
|
||||
Description = eb.Description,
|
||||
Url = eb.Url,
|
||||
Thumbnail = eb.Thumbnail?.Url,
|
||||
Image = eb.Image?.Url,
|
||||
Author = eb.Author is { } ea
|
||||
? new()
|
||||
{
|
||||
Name = ea.Name,
|
||||
Url = ea.Url,
|
||||
IconUrl = ea.IconUrl
|
||||
}
|
||||
: null,
|
||||
Footer = eb.Footer is { } ef
|
||||
? new()
|
||||
{
|
||||
Text = ef.Text,
|
||||
IconUrl = ef.IconUrl
|
||||
}
|
||||
: null
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected SmartEmbedTextBase(IEmbed eb)
|
||||
{
|
||||
Title = eb.Title;
|
||||
Description = eb.Description;
|
||||
Url = eb.Url;
|
||||
Thumbnail = eb.Thumbnail?.Url;
|
||||
Image = eb.Image?.Url;
|
||||
Author = eb.Author is { } ea
|
||||
? new()
|
||||
{
|
||||
Name = ea.Name,
|
||||
Url = ea.Url,
|
||||
IconUrl = ea.IconUrl
|
||||
}
|
||||
: null;
|
||||
Footer = eb.Footer is { } ef
|
||||
? new()
|
||||
{
|
||||
Text = ef.Text,
|
||||
IconUrl = ef.IconUrl
|
||||
}
|
||||
: null;
|
||||
|
||||
if (eb.Fields.Length > 0)
|
||||
{
|
||||
set.Fields = eb.Fields.Select(field
|
||||
Fields = eb.Fields.Select(field
|
||||
=> new SmartTextEmbedField
|
||||
{
|
||||
Inline = field.Inline,
|
||||
@@ -64,14 +112,14 @@ public sealed record SmartEmbedText : SmartText
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
set.Color = eb.Color?.RawValue ?? 0;
|
||||
return set;
|
||||
}
|
||||
|
||||
public EmbedBuilder GetEmbed()
|
||||
=> GetEmbedInternal();
|
||||
|
||||
protected virtual EmbedBuilder GetEmbedInternal()
|
||||
{
|
||||
var embed = new EmbedBuilder().WithColor(Color);
|
||||
var embed = new EmbedBuilder();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Title))
|
||||
embed.WithTitle(Title);
|
||||
|
31
src/NadekoBot/Common/SmartText/SmartEmbedTextArray.cs
Normal file
31
src/NadekoBot/Common/SmartText/SmartEmbedTextArray.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot;
|
||||
|
||||
public sealed record SmartEmbedTextArray : SmartText
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public SmartEmbedArrayElementText[] Embeds { get; set; }
|
||||
|
||||
public bool IsValid
|
||||
=> Embeds?.All(x => x.IsValid) ?? false;
|
||||
|
||||
public EmbedBuilder[] GetEmbedBuilders()
|
||||
{
|
||||
if (Embeds is null)
|
||||
return Array.Empty<EmbedBuilder>();
|
||||
|
||||
return Embeds
|
||||
.Where(x => x.IsValid)
|
||||
.Select(em => em.GetEmbed())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public void NormalizeFields()
|
||||
{
|
||||
if (Embeds is null)
|
||||
return;
|
||||
|
||||
foreach(var eb in Embeds)
|
||||
eb.NormalizeFields();
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
#nullable disable
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NadekoBot;
|
||||
|
||||
@@ -11,6 +11,9 @@ public abstract record SmartText
|
||||
public bool IsPlainText
|
||||
=> this is SmartPlainText;
|
||||
|
||||
public bool IsEmbedArray
|
||||
=> this is SmartEmbedTextArray;
|
||||
|
||||
public static SmartText operator +(SmartText text, string input)
|
||||
=> text switch
|
||||
{
|
||||
@@ -19,6 +22,10 @@ public abstract record SmartText
|
||||
PlainText = set.PlainText + input
|
||||
},
|
||||
SmartPlainText spt => new SmartPlainText(spt.Text + input),
|
||||
SmartEmbedTextArray arr => arr with
|
||||
{
|
||||
Content = arr.Content + input
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||
};
|
||||
|
||||
@@ -30,27 +37,45 @@ public abstract record SmartText
|
||||
PlainText = input + set.PlainText
|
||||
},
|
||||
SmartPlainText spt => new SmartPlainText(input + spt.Text),
|
||||
SmartEmbedTextArray arr => arr with
|
||||
{
|
||||
Content = input + arr.Content
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||
};
|
||||
|
||||
public static SmartText CreateFrom(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input) || !input.TrimStart().StartsWith("{"))
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return new SmartPlainText(input);
|
||||
|
||||
try
|
||||
{
|
||||
var smartEmbedText = JsonConvert.DeserializeObject<SmartEmbedText>(input);
|
||||
var doc = JObject.Parse(input);
|
||||
var root = doc.Root;
|
||||
if (root.Type == JTokenType.Object)
|
||||
{
|
||||
if (((JObject)root).TryGetValue("embeds", out _))
|
||||
{
|
||||
var arr = root.ToObject<SmartEmbedTextArray>();
|
||||
|
||||
if (smartEmbedText is null)
|
||||
throw new FormatException();
|
||||
if (arr is null)
|
||||
return new SmartPlainText(input);
|
||||
|
||||
smartEmbedText.NormalizeFields();
|
||||
arr!.NormalizeFields();
|
||||
return arr;
|
||||
}
|
||||
|
||||
if (!smartEmbedText.IsValid)
|
||||
return new SmartPlainText(input);
|
||||
var obj = root.ToObject<SmartEmbedText>();
|
||||
|
||||
return smartEmbedText;
|
||||
if (obj is null || !(obj.IsValid || !string.IsNullOrWhiteSpace(obj.PlainText)))
|
||||
return new SmartPlainText(input);
|
||||
|
||||
obj.NormalizeFields();
|
||||
return obj;
|
||||
}
|
||||
|
||||
return new SmartPlainText(input);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@@ -1,95 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public sealed class ReactionEventWrapper : IDisposable
|
||||
{
|
||||
public event Action<SocketReaction> OnReactionAdded = delegate { };
|
||||
public event Action<SocketReaction> OnReactionRemoved = delegate { };
|
||||
public event Action OnReactionsCleared = delegate { };
|
||||
|
||||
public IUserMessage Message { get; }
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
private bool disposing;
|
||||
|
||||
public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
|
||||
{
|
||||
Message = msg ?? throw new ArgumentNullException(nameof(msg));
|
||||
_client = client;
|
||||
|
||||
_client.ReactionAdded += Discord_ReactionAdded;
|
||||
_client.ReactionRemoved += Discord_ReactionRemoved;
|
||||
_client.ReactionsCleared += Discord_ReactionsCleared;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
return;
|
||||
disposing = true;
|
||||
UnsubAll();
|
||||
}
|
||||
|
||||
private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, Cacheable<IMessageChannel, ulong> channel)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionsCleared?.Invoke();
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Discord_ReactionRemoved(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> cacheable,
|
||||
SocketReaction reaction)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionRemoved?.Invoke(reaction);
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Discord_ReactionAdded(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> cacheable,
|
||||
SocketReaction reaction)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionAdded?.Invoke(reaction);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void UnsubAll()
|
||||
{
|
||||
_client.ReactionAdded -= Discord_ReactionAdded;
|
||||
_client.ReactionRemoved -= Discord_ReactionRemoved;
|
||||
_client.ReactionsCleared -= Discord_ReactionsCleared;
|
||||
OnReactionAdded = null;
|
||||
OnReactionRemoved = null;
|
||||
OnReactionsCleared = null;
|
||||
}
|
||||
}
|
@@ -31,38 +31,38 @@ public sealed class CommandTypeReader : NadekoTypeReader<CommandInfo>
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
||||
public sealed class CommandOrExprTypeReader : NadekoTypeReader<CommandOrExprInfo>
|
||||
{
|
||||
private readonly CommandService _cmds;
|
||||
private readonly CommandHandler _commandHandler;
|
||||
private readonly NadekoExpressionsService _exprs;
|
||||
|
||||
public CommandOrCrTypeReader(CommandService cmds, NadekoExpressionsService exprs, CommandHandler commandHandler)
|
||||
public CommandOrExprTypeReader(CommandService cmds, NadekoExpressionsService exprs, CommandHandler commandHandler)
|
||||
{
|
||||
_cmds = cmds;
|
||||
_exprs = exprs;
|
||||
_commandHandler = commandHandler;
|
||||
}
|
||||
|
||||
public override async ValueTask<TypeReaderResult<CommandOrCrInfo>> ReadAsync(ICommandContext ctx, string input)
|
||||
public override async ValueTask<TypeReaderResult<CommandOrExprInfo>> ReadAsync(ICommandContext ctx, string input)
|
||||
{
|
||||
input = input.ToUpperInvariant();
|
||||
|
||||
if (_exprs.ExpressionExists(ctx.Guild?.Id, input))
|
||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input, CommandOrCrInfo.Type.Custom));
|
||||
if (_exprs.ExpressionExists(ctx.Guild?.Id, input) || _exprs.ExpressionExists(null, input))
|
||||
return TypeReaderResult.FromSuccess(new CommandOrExprInfo(input, CommandOrExprInfo.Type.Custom));
|
||||
|
||||
var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(ctx, input);
|
||||
if (cmd.IsSuccess)
|
||||
{
|
||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name,
|
||||
CommandOrCrInfo.Type.Normal));
|
||||
return TypeReaderResult.FromSuccess(new CommandOrExprInfo(((CommandInfo)cmd.Values.First().Value).Name,
|
||||
CommandOrExprInfo.Type.Normal));
|
||||
}
|
||||
|
||||
return TypeReaderResult.FromError<CommandOrCrInfo>(CommandError.ParseFailed, "No such command or cr found.");
|
||||
return TypeReaderResult.FromError<CommandOrExprInfo>(CommandError.ParseFailed, "No such command or expression found.");
|
||||
}
|
||||
}
|
||||
|
||||
public class CommandOrCrInfo
|
||||
public class CommandOrExprInfo
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
@@ -76,7 +76,7 @@ public class CommandOrCrInfo
|
||||
public bool IsCustom
|
||||
=> CmdType == Type.Custom;
|
||||
|
||||
public CommandOrCrInfo(string input, Type type)
|
||||
public CommandOrExprInfo(string input, Type type)
|
||||
{
|
||||
Name = input;
|
||||
CmdType = type;
|
||||
|
@@ -50,9 +50,7 @@ public static class GuildConfigExtensions
|
||||
.Include(gc => gc.StreamRole)
|
||||
.Include(gc => gc.XpSettings)
|
||||
.ThenInclude(x => x.ExclusionList)
|
||||
.Include(gc => gc.DelMsgOnCmdChannels)
|
||||
.Include(gc => gc.ReactionRoleMessages)
|
||||
.ThenInclude(x => x.ReactionRoles);
|
||||
.Include(gc => gc.DelMsgOnCmdChannels);
|
||||
|
||||
public static IEnumerable<GuildConfig> GetAllGuildConfigs(
|
||||
this DbSet<GuildConfig> configs,
|
||||
|
9
src/NadekoBot/Db/Models/BankUser.cs
Normal file
9
src/NadekoBot/Db/Models/BankUser.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Db.Models;
|
||||
|
||||
public class BankUser : DbEntity
|
||||
{
|
||||
public ulong UserId { get; set; }
|
||||
public long Balance { get; set; }
|
||||
}
|
@@ -3,6 +3,8 @@ using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Db.Models;
|
||||
|
||||
|
||||
// FUTURE remove LastLevelUp from here and UserXpStats
|
||||
public class DiscordUser : DbEntity
|
||||
{
|
||||
public ulong UserId { get; set; }
|
||||
@@ -14,7 +16,7 @@ public class DiscordUser : DbEntity
|
||||
public ClubInfo Club { get; set; }
|
||||
public bool IsClubAdmin { get; set; }
|
||||
|
||||
public int TotalXp { get; set; }
|
||||
public long TotalXp { get; set; }
|
||||
public DateTime LastLevelUp { get; set; } = DateTime.UtcNow;
|
||||
public DateTime LastXpGain { get; set; } = DateTime.MinValue;
|
||||
public XpNotificationLocation NotifyOnLevelUp { get; set; }
|
||||
|
@@ -84,14 +84,14 @@ public class GuildConfig : DbEntity
|
||||
|
||||
public List<ShopEntry> ShopEntries { get; set; }
|
||||
public ulong? GameVoiceChannel { get; set; }
|
||||
public bool VerboseErrors { get; set; }
|
||||
public bool VerboseErrors { get; set; } = true;
|
||||
|
||||
public StreamRoleSettings StreamRole { get; set; }
|
||||
|
||||
public XpSettings XpSettings { get; set; }
|
||||
public List<FeedSub> FeedSubs { get; set; } = new();
|
||||
public IndexedCollection<ReactionRoleMessage> ReactionRoleMessages { get; set; } = new();
|
||||
public bool NotifyStreamOffline { get; set; }
|
||||
public bool DeleteStreamOnlineMessage { get; set; }
|
||||
public List<GroupName> SelfAssignableRoleGroupNames { get; set; }
|
||||
public int WarnExpireHours { get; set; }
|
||||
public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear;
|
||||
|
48
src/NadekoBot/Db/Models/PatronQuota.cs
Normal file
48
src/NadekoBot/Db/Models/PatronQuota.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Db.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Contains data about usage of Patron-Only commands per user
|
||||
/// in order to provide support for quota limitations
|
||||
/// (allow user x who is pledging amount y to use the specified command only
|
||||
/// x amount of times in the specified time period)
|
||||
/// </summary>
|
||||
public class PatronQuota
|
||||
{
|
||||
public ulong UserId { get; set; }
|
||||
public FeatureType FeatureType { get; set; }
|
||||
public string Feature { get; set; }
|
||||
public uint HourlyCount { get; set; }
|
||||
public uint DailyCount { get; set; }
|
||||
public uint MonthlyCount { get; set; }
|
||||
}
|
||||
|
||||
public enum FeatureType
|
||||
{
|
||||
Command,
|
||||
Group,
|
||||
Module,
|
||||
Limit
|
||||
}
|
||||
|
||||
public class PatronUser
|
||||
{
|
||||
public string UniquePlatformUserId { get; set; }
|
||||
public ulong UserId { get; set; }
|
||||
public int AmountCents { get; set; }
|
||||
|
||||
public DateTime LastCharge { get; set; }
|
||||
|
||||
// Date Only component
|
||||
public DateTime ValidThru { get; set; }
|
||||
|
||||
public PatronUser Clone()
|
||||
=> new PatronUser()
|
||||
{
|
||||
UniquePlatformUserId = this.UniquePlatformUserId,
|
||||
UserId = this.UserId,
|
||||
AmountCents = this.AmountCents,
|
||||
LastCharge = this.LastCharge,
|
||||
ValidThru = this.ValidThru
|
||||
};
|
||||
}
|
@@ -1,22 +1,18 @@
|
||||
#nullable disable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace NadekoBot.Services.Database.Models;
|
||||
|
||||
public class ReactionRoleMessage : DbEntity, IIndexed
|
||||
public class ReactionRoleV2 : DbEntity
|
||||
{
|
||||
public int Index { get; set; }
|
||||
|
||||
public int GuildConfigId { get; set; }
|
||||
public GuildConfig GuildConfig { get; set; }
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
|
||||
public ulong MessageId { get; set; }
|
||||
|
||||
public List<ReactionRole> ReactionRoles { get; set; }
|
||||
public bool Exclusive { get; set; }
|
||||
}
|
||||
|
||||
public class ReactionRole : DbEntity
|
||||
{
|
||||
public string EmoteName { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string Emote { get; set; }
|
||||
public ulong RoleId { get; set; }
|
||||
public int Group { get; set; }
|
||||
public int LevelReq { get; set; }
|
||||
}
|
@@ -4,7 +4,7 @@ namespace NadekoBot.Services.Database.Models;
|
||||
public class RewardedUser : DbEntity
|
||||
{
|
||||
public ulong UserId { get; set; }
|
||||
public string PatreonUserId { get; set; }
|
||||
public int AmountRewardedThisMonth { get; set; }
|
||||
public string PlatformUserId { get; set; }
|
||||
public long AmountRewardedThisMonth { get; set; }
|
||||
public DateTime LastReward { get; set; }
|
||||
}
|
13
src/NadekoBot/Db/Models/StreamOnlineMessage.cs
Normal file
13
src/NadekoBot/Db/Models/StreamOnlineMessage.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Db.Models;
|
||||
|
||||
public class StreamOnlineMessage : DbEntity
|
||||
{
|
||||
public ulong ChannelId { get; set; }
|
||||
public ulong MessageId { get; set; }
|
||||
|
||||
public FollowedStream.FType Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
@@ -5,8 +5,8 @@ public class UserXpStats : DbEntity
|
||||
{
|
||||
public ulong UserId { get; set; }
|
||||
public ulong GuildId { get; set; }
|
||||
public int Xp { get; set; }
|
||||
public int AwardedXp { get; set; }
|
||||
public long Xp { get; set; }
|
||||
public long AwardedXp { get; set; }
|
||||
public XpNotificationLocation NotifyOnLevelUp { get; set; }
|
||||
public DateTime LastLevelUp { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
@@ -26,7 +25,7 @@ public abstract class NadekoContext : DbContext
|
||||
public DbSet<ClubInfo> Clubs { get; set; }
|
||||
public DbSet<ClubBans> ClubBans { get; set; }
|
||||
public DbSet<ClubApplicants> ClubApplicants { get; set; }
|
||||
|
||||
|
||||
|
||||
//logging
|
||||
public DbSet<LogSetting> LogSettings { get; set; }
|
||||
@@ -52,14 +51,25 @@ public abstract class NadekoContext : DbContext
|
||||
|
||||
public DbSet<Permissionv2> Permissions { get; set; }
|
||||
|
||||
public DbSet<BankUser> BankUsers { get; set; }
|
||||
|
||||
public DbSet<ReactionRoleV2> ReactionRoles { get; set; }
|
||||
|
||||
public DbSet<PatronUser> Patrons { get; set; }
|
||||
|
||||
public DbSet<PatronQuota> PatronQuotas { get; set; }
|
||||
|
||||
public DbSet<StreamOnlineMessage> StreamOnlineMessages { get; set; }
|
||||
|
||||
|
||||
#region Mandatory Provider-Specific Values
|
||||
|
||||
protected abstract string CurrencyTransactionOtherIdDefaultValue { get; }
|
||||
protected abstract string DiscordUserLastXpGainDefaultValue { get; }
|
||||
protected abstract string LastLevelUpDefaultValue { get; }
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
#region QUOTES
|
||||
@@ -73,7 +83,11 @@ public abstract class NadekoContext : DbContext
|
||||
#region GuildConfig
|
||||
|
||||
var configEntity = modelBuilder.Entity<GuildConfig>();
|
||||
configEntity.HasIndex(c => c.GuildId).IsUnique();
|
||||
configEntity.HasIndex(c => c.GuildId)
|
||||
.IsUnique();
|
||||
|
||||
configEntity.Property(x => x.VerboseErrors)
|
||||
.HasDefaultValue(true);
|
||||
|
||||
modelBuilder.Entity<AntiSpamSetting>().HasOne(x => x.GuildConfig).WithOne(x => x.AntiSpamSetting);
|
||||
|
||||
@@ -189,13 +203,6 @@ public abstract class NadekoContext : DbContext
|
||||
|
||||
#endregion
|
||||
|
||||
#region PatreonRewards
|
||||
|
||||
var pr = modelBuilder.Entity<RewardedUser>();
|
||||
pr.HasIndex(x => x.PatreonUserId).IsUnique();
|
||||
|
||||
#endregion
|
||||
|
||||
#region XpStats
|
||||
|
||||
var xps = modelBuilder.Entity<UserXpStats>();
|
||||
@@ -359,10 +366,18 @@ public abstract class NadekoContext : DbContext
|
||||
|
||||
#region Reaction roles
|
||||
|
||||
modelBuilder.Entity<ReactionRoleMessage>(rrm => rrm
|
||||
.HasMany(x => x.ReactionRoles)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade));
|
||||
modelBuilder.Entity<ReactionRoleV2>(rr2 =>
|
||||
{
|
||||
rr2.HasIndex(x => x.GuildId)
|
||||
.IsUnique(false);
|
||||
|
||||
rr2.HasIndex(x => new
|
||||
{
|
||||
x.MessageId,
|
||||
x.Emote
|
||||
})
|
||||
.IsUnique();
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -402,6 +417,43 @@ public abstract class NadekoContext : DbContext
|
||||
x.ChannelId,
|
||||
x.UserId
|
||||
}));
|
||||
|
||||
#region BANK
|
||||
|
||||
modelBuilder.Entity<BankUser>(bu => bu.HasIndex(x => x.UserId).IsUnique());
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Patron
|
||||
|
||||
// currency rewards
|
||||
var pr = modelBuilder.Entity<RewardedUser>();
|
||||
pr.HasIndex(x => x.PlatformUserId).IsUnique();
|
||||
|
||||
// patrons
|
||||
// patrons are not identified by their user id, but by their platform user id
|
||||
// as multiple accounts (even maybe on different platforms) could have
|
||||
// the same account connected to them
|
||||
modelBuilder.Entity<PatronUser>(pu =>
|
||||
{
|
||||
pu.HasIndex(x => x.UniquePlatformUserId).IsUnique();
|
||||
pu.HasKey(x => x.UserId);
|
||||
});
|
||||
|
||||
// quotes are per user id
|
||||
modelBuilder.Entity<PatronQuota>(pq =>
|
||||
{
|
||||
pq.HasIndex(x => x.UserId).IsUnique(false);
|
||||
pq.HasKey(x => new
|
||||
{
|
||||
x.UserId,
|
||||
x.FeatureType,
|
||||
x.Feature
|
||||
});
|
||||
});
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
@@ -1,4 +1,5 @@
|
||||
global using System.Collections.Concurrent;
|
||||
// global using System.Collections.Concurrent;
|
||||
global using NonBlocking;
|
||||
|
||||
// packages
|
||||
global using Serilog;
|
||||
|
16
src/NadekoBot/Migrations/MigrationQueries.cs
Normal file
16
src/NadekoBot/Migrations/MigrationQueries.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations;
|
||||
|
||||
public static class MigrationQueries
|
||||
{
|
||||
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
@"insert or ignore into reactionroles(guildid, channelid, messageid, emote, roleid, 'group', levelreq, dateadded)
|
||||
select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
|
||||
from reactionrole
|
||||
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
|
||||
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;");
|
||||
}
|
||||
}
|
3429
src/NadekoBot/Migrations/MySql/20220428044612_stondel.Designer.cs
generated
Normal file
3429
src/NadekoBot/Migrations/MySql/20220428044612_stondel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
src/NadekoBot/Migrations/MySql/20220428044612_stondel.cs
Normal file
26
src/NadekoBot/Migrations/MySql/20220428044612_stondel.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class stondel : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "deletestreamonlinemessage",
|
||||
table: "guildconfigs",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "deletestreamonlinemessage",
|
||||
table: "guildconfigs");
|
||||
}
|
||||
}
|
||||
}
|
3458
src/NadekoBot/Migrations/MySql/20220429044757_bank.Designer.cs
generated
Normal file
3458
src/NadekoBot/Migrations/MySql/20220429044757_bank.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
src/NadekoBot/Migrations/MySql/20220429044757_bank.cs
Normal file
42
src/NadekoBot/Migrations/MySql/20220429044757_bank.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class bank : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "bankusers",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
userid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
balance = table.Column<long>(type: "bigint", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_bankusers", x => x.id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_bankusers_userid",
|
||||
table: "bankusers",
|
||||
column: "userid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "bankusers");
|
||||
}
|
||||
}
|
||||
}
|
3411
src/NadekoBot/Migrations/MySql/20220504162509_new-rero.Designer.cs
generated
Normal file
3411
src/NadekoBot/Migrations/MySql/20220504162509_new-rero.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
121
src/NadekoBot/Migrations/MySql/20220504162509_new-rero.cs
Normal file
121
src/NadekoBot/Migrations/MySql/20220504162509_new-rero.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class newrero : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionroles",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
messageid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
emote = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
roleid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
group = table.Column<int>(type: "int", nullable: false),
|
||||
levelreq = table.Column<int>(type: "int", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionroles", x => x.id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionroles_guildid",
|
||||
table: "reactionroles",
|
||||
column: "guildid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionroles_messageid_emote",
|
||||
table: "reactionroles",
|
||||
columns: new[] { "messageid", "emote" },
|
||||
unique: true);
|
||||
|
||||
MigrationQueries.MigrateRero(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionrole");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionrolemessage");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionroles");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionrolemessage",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
guildconfigid = table.Column<int>(type: "int", nullable: false),
|
||||
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
exclusive = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
index = table.Column<int>(type: "int", nullable: false),
|
||||
messageid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionrolemessage", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_reactionrolemessage_guildconfigs_guildconfigid",
|
||||
column: x => x.guildconfigid,
|
||||
principalTable: "guildconfigs",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionrole",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
emotename = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
reactionrolemessageid = table.Column<int>(type: "int", nullable: true),
|
||||
roleid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionrole", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_reactionrole_reactionrolemessage_reactionrolemessageid",
|
||||
column: x => x.reactionrolemessageid,
|
||||
principalTable: "reactionrolemessage",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionrole_reactionrolemessageid",
|
||||
table: "reactionrole",
|
||||
column: "reactionrolemessageid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionrolemessage_guildconfigid",
|
||||
table: "reactionrolemessage",
|
||||
column: "guildconfigid");
|
||||
}
|
||||
}
|
||||
}
|
3481
src/NadekoBot/Migrations/MySql/20220614071410_patronage-system.Designer.cs
generated
Normal file
3481
src/NadekoBot/Migrations/MySql/20220614071410_patronage-system.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class patronagesystem : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "patreonuserid",
|
||||
table: "rewardedusers",
|
||||
newName: "platformuserid");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_rewardedusers_patreonuserid",
|
||||
table: "rewardedusers",
|
||||
newName: "ix_rewardedusers_platformuserid");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "xp",
|
||||
table: "userxpstats",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "awardedxp",
|
||||
table: "userxpstats",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "amountrewardedthismonth",
|
||||
table: "rewardedusers",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "verboseerrors",
|
||||
table: "guildconfigs",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: true,
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "tinyint(1)");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "totalxp",
|
||||
table: "discorduser",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int",
|
||||
oldDefaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patronquotas",
|
||||
columns: table => new
|
||||
{
|
||||
userid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
featuretype = table.Column<int>(type: "int", nullable: false),
|
||||
feature = table.Column<string>(type: "varchar(255)", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
hourlycount = table.Column<uint>(type: "int unsigned", nullable: false),
|
||||
dailycount = table.Column<uint>(type: "int unsigned", nullable: false),
|
||||
monthlycount = table.Column<uint>(type: "int unsigned", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_patronquotas", x => new { x.userid, x.featuretype, x.feature });
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patrons",
|
||||
columns: table => new
|
||||
{
|
||||
userid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
uniqueplatformuserid = table.Column<string>(type: "varchar(255)", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
amountcents = table.Column<int>(type: "int", nullable: false),
|
||||
lastcharge = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
validthru = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_patrons", x => x.userid);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_patronquotas_userid",
|
||||
table: "patronquotas",
|
||||
column: "userid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_patrons_uniqueplatformuserid",
|
||||
table: "patrons",
|
||||
column: "uniqueplatformuserid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "patronquotas");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "patrons");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "platformuserid",
|
||||
table: "rewardedusers",
|
||||
newName: "patreonuserid");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_rewardedusers_platformuserid",
|
||||
table: "rewardedusers",
|
||||
newName: "ix_rewardedusers_patreonuserid");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "xp",
|
||||
table: "userxpstats",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "awardedxp",
|
||||
table: "userxpstats",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "amountrewardedthismonth",
|
||||
table: "rewardedusers",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "verboseerrors",
|
||||
table: "guildconfigs",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "tinyint(1)",
|
||||
oldDefaultValue: true);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "totalxp",
|
||||
table: "discorduser",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint",
|
||||
oldDefaultValue: 0L);
|
||||
}
|
||||
}
|
||||
}
|
3514
src/NadekoBot/Migrations/MySql/20220623090718_stondel-db-cache.Designer.cs
generated
Normal file
3514
src/NadekoBot/Migrations/MySql/20220623090718_stondel-db-cache.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class stondeldbcache : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "streamonlinemessages",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
messageid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
type = table.Column<int>(type: "int", nullable: false),
|
||||
name = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_streamonlinemessages", x => x.id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "streamonlinemessages");
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,9 +16,38 @@ namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.3")
|
||||
.HasAnnotation("ProductVersion", "6.0.6")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<long>("Balance")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("balance");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("userid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_bankusers");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_bankusers_userid");
|
||||
|
||||
b.ToTable("bankusers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
|
||||
{
|
||||
b.Property<int>("ClubId")
|
||||
@@ -157,10 +186,10 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("notifyonlevelup");
|
||||
|
||||
b.Property<int>("TotalXp")
|
||||
b.Property<long>("TotalXp")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnType("bigint")
|
||||
.HasDefaultValue(0L)
|
||||
.HasColumnName("totalxp");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
@@ -236,6 +265,107 @@ namespace NadekoBot.Migrations.Mysql
|
||||
b.ToTable("followedstream", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.PatronQuota", b =>
|
||||
{
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("userid");
|
||||
|
||||
b.Property<int>("FeatureType")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("featuretype");
|
||||
|
||||
b.Property<string>("Feature")
|
||||
.HasColumnType("varchar(255)")
|
||||
.HasColumnName("feature");
|
||||
|
||||
b.Property<uint>("DailyCount")
|
||||
.HasColumnType("int unsigned")
|
||||
.HasColumnName("dailycount");
|
||||
|
||||
b.Property<uint>("HourlyCount")
|
||||
.HasColumnType("int unsigned")
|
||||
.HasColumnName("hourlycount");
|
||||
|
||||
b.Property<uint>("MonthlyCount")
|
||||
.HasColumnType("int unsigned")
|
||||
.HasColumnName("monthlycount");
|
||||
|
||||
b.HasKey("UserId", "FeatureType", "Feature")
|
||||
.HasName("pk_patronquotas");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("ix_patronquotas_userid");
|
||||
|
||||
b.ToTable("patronquotas", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b =>
|
||||
{
|
||||
b.Property<ulong>("UserId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("userid");
|
||||
|
||||
b.Property<int>("AmountCents")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("amountcents");
|
||||
|
||||
b.Property<DateTime>("LastCharge")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("lastcharge");
|
||||
|
||||
b.Property<string>("UniquePlatformUserId")
|
||||
.HasColumnType("varchar(255)")
|
||||
.HasColumnName("uniqueplatformuserid");
|
||||
|
||||
b.Property<DateTime>("ValidThru")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("validthru");
|
||||
|
||||
b.HasKey("UserId")
|
||||
.HasName("pk_patrons");
|
||||
|
||||
b.HasIndex("UniquePlatformUserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_patrons_uniqueplatformuserid");
|
||||
|
||||
b.ToTable("patrons", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.StreamOnlineMessage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("channelid");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<ulong>("MessageId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("messageid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_streamonlinemessages");
|
||||
|
||||
b.ToTable("streamonlinemessages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1032,6 +1162,10 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("deletemessageoncommand");
|
||||
|
||||
b.Property<bool>("DeleteStreamOnlineMessage")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("deletestreamonlinemessage");
|
||||
|
||||
b.Property<string>("DmGreetMessageText")
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("dmgreetmessagetext");
|
||||
@@ -1105,7 +1239,9 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnName("timezoneid");
|
||||
|
||||
b.Property<bool>("VerboseErrors")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("verboseerrors");
|
||||
|
||||
b.Property<bool>("VerbosePermissions")
|
||||
@@ -1780,39 +1916,7 @@ namespace NadekoBot.Migrations.Mysql
|
||||
b.ToTable("quotes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<string>("EmoteName")
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("emotename");
|
||||
|
||||
b.Property<int?>("ReactionRoleMessageId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("reactionrolemessageid");
|
||||
|
||||
b.Property<ulong>("RoleId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("roleid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_reactionrole");
|
||||
|
||||
b.HasIndex("ReactionRoleMessageId")
|
||||
.HasDatabaseName("ix_reactionrole_reactionrolemessageid");
|
||||
|
||||
b.ToTable("reactionrole", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleV2", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -1827,29 +1931,42 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<bool>("Exclusive")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("exclusive");
|
||||
b.Property<string>("Emote")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("emote");
|
||||
|
||||
b.Property<int>("GuildConfigId")
|
||||
b.Property<int>("Group")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("guildconfigid");
|
||||
.HasColumnName("group");
|
||||
|
||||
b.Property<int>("Index")
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<int>("LevelReq")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("index");
|
||||
.HasColumnName("levelreq");
|
||||
|
||||
b.Property<ulong>("MessageId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("messageid");
|
||||
|
||||
b.Property<ulong>("RoleId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("roleid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_reactionrolemessage");
|
||||
.HasName("pk_reactionroles");
|
||||
|
||||
b.HasIndex("GuildConfigId")
|
||||
.HasDatabaseName("ix_reactionrolemessage_guildconfigid");
|
||||
b.HasIndex("GuildId")
|
||||
.HasDatabaseName("ix_reactionroles_guildid");
|
||||
|
||||
b.ToTable("reactionrolemessage", (string)null);
|
||||
b.HasIndex("MessageId", "Emote")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_reactionroles_messageid_emote");
|
||||
|
||||
b.ToTable("reactionroles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
|
||||
@@ -1948,8 +2065,8 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("AmountRewardedThisMonth")
|
||||
.HasColumnType("int")
|
||||
b.Property<long>("AmountRewardedThisMonth")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("amountrewardedthismonth");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
@@ -1960,9 +2077,9 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("lastreward");
|
||||
|
||||
b.Property<string>("PatreonUserId")
|
||||
b.Property<string>("PlatformUserId")
|
||||
.HasColumnType("varchar(255)")
|
||||
.HasColumnName("patreonuserid");
|
||||
.HasColumnName("platformuserid");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
@@ -1971,9 +2088,9 @@ namespace NadekoBot.Migrations.Mysql
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_rewardedusers");
|
||||
|
||||
b.HasIndex("PatreonUserId")
|
||||
b.HasIndex("PlatformUserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_rewardedusers_patreonuserid");
|
||||
.HasDatabaseName("ix_rewardedusers_platformuserid");
|
||||
|
||||
b.ToTable("rewardedusers", (string)null);
|
||||
});
|
||||
@@ -2390,8 +2507,8 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("AwardedXp")
|
||||
.HasColumnType("int")
|
||||
b.Property<long>("AwardedXp")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("awardedxp");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
@@ -2416,8 +2533,8 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("userid");
|
||||
|
||||
b.Property<int>("Xp")
|
||||
.HasColumnType("int")
|
||||
b.Property<long>("Xp")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("xp");
|
||||
|
||||
b.HasKey("Id")
|
||||
@@ -3075,27 +3192,6 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasConstraintName("fk_pollvote_poll_pollid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.ReactionRoleMessage", null)
|
||||
.WithMany("ReactionRoles")
|
||||
.HasForeignKey("ReactionRoleMessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.HasConstraintName("fk_reactionrole_reactionrolemessage_reactionrolemessageid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
|
||||
.WithMany("ReactionRoleMessages")
|
||||
.HasForeignKey("GuildConfigId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_reactionrolemessage_guildconfigs_guildconfigid");
|
||||
|
||||
b.Navigation("GuildConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
@@ -3345,8 +3441,6 @@ namespace NadekoBot.Migrations.Mysql
|
||||
|
||||
b.Navigation("Permissions");
|
||||
|
||||
b.Navigation("ReactionRoleMessages");
|
||||
|
||||
b.Navigation("SelfAssignableRoleGroupNames");
|
||||
|
||||
b.Navigation("ShopEntries");
|
||||
@@ -3387,11 +3481,6 @@ namespace NadekoBot.Migrations.Mysql
|
||||
b.Navigation("Votes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.Navigation("ReactionRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
|
3569
src/NadekoBot/Migrations/Postgresql/20220428044547_stondel.Designer.cs
generated
Normal file
3569
src/NadekoBot/Migrations/Postgresql/20220428044547_stondel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class stondel : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "deletestreamonlinemessage",
|
||||
table: "guildconfigs",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "deletestreamonlinemessage",
|
||||
table: "guildconfigs");
|
||||
}
|
||||
}
|
||||
}
|
3600
src/NadekoBot/Migrations/Postgresql/20220429044808_bank.Designer.cs
generated
Normal file
3600
src/NadekoBot/Migrations/Postgresql/20220429044808_bank.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
src/NadekoBot/Migrations/Postgresql/20220429044808_bank.cs
Normal file
41
src/NadekoBot/Migrations/Postgresql/20220429044808_bank.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class bank : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "bankusers",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
balance = table.Column<long>(type: "bigint", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_bankusers", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_bankusers_userid",
|
||||
table: "bankusers",
|
||||
column: "userid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "bankusers");
|
||||
}
|
||||
}
|
||||
}
|
3551
src/NadekoBot/Migrations/Postgresql/20220504162457_new-rero.Designer.cs
generated
Normal file
3551
src/NadekoBot/Migrations/Postgresql/20220504162457_new-rero.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
115
src/NadekoBot/Migrations/Postgresql/20220504162457_new-rero.cs
Normal file
115
src/NadekoBot/Migrations/Postgresql/20220504162457_new-rero.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class newrero : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionroles",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
messageid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
emote = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
group = table.Column<int>(type: "integer", nullable: false),
|
||||
levelreq = table.Column<int>(type: "integer", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionroles", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionroles_guildid",
|
||||
table: "reactionroles",
|
||||
column: "guildid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionroles_messageid_emote",
|
||||
table: "reactionroles",
|
||||
columns: new[] { "messageid", "emote" },
|
||||
unique: true);
|
||||
|
||||
MigrationQueries.MigrateRero(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionrole");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionrolemessage");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionroles");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionrolemessage",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
guildconfigid = table.Column<int>(type: "integer", nullable: false),
|
||||
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
exclusive = table.Column<bool>(type: "boolean", nullable: false),
|
||||
index = table.Column<int>(type: "integer", nullable: false),
|
||||
messageid = table.Column<decimal>(type: "numeric(20,0)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionrolemessage", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_reactionrolemessage_guildconfigs_guildconfigid",
|
||||
column: x => x.guildconfigid,
|
||||
principalTable: "guildconfigs",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionrole",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
emotename = table.Column<string>(type: "text", nullable: true),
|
||||
reactionrolemessageid = table.Column<int>(type: "integer", nullable: true),
|
||||
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionrole", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_reactionrole_reactionrolemessage_reactionrolemessageid",
|
||||
column: x => x.reactionrolemessageid,
|
||||
principalTable: "reactionrolemessage",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionrole_reactionrolemessageid",
|
||||
table: "reactionrole",
|
||||
column: "reactionrolemessageid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionrolemessage_guildconfigid",
|
||||
table: "reactionrolemessage",
|
||||
column: "guildconfigid");
|
||||
}
|
||||
}
|
||||
}
|
3621
src/NadekoBot/Migrations/Postgresql/20220614071421_patronage-system.Designer.cs
generated
Normal file
3621
src/NadekoBot/Migrations/Postgresql/20220614071421_patronage-system.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class patronagesystem : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "patreonuserid",
|
||||
table: "rewardedusers",
|
||||
newName: "platformuserid");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_rewardedusers_patreonuserid",
|
||||
table: "rewardedusers",
|
||||
newName: "ix_rewardedusers_platformuserid");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "xp",
|
||||
table: "userxpstats",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "integer");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "awardedxp",
|
||||
table: "userxpstats",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "integer");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "amountrewardedthismonth",
|
||||
table: "rewardedusers",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "integer");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "verboseerrors",
|
||||
table: "guildconfigs",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: true,
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "boolean");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "totalxp",
|
||||
table: "discorduser",
|
||||
type: "bigint",
|
||||
nullable: false,
|
||||
defaultValue: 0L,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "integer",
|
||||
oldDefaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patronquotas",
|
||||
columns: table => new
|
||||
{
|
||||
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
featuretype = table.Column<int>(type: "integer", nullable: false),
|
||||
feature = table.Column<string>(type: "text", nullable: false),
|
||||
hourlycount = table.Column<long>(type: "bigint", nullable: false),
|
||||
dailycount = table.Column<long>(type: "bigint", nullable: false),
|
||||
monthlycount = table.Column<long>(type: "bigint", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_patronquotas", x => new { x.userid, x.featuretype, x.feature });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patrons",
|
||||
columns: table => new
|
||||
{
|
||||
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
uniqueplatformuserid = table.Column<string>(type: "text", nullable: true),
|
||||
amountcents = table.Column<int>(type: "integer", nullable: false),
|
||||
lastcharge = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
validthru = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_patrons", x => x.userid);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_patronquotas_userid",
|
||||
table: "patronquotas",
|
||||
column: "userid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_patrons_uniqueplatformuserid",
|
||||
table: "patrons",
|
||||
column: "uniqueplatformuserid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "patronquotas");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "patrons");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "platformuserid",
|
||||
table: "rewardedusers",
|
||||
newName: "patreonuserid");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_rewardedusers_platformuserid",
|
||||
table: "rewardedusers",
|
||||
newName: "ix_rewardedusers_patreonuserid");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "xp",
|
||||
table: "userxpstats",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "awardedxp",
|
||||
table: "userxpstats",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "amountrewardedthismonth",
|
||||
table: "rewardedusers",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "verboseerrors",
|
||||
table: "guildconfigs",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "boolean",
|
||||
oldDefaultValue: true);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "totalxp",
|
||||
table: "discorduser",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "bigint",
|
||||
oldDefaultValue: 0L);
|
||||
}
|
||||
}
|
||||
}
|
3656
src/NadekoBot/Migrations/Postgresql/20220623090729_stondel-db-cache.Designer.cs
generated
Normal file
3656
src/NadekoBot/Migrations/Postgresql/20220623090729_stondel-db-cache.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class stondeldbcache : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "streamonlinemessages",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
messageid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: true),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_streamonlinemessages", x => x.id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "streamonlinemessages");
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,11 +17,42 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.3")
|
||||
.HasAnnotation("ProductVersion", "6.0.6")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<long>("Balance")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("balance");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<decimal>("UserId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("userid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_bankusers");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_bankusers_userid");
|
||||
|
||||
b.ToTable("bankusers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
|
||||
{
|
||||
b.Property<int>("ClubId")
|
||||
@@ -163,10 +194,10 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("notifyonlevelup");
|
||||
|
||||
b.Property<int>("TotalXp")
|
||||
b.Property<long>("TotalXp")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnType("bigint")
|
||||
.HasDefaultValue(0L)
|
||||
.HasColumnName("totalxp");
|
||||
|
||||
b.Property<decimal>("UserId")
|
||||
@@ -244,6 +275,109 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.ToTable("followedstream", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.PatronQuota", b =>
|
||||
{
|
||||
b.Property<decimal>("UserId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("userid");
|
||||
|
||||
b.Property<int>("FeatureType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("featuretype");
|
||||
|
||||
b.Property<string>("Feature")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("feature");
|
||||
|
||||
b.Property<long>("DailyCount")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("dailycount");
|
||||
|
||||
b.Property<long>("HourlyCount")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("hourlycount");
|
||||
|
||||
b.Property<long>("MonthlyCount")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("monthlycount");
|
||||
|
||||
b.HasKey("UserId", "FeatureType", "Feature")
|
||||
.HasName("pk_patronquotas");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("ix_patronquotas_userid");
|
||||
|
||||
b.ToTable("patronquotas", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b =>
|
||||
{
|
||||
b.Property<decimal>("UserId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("userid");
|
||||
|
||||
b.Property<int>("AmountCents")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("amountcents");
|
||||
|
||||
b.Property<DateTime>("LastCharge")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("lastcharge");
|
||||
|
||||
b.Property<string>("UniquePlatformUserId")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("uniqueplatformuserid");
|
||||
|
||||
b.Property<DateTime>("ValidThru")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("validthru");
|
||||
|
||||
b.HasKey("UserId")
|
||||
.HasName("pk_patrons");
|
||||
|
||||
b.HasIndex("UniquePlatformUserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_patrons_uniqueplatformuserid");
|
||||
|
||||
b.ToTable("patrons", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.StreamOnlineMessage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<decimal>("ChannelId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("channelid");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<decimal>("MessageId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("messageid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_streamonlinemessages");
|
||||
|
||||
b.ToTable("streamonlinemessages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1086,6 +1220,10 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("deletemessageoncommand");
|
||||
|
||||
b.Property<bool>("DeleteStreamOnlineMessage")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("deletestreamonlinemessage");
|
||||
|
||||
b.Property<string>("DmGreetMessageText")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("dmgreetmessagetext");
|
||||
@@ -1159,7 +1297,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnName("timezoneid");
|
||||
|
||||
b.Property<bool>("VerboseErrors")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("verboseerrors");
|
||||
|
||||
b.Property<bool>("VerbosePermissions")
|
||||
@@ -1866,41 +2006,7 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.ToTable("quotes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<string>("EmoteName")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("emotename");
|
||||
|
||||
b.Property<int?>("ReactionRoleMessageId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("reactionrolemessageid");
|
||||
|
||||
b.Property<decimal>("RoleId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("roleid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_reactionrole");
|
||||
|
||||
b.HasIndex("ReactionRoleMessageId")
|
||||
.HasDatabaseName("ix_reactionrole_reactionrolemessageid");
|
||||
|
||||
b.ToTable("reactionrole", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleV2", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -1917,29 +2023,42 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<bool>("Exclusive")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("exclusive");
|
||||
b.Property<string>("Emote")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("emote");
|
||||
|
||||
b.Property<int>("GuildConfigId")
|
||||
b.Property<int>("Group")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("guildconfigid");
|
||||
.HasColumnName("group");
|
||||
|
||||
b.Property<int>("Index")
|
||||
b.Property<decimal>("GuildId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<int>("LevelReq")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("index");
|
||||
.HasColumnName("levelreq");
|
||||
|
||||
b.Property<decimal>("MessageId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("messageid");
|
||||
|
||||
b.Property<decimal>("RoleId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("roleid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_reactionrolemessage");
|
||||
.HasName("pk_reactionroles");
|
||||
|
||||
b.HasIndex("GuildConfigId")
|
||||
.HasDatabaseName("ix_reactionrolemessage_guildconfigid");
|
||||
b.HasIndex("GuildId")
|
||||
.HasDatabaseName("ix_reactionroles_guildid");
|
||||
|
||||
b.ToTable("reactionrolemessage", (string)null);
|
||||
b.HasIndex("MessageId", "Emote")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_reactionroles_messageid_emote");
|
||||
|
||||
b.ToTable("reactionroles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
|
||||
@@ -2044,8 +2163,8 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("AmountRewardedThisMonth")
|
||||
.HasColumnType("integer")
|
||||
b.Property<long>("AmountRewardedThisMonth")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("amountrewardedthismonth");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
@@ -2056,9 +2175,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("lastreward");
|
||||
|
||||
b.Property<string>("PatreonUserId")
|
||||
b.Property<string>("PlatformUserId")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("patreonuserid");
|
||||
.HasColumnName("platformuserid");
|
||||
|
||||
b.Property<decimal>("UserId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
@@ -2067,9 +2186,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_rewardedusers");
|
||||
|
||||
b.HasIndex("PatreonUserId")
|
||||
b.HasIndex("PlatformUserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_rewardedusers_patreonuserid");
|
||||
.HasDatabaseName("ix_rewardedusers_platformuserid");
|
||||
|
||||
b.ToTable("rewardedusers", (string)null);
|
||||
});
|
||||
@@ -2512,8 +2631,8 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("AwardedXp")
|
||||
.HasColumnType("integer")
|
||||
b.Property<long>("AwardedXp")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("awardedxp");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
@@ -2538,8 +2657,8 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("userid");
|
||||
|
||||
b.Property<int>("Xp")
|
||||
.HasColumnType("integer")
|
||||
b.Property<long>("Xp")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("xp");
|
||||
|
||||
b.HasKey("Id")
|
||||
@@ -3215,27 +3334,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasConstraintName("fk_pollvote_poll_pollid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.ReactionRoleMessage", null)
|
||||
.WithMany("ReactionRoles")
|
||||
.HasForeignKey("ReactionRoleMessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.HasConstraintName("fk_reactionrole_reactionrolemessage_reactionrolemessageid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
|
||||
.WithMany("ReactionRoleMessages")
|
||||
.HasForeignKey("GuildConfigId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_reactionrolemessage_guildconfigs_guildconfigid");
|
||||
|
||||
b.Navigation("GuildConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
@@ -3485,8 +3583,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
|
||||
b.Navigation("Permissions");
|
||||
|
||||
b.Navigation("ReactionRoleMessages");
|
||||
|
||||
b.Navigation("SelfAssignableRoleGroupNames");
|
||||
|
||||
b.Navigation("ShopEntries");
|
||||
@@ -3527,11 +3623,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.Navigation("Votes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.Navigation("ReactionRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
|
@@ -87,7 +87,7 @@ namespace NadekoBot.Migrations
|
||||
name: "VoicePresenceChannelId",
|
||||
table: "LogSettings");
|
||||
|
||||
// todo cleanup guildconfigs which have logsettings id set to null
|
||||
// FUTURE cleanup guildconfigs which have logsettings id set to null
|
||||
migrationBuilder.Sql("UPDATE GuildConfigs SET LogSettingId = null WHERE LogSettingId NOT IN (SELECT Id from LogSettings)");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
|
2759
src/NadekoBot/Migrations/Sqlite/20220427200557_stondel.Designer.cs
generated
Normal file
2759
src/NadekoBot/Migrations/Sqlite/20220427200557_stondel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
src/NadekoBot/Migrations/Sqlite/20220427200557_stondel.cs
Normal file
26
src/NadekoBot/Migrations/Sqlite/20220427200557_stondel.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class stondel : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "DeleteStreamOnlineMessage",
|
||||
table: "GuildConfigs",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DeleteStreamOnlineMessage",
|
||||
table: "GuildConfigs");
|
||||
}
|
||||
}
|
||||
}
|
2782
src/NadekoBot/Migrations/Sqlite/20220428051304_bank.Designer.cs
generated
Normal file
2782
src/NadekoBot/Migrations/Sqlite/20220428051304_bank.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
src/NadekoBot/Migrations/Sqlite/20220428051304_bank.cs
Normal file
40
src/NadekoBot/Migrations/Sqlite/20220428051304_bank.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class bank : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BankUsers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Balance = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BankUsers", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BankUsers_UserId",
|
||||
table: "BankUsers",
|
||||
column: "UserId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "BankUsers");
|
||||
}
|
||||
}
|
||||
}
|
2741
src/NadekoBot/Migrations/Sqlite/20220503234243_new-rero.Designer.cs
generated
Normal file
2741
src/NadekoBot/Migrations/Sqlite/20220503234243_new-rero.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
114
src/NadekoBot/Migrations/Sqlite/20220503234243_new-rero.cs
Normal file
114
src/NadekoBot/Migrations/Sqlite/20220503234243_new-rero.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class newrero : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReactionRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
MessageId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Emote = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Group = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
LevelReq = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReactionRoles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReactionRoles_GuildId",
|
||||
table: "ReactionRoles",
|
||||
column: "GuildId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReactionRoles_MessageId_Emote",
|
||||
table: "ReactionRoles",
|
||||
columns: new[] { "MessageId", "Emote" },
|
||||
unique: true);
|
||||
|
||||
MigrationQueries.MigrateRero(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReactionRole");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReactionRoleMessage");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReactionRoles");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReactionRoleMessage",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildConfigId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
Exclusive = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
Index = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
MessageId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReactionRoleMessage", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReactionRoleMessage_GuildConfigs_GuildConfigId",
|
||||
column: x => x.GuildConfigId,
|
||||
principalTable: "GuildConfigs",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReactionRole",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
EmoteName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ReactionRoleMessageId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReactionRole", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReactionRole_ReactionRoleMessage_ReactionRoleMessageId",
|
||||
column: x => x.ReactionRoleMessageId,
|
||||
principalTable: "ReactionRoleMessage",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReactionRole_ReactionRoleMessageId",
|
||||
table: "ReactionRole",
|
||||
column: "ReactionRoleMessageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReactionRoleMessage_GuildConfigId",
|
||||
table: "ReactionRoleMessage",
|
||||
column: "GuildConfigId");
|
||||
}
|
||||
}
|
||||
}
|
2796
src/NadekoBot/Migrations/Sqlite/20220614071359_patronage-system.Designer.cs
generated
Normal file
2796
src/NadekoBot/Migrations/Sqlite/20220614071359_patronage-system.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class patronagesystem : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "PatreonUserId",
|
||||
table: "RewardedUsers",
|
||||
newName: "PlatformUserId");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_RewardedUsers_PatreonUserId",
|
||||
table: "RewardedUsers",
|
||||
newName: "IX_RewardedUsers_PlatformUserId");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "VerboseErrors",
|
||||
table: "GuildConfigs",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: true,
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "INTEGER");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "TotalXp",
|
||||
table: "DiscordUser",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0L,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "INTEGER",
|
||||
oldDefaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PatronQuotas",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
FeatureType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Feature = table.Column<string>(type: "TEXT", nullable: false),
|
||||
HourlyCount = table.Column<uint>(type: "INTEGER", nullable: false),
|
||||
DailyCount = table.Column<uint>(type: "INTEGER", nullable: false),
|
||||
MonthlyCount = table.Column<uint>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PatronQuotas", x => new { x.UserId, x.FeatureType, x.Feature });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Patrons",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UniquePlatformUserId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
AmountCents = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
LastCharge = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
ValidThru = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Patrons", x => x.UserId);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PatronQuotas_UserId",
|
||||
table: "PatronQuotas",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Patrons_UniquePlatformUserId",
|
||||
table: "Patrons",
|
||||
column: "UniquePlatformUserId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PatronQuotas");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Patrons");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "PlatformUserId",
|
||||
table: "RewardedUsers",
|
||||
newName: "PatreonUserId");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_RewardedUsers_PlatformUserId",
|
||||
table: "RewardedUsers",
|
||||
newName: "IX_RewardedUsers_PatreonUserId");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "VerboseErrors",
|
||||
table: "GuildConfigs",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "INTEGER",
|
||||
oldDefaultValue: true);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "TotalXp",
|
||||
table: "DiscordUser",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "INTEGER",
|
||||
oldDefaultValue: 0L);
|
||||
}
|
||||
}
|
||||
}
|
2823
src/NadekoBot/Migrations/Sqlite/20220623073903_stondel-db-cache.Designer.cs
generated
Normal file
2823
src/NadekoBot/Migrations/Sqlite/20220623073903_stondel-db-cache.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class stondeldbcache : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StreamOnlineMessages",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
MessageId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_StreamOnlineMessages", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "StreamOnlineMessages");
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,7 +15,30 @@ namespace NadekoBot.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.3");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Balance")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("BankUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
|
||||
{
|
||||
@@ -126,10 +149,10 @@ namespace NadekoBot.Migrations
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<int>("TotalXp")
|
||||
b.Property<long>("TotalXp")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0);
|
||||
.HasDefaultValue(0L);
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
@@ -186,6 +209,86 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("FollowedStream");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.PatronQuota", b =>
|
||||
{
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("FeatureType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Feature")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("DailyCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("HourlyCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("MonthlyCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("UserId", "FeatureType", "Feature");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PatronQuotas");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.PatronUser", b =>
|
||||
{
|
||||
b.Property<ulong>("UserId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AmountCents")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastCharge")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UniquePlatformUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ValidThru")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId");
|
||||
|
||||
b.HasIndex("UniquePlatformUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Patrons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.StreamOnlineMessage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("MessageId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("StreamOnlineMessages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -809,6 +912,9 @@ namespace NadekoBot.Migrations
|
||||
b.Property<bool>("DeleteMessageOnCommand")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DeleteStreamOnlineMessage")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DmGreetMessageText")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -864,7 +970,9 @@ namespace NadekoBot.Migrations
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("VerboseErrors")
|
||||
.HasColumnType("INTEGER");
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("VerbosePermissions")
|
||||
.HasColumnType("INTEGER");
|
||||
@@ -1389,32 +1497,7 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("Quotes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EmoteName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ReactionRoleMessageId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("RoleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ReactionRoleMessageId");
|
||||
|
||||
b.ToTable("ReactionRole");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleV2", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -1426,23 +1509,33 @@ namespace NadekoBot.Migrations
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Exclusive")
|
||||
b.Property<string>("Emote")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Group")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("GuildConfigId")
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Index")
|
||||
b.Property<int>("LevelReq")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("MessageId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("RoleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GuildConfigId");
|
||||
b.HasIndex("GuildId");
|
||||
|
||||
b.ToTable("ReactionRoleMessage");
|
||||
b.HasIndex("MessageId", "Emote")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ReactionRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
|
||||
@@ -1520,7 +1613,7 @@ namespace NadekoBot.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AmountRewardedThisMonth")
|
||||
b.Property<long>("AmountRewardedThisMonth")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
@@ -1529,7 +1622,7 @@ namespace NadekoBot.Migrations
|
||||
b.Property<DateTime>("LastReward")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PatreonUserId")
|
||||
b.Property<string>("PlatformUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
@@ -1537,7 +1630,7 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PatreonUserId")
|
||||
b.HasIndex("PlatformUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("RewardedUsers");
|
||||
@@ -1866,7 +1959,7 @@ namespace NadekoBot.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AwardedXp")
|
||||
b.Property<long>("AwardedXp")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
@@ -1886,7 +1979,7 @@ namespace NadekoBot.Migrations
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Xp")
|
||||
b.Property<long>("Xp")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
@@ -2430,25 +2523,6 @@ namespace NadekoBot.Migrations
|
||||
.HasForeignKey("PollId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.ReactionRoleMessage", null)
|
||||
.WithMany("ReactionRoles")
|
||||
.HasForeignKey("ReactionRoleMessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
|
||||
.WithMany("ReactionRoleMessages")
|
||||
.HasForeignKey("GuildConfigId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("GuildConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
@@ -2676,8 +2750,6 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.Navigation("Permissions");
|
||||
|
||||
b.Navigation("ReactionRoleMessages");
|
||||
|
||||
b.Navigation("SelfAssignableRoleGroupNames");
|
||||
|
||||
b.Navigation("ShopEntries");
|
||||
@@ -2718,11 +2790,6 @@ namespace NadekoBot.Migrations
|
||||
b.Navigation("Votes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.Navigation("ReactionRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user