mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
172
.gitlab-ci.yml
172
.gitlab-ci.yml
@@ -7,6 +7,7 @@ stages:
|
|||||||
- release
|
- release
|
||||||
- publish-windows
|
- publish-windows
|
||||||
- upload-windows-updater-release
|
- upload-windows-updater-release
|
||||||
|
- publish-medusa-package
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
project: "NadekoBot"
|
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}"
|
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_OUTPUT_DIR: "nadeko-installers/${CI_COMMIT_TAG}"
|
||||||
INSTALLER_FILE_NAME: "nadeko-setup-${CI_COMMIT_TAG}.exe"
|
INSTALLER_FILE_NAME: "nadeko-setup-${CI_COMMIT_TAG}.exe"
|
||||||
|
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- "dotnet publish -c Release -r linux-x64 -o $LINUX_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 -o $WIN_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:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- "$LINUX_X64_OUTPUT_DIR/"
|
- "$LINUX_X64_OUTPUT_DIR/"
|
||||||
- "$WIN_X64_OUTPUT_DIR/"
|
- "$WIN_X64_OUTPUT_DIR/"
|
||||||
|
|
||||||
upload-builds:
|
upload-builds:
|
||||||
stage: upload-builds
|
stage: upload-builds
|
||||||
image: alpine:latest
|
image: alpine:latest
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_TAG
|
- if: $CI_COMMIT_TAG
|
||||||
script:
|
script:
|
||||||
- apk add --no-cache curl tar zip
|
- apk add --no-cache curl tar zip
|
||||||
- "tar cvf $LINUX_X64_RELEASE $LINUX_X64_OUTPUT_DIR/*"
|
- "tar cvf $LINUX_X64_RELEASE $LINUX_X64_OUTPUT_DIR/*"
|
||||||
- "zip -r $WIN_X64_RELEASE $WIN_X64_OUTPUT_DIR/*"
|
- "zip -r $WIN_X64_RELEASE $WIN_X64_OUTPUT_DIR/*"
|
||||||
- |
|
- |
|
||||||
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $LINUX_X64_RELEASE $PACKAGE_REGISTRY_URL/$LINUX_X64_RELEASE
|
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $LINUX_X64_RELEASE $PACKAGE_REGISTRY_URL/$LINUX_X64_RELEASE
|
||||||
- |
|
- |
|
||||||
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $WIN_X64_RELEASE $PACKAGE_REGISTRY_URL/$WIN_X64_RELEASE
|
curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file $WIN_X64_RELEASE $PACKAGE_REGISTRY_URL/$WIN_X64_RELEASE
|
||||||
|
|
||||||
release:
|
release:
|
||||||
stage: release
|
stage: release
|
||||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_TAG
|
- if: $CI_COMMIT_TAG
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
release-cli create --name "NadekoBot v$CI_COMMIT_TAG" --description "## [Changelog](https://gitlab.com/Kwoth/nadekobot/-/blob/v4/CHANGELOG.md#$(echo "$CI_COMMIT_TAG" | sed "s/\.//g")-$(date +%d%m%Y))" --tag-name $CI_COMMIT_TAG \
|
release-cli create --name "NadekoBot v$CI_COMMIT_TAG" --description "## [Changelog](https://gitlab.com/Kwoth/nadekobot/-/blob/v4/CHANGELOG.md#$(echo "$CI_COMMIT_TAG" | sed "s/\.//g")-$(date +%d%m%Y))" --tag-name $CI_COMMIT_TAG \
|
||||||
--assets-link "{\"name\":\"${LINUX_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_X64_RELEASE}\"}" \
|
--assets-link "{\"name\":\"${LINUX_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${LINUX_X64_RELEASE}\"}" \
|
||||||
--assets-link "{\"name\":\"${WIN_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${WIN_X64_RELEASE}\"}"
|
--assets-link "{\"name\":\"${WIN_X64_RELEASE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${WIN_X64_RELEASE}\"}"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
@@ -63,63 +64,74 @@ test:
|
|||||||
- "dotnet test"
|
- "dotnet test"
|
||||||
|
|
||||||
publish-windows:
|
publish-windows:
|
||||||
stage: publish-windows
|
stage: publish-windows
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_TAG'
|
- if: "$CI_COMMIT_TAG"
|
||||||
image: scottyhardy/docker-wine
|
image: scottyhardy/docker-wine
|
||||||
before_script:
|
before_script:
|
||||||
- choco install dotnet-6.0-runtime -y
|
- choco install dotnet-6.0-runtime --version=6.0.4 -y
|
||||||
- choco install dotnet-6.0-sdk -y
|
- choco install dotnet-6.0-sdk --version=6.0.202 -y
|
||||||
- choco install innosetup -y
|
- choco install innosetup -y
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
- "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
||||||
script:
|
script:
|
||||||
- dotnet clean
|
- dotnet clean
|
||||||
- dotnet restore
|
- dotnet restore -f --no-cache -v n
|
||||||
- dotnet publish -c Release --runtime win7-x64 /p:Version=$CI_COMMIT_TAG src/NadekoBot
|
- dotnet publish -c Release --runtime win7-x64 /p:Version=$CI_COMMIT_TAG src/NadekoBot
|
||||||
- $env:NADEKOBOT_INSTALL_VERSION = $CI_COMMIT_TAG
|
- $env:NADEKOBOT_INSTALL_VERSION = $CI_COMMIT_TAG
|
||||||
- iscc.exe "/O+" ".\exe_builder.iss"
|
- iscc.exe "/O+" ".\exe_builder.iss"
|
||||||
tags:
|
tags:
|
||||||
- windows
|
- windows
|
||||||
|
|
||||||
upload-windows-updater-release:
|
upload-windows-updater-release:
|
||||||
stage: upload-windows-updater-release
|
stage: upload-windows-updater-release
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_TAG'
|
- if: "$CI_COMMIT_TAG"
|
||||||
image:
|
image:
|
||||||
name: amazon/aws-cli
|
name: amazon/aws-cli
|
||||||
entrypoint: [""]
|
entrypoint: [""]
|
||||||
script:
|
script:
|
||||||
- sed -i "s/_INSTALLER_FILE_NAME_/$INSTALLER_FILE_NAME/g" releases-v3.json
|
- sed -i "s/_INSTALLER_FILE_NAME_/$INSTALLER_FILE_NAME/g" releases-v3.json
|
||||||
- sed -i "s/_VERSION_/$CI_COMMIT_TAG/g" releases-v3.json
|
- sed -i "s/_VERSION_/$CI_COMMIT_TAG/g" releases-v3.json
|
||||||
- aws --version
|
- aws --version
|
||||||
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/$INSTALLER_FILE_NAME" --acl public-read --body "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/$INSTALLER_FILE_NAME" --acl public-read --body "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
|
||||||
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/releases-v3.json" --acl public-read --body "releases-v3.json"
|
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/releases-v3.json" --acl public-read --body "releases-v3.json"
|
||||||
|
|
||||||
|
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:
|
docker-build:
|
||||||
# Use the official docker image.
|
# Use the official docker image.
|
||||||
image: docker:latest
|
image: docker:latest
|
||||||
stage: build
|
stage: build
|
||||||
services:
|
services:
|
||||||
- docker:dind
|
- docker:dind
|
||||||
before_script:
|
before_script:
|
||||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||||
# Default branch leaves tag empty (= latest tag)
|
# Default branch leaves tag empty (= latest tag)
|
||||||
# All other branches are tagged with the escaped branch name (commit ref slug)
|
# All other branches are tagged with the escaped branch name (commit ref slug)
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
|
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
|
||||||
tag=""
|
tag=""
|
||||||
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
|
||||||
else
|
else
|
||||||
tag=":$CI_COMMIT_REF_SLUG"
|
tag=":$CI_COMMIT_SHA"
|
||||||
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
||||||
fi
|
fi
|
||||||
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
|
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
|
||||||
- docker push "$CI_REGISTRY_IMAGE${tag}"
|
- docker push "$CI_REGISTRY_IMAGE${tag}"
|
||||||
# Run this job in a branch where a Dockerfile exists
|
# Run this job in a branch where a Dockerfile exists
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_BRANCH
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
|
||||||
exists:
|
exists:
|
||||||
- Dockerfile
|
- Dockerfile
|
||||||
|
174
CHANGELOG.md
174
CHANGELOG.md
@@ -3,6 +3,178 @@
|
|||||||
|
|
||||||
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||||
|
|
||||||
|
## [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
|
## [4.1.3] - 06.05.2022
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -44,7 +216,7 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
|
|||||||
|
|
||||||
- Fixed `.deletexp` command
|
- Fixed `.deletexp` command
|
||||||
- `.give` command should send DMs again
|
- `.give` command should send DMs again
|
||||||
- `.modules` commanad now has a medusa module description
|
- `.modules` command now has a medusa module description
|
||||||
|
|
||||||
## [4.1.2] - 16.04.2022
|
## [4.1.2] - 16.04.2022
|
||||||
|
|
||||||
|
10
Dockerfile
10
Dockerfile
@@ -25,12 +25,14 @@ WORKDIR /app
|
|||||||
RUN set -xe; \
|
RUN set -xe; \
|
||||||
useradd -m nadeko; \
|
useradd -m nadeko; \
|
||||||
apt-get update; \
|
apt-get update; \
|
||||||
apt-get install -y libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 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; \
|
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; \
|
echo 'Defaults>nadeko env_keep+="ASPNETCORE_* DOTNET_* NadekoBot_* shard_id total_shards TZ"' > /etc/sudoers.d/nadeko; \
|
||||||
pip3 install --upgrade youtube-dl; \
|
pip3 install --no-cache-dir --upgrade youtube-dl; \
|
||||||
apt-get remove -y python3-pip; \
|
apt-get purge -y python3-pip; \
|
||||||
chmod +x /usr/local/bin/youtube-dl
|
chmod +x /usr/local/bin/youtube-dl; \
|
||||||
|
apt-get autoremove -y; \
|
||||||
|
apt-get autoclean -y
|
||||||
|
|
||||||
COPY --from=build /app ./
|
COPY --from=build /app ./
|
||||||
COPY docker-entrypoint.sh /usr/local/sbin
|
COPY docker-entrypoint.sh /usr/local/sbin
|
||||||
|
@@ -1,23 +1,23 @@
|
|||||||
## Custom Reactions / Expressions
|
## Expressions
|
||||||
|
|
||||||
### Important
|
### 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.
|
- 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 reactions in a direct message with the bot.
|
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.
|
- 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 reactions in the server you want the custom reactions to work on.
|
You must also use the commands for adding, deleting and listing these reactions in the server you want the expressions to work on.
|
||||||
|
|
||||||
### Commands and Their Use
|
### Commands and Their Use
|
||||||
|
|
||||||
| Command Name | Description | Example |
|
| 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%!` |
|
| `.exadd` | 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%!` |
|
||||||
| `.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` |
|
| `exl` | Lists a page of global or server expression(15 reactions / 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` |
|
||||||
| `.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` |
|
| `.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 `.acr`,
|
||||||
|
|
||||||
`.acr "Nice Weather" It sure is, %user%!`
|
`.exadd "Nice Weather" It sure is, %user%!`
|
||||||
|
|
||||||
This command can be split into two different arguments:
|
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.
|
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:
|
For example:
|
||||||
`.acr /o/ -`
|
`.acr /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!
|
### Placeholders!
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ Donating to us also gives you the following benefits:
|
|||||||
- A hoisted **Donators role** in our [Discord server][discord-server]
|
- A hoisted **Donators role** in our [Discord server][discord-server]
|
||||||
- Access to exclusive **#noticed** text and voice channels
|
- Access to exclusive **#noticed** text and voice channels
|
||||||
- **1000 flowers** on the public bot per dollar donated (after fees)
|
- **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
|
## Patreon
|
||||||
|
|
||||||
|
@@ -17,8 +17,6 @@ To self-host your own Nadeko, use the guides below:
|
|||||||
- [:material-linux: Linux guide][linux-guide]
|
- [:material-linux: Linux guide][linux-guide]
|
||||||
- [:material-apple: Mac OS guide][macos-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.
|
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`
|
2. `.rm NSFW enable Lewd`
|
||||||
- Enables usage of the NSFW module for the Lewd role
|
- 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`
|
1. `.sm Expressions disable`
|
||||||
- Disables the ActualCustomReactions module from being used
|
- Disables the ActualCustomReactions 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?
|
#### I've broken permissions and am stuck, can I reset permissions?
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Placeholders
|
# 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.
|
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
|
### Miscellaneous placeholders
|
||||||
|
|
||||||
- `%rngX-Y%` - Returns a random number between X and Y
|
- `%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)
|
- `%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 custom reactions)
|
- `%img:stuff%` - Returns an `imgur.com` search for "stuff" (only works on Expressions)
|
||||||
|
|
||||||

|

|
||||||
|
@@ -5,7 +5,7 @@ namespace NadekoBot;
|
|||||||
public interface IEmbedBuilder
|
public interface IEmbedBuilder
|
||||||
{
|
{
|
||||||
IEmbedBuilder WithDescription(string? desc);
|
IEmbedBuilder WithDescription(string? desc);
|
||||||
IEmbedBuilder WithTitle(string title);
|
IEmbedBuilder WithTitle(string? title);
|
||||||
IEmbedBuilder AddField(string title, object value, bool isInline = false);
|
IEmbedBuilder AddField(string title, object value, bool isInline = false);
|
||||||
IEmbedBuilder WithFooter(string text, string? iconUrl = null);
|
IEmbedBuilder WithFooter(string text, string? iconUrl = null);
|
||||||
IEmbedBuilder WithAuthor(string name, string? iconUrl = null, string? url = null);
|
IEmbedBuilder WithAuthor(string name, string? iconUrl = null, string? url = null);
|
||||||
|
@@ -9,12 +9,11 @@
|
|||||||
<RootNamespace>Nadeko.Snake</RootNamespace>
|
<RootNamespace>Nadeko.Snake</RootNamespace>
|
||||||
|
|
||||||
<Authors>The NadekoBot Team</Authors>
|
<Authors>The NadekoBot Team</Authors>
|
||||||
<Version>1.0.2</Version>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Discord.Net.Core" Version="3.5.0" />
|
<PackageReference Include="Discord.Net.Core" Version="3.6.1" />
|
||||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@@ -9,10 +9,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.AspNetCore" Version="2.44.0" />
|
<PackageReference Include="Grpc.AspNetCore" Version="2.45.0" />
|
||||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
<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" />
|
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@@ -8,9 +8,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
<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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
|
<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>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -4,9 +4,11 @@ using NadekoBot.Common.Configs;
|
|||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using NadekoBot.Modules.Administration;
|
using NadekoBot.Modules.Administration;
|
||||||
|
using NadekoBot.Modules.Utility;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using RunMode = Discord.Commands.RunMode;
|
using RunMode = Discord.Commands.RunMode;
|
||||||
|
|
||||||
@@ -125,6 +127,12 @@ public sealed class Bot
|
|||||||
{
|
{
|
||||||
AllowAutoRedirect = false
|
AllowAutoRedirect = false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
svcs.AddHttpClient("google:search")
|
||||||
|
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
|
||||||
|
{
|
||||||
|
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
|
||||||
|
});
|
||||||
|
|
||||||
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
|
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
|
||||||
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
|
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
|
||||||
@@ -164,6 +172,7 @@ public sealed class Bot
|
|||||||
//initialize Services
|
//initialize Services
|
||||||
Services = svcs.BuildServiceProvider();
|
Services = svcs.BuildServiceProvider();
|
||||||
Services.GetRequiredService<IBehaviorHandler>().Initialize();
|
Services.GetRequiredService<IBehaviorHandler>().Initialize();
|
||||||
|
Services.GetRequiredService<CurrencyRewardService>();
|
||||||
|
|
||||||
if (Client.ShardId == 0)
|
if (Client.ShardId == 0)
|
||||||
ApplyConfigMigrations();
|
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>
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||||
/// Classed marked with this attribute will not be added to the service provider
|
[SuppressMessage("Style", "IDE0022:Use expression body for methods")]
|
||||||
/// </summary>
|
public sealed class OnlyPublicBotAttribute : PreconditionAttribute
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
|
||||||
public class DontAddToIocContainerAttribute : Attribute
|
|
||||||
{
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
@@ -18,7 +18,7 @@ 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")]
|
[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; }
|
public bool UsePrivilegedIntents { get; set; }
|
||||||
|
|
||||||
[Comment(@"The number of shards that the bot will running on.
|
[Comment(@"The number of shards that the bot will be running on.
|
||||||
Leave at 1 if you don't know what you're doing.")]
|
Leave at 1 if you don't know what you're doing.")]
|
||||||
public int TotalShards { get; set; }
|
public int TotalShards { get; set; }
|
||||||
|
|
||||||
@@ -27,6 +27,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.
|
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
||||||
Used only for Youtube Data Api (at the moment).")]
|
Used only for Youtube Data Api (at the moment).")]
|
||||||
public string GoogleApiKey { get; set; }
|
public string GoogleApiKey { get; set; }
|
||||||
|
|
||||||
|
[Comment(
|
||||||
|
@"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.")]
|
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
||||||
public VotesSettings Votes { get; set; }
|
public VotesSettings Votes { get; set; }
|
||||||
@@ -119,6 +129,7 @@ Windows default
|
|||||||
CoordinatorUrl = "http://localhost:3442";
|
CoordinatorUrl = "http://localhost:3442";
|
||||||
|
|
||||||
RestartCommand = new();
|
RestartCommand = new();
|
||||||
|
Google = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -200,4 +211,10 @@ This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsett
|
|||||||
DiscordsKey = discordsKey;
|
DiscordsKey = discordsKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GoogleApiConfig
|
||||||
|
{
|
||||||
|
public string SearchId { get; init; }
|
||||||
|
public string ImageSearchId { get; init; }
|
||||||
}
|
}
|
@@ -25,6 +25,7 @@ public interface IBotCredentials
|
|||||||
string CoordinatorUrl { get; set; }
|
string CoordinatorUrl { get; set; }
|
||||||
string TwitchClientId { get; set; }
|
string TwitchClientId { get; set; }
|
||||||
string TwitchClientSecret { get; set; }
|
string TwitchClientSecret { get; set; }
|
||||||
|
GoogleApiConfig Google { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RestartConfig
|
public class RestartConfig
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
|
|
||||||
public sealed class NadekoActionInteraction : NadekoOwnInteraction
|
public sealed class NadekoButtonActionInteraction : NadekoButtonOwnInteraction
|
||||||
{
|
{
|
||||||
private readonly NadekoInteractionData _data;
|
private readonly NadekoInteractionData _data;
|
||||||
private readonly Func<SocketMessageComponent, Task> _action;
|
private readonly Func<SocketMessageComponent, Task> _action;
|
||||||
|
|
||||||
public NadekoActionInteraction(
|
public NadekoButtonActionInteraction(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
ulong authorId,
|
ulong authorId,
|
||||||
NadekoInteractionData data,
|
NadekoInteractionData data,
|
||||||
@@ -17,10 +17,12 @@ public sealed class NadekoActionInteraction : NadekoOwnInteraction
|
|||||||
_action = action;
|
_action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Name
|
protected override string Name
|
||||||
=> _data.CustomId;
|
=> _data.CustomId;
|
||||||
public override IEmote Emote
|
protected override IEmote Emote
|
||||||
=> _data.Emote;
|
=> _data.Emote;
|
||||||
|
protected override string? Text
|
||||||
|
=> _data.Text;
|
||||||
|
|
||||||
public override Task ExecuteOnActionAsync(SocketMessageComponent smc)
|
public override Task ExecuteOnActionAsync(SocketMessageComponent smc)
|
||||||
=> _action(smc);
|
=> _action(smc);
|
||||||
|
@@ -1,23 +1,24 @@
|
|||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
|
|
||||||
public abstract class NadekoInteraction
|
public abstract class NadekoButtonInteraction
|
||||||
{
|
{
|
||||||
// improvements:
|
// improvements:
|
||||||
// - state in OnAction
|
// - state in OnAction
|
||||||
// - configurable delay
|
// - configurable delay
|
||||||
// -
|
// -
|
||||||
public abstract string Name { get; }
|
protected abstract string Name { get; }
|
||||||
public abstract IEmote Emote { get; }
|
protected abstract IEmote Emote { get; }
|
||||||
|
protected virtual string? Text { get; } = null;
|
||||||
|
|
||||||
protected readonly DiscordSocketClient _client;
|
public DiscordSocketClient Client { get; }
|
||||||
|
|
||||||
protected readonly TaskCompletionSource<bool> _interactionCompletedSource;
|
protected readonly TaskCompletionSource<bool> _interactionCompletedSource;
|
||||||
|
|
||||||
protected IUserMessage message = null!;
|
protected IUserMessage message = null!;
|
||||||
|
|
||||||
protected NadekoInteraction(DiscordSocketClient client)
|
protected NadekoButtonInteraction(DiscordSocketClient client)
|
||||||
{
|
{
|
||||||
_client = client;
|
Client = client;
|
||||||
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,9 +26,9 @@ public abstract class NadekoInteraction
|
|||||||
{
|
{
|
||||||
message = msg;
|
message = msg;
|
||||||
|
|
||||||
_client.InteractionCreated += OnInteraction;
|
Client.InteractionCreated += OnInteraction;
|
||||||
await Task.WhenAny(Task.Delay(10_000), _interactionCompletedSource.Task);
|
await Task.WhenAny(Task.Delay(10_000), _interactionCompletedSource.Task);
|
||||||
_client.InteractionCreated -= OnInteraction;
|
Client.InteractionCreated -= OnInteraction;
|
||||||
|
|
||||||
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
|
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
|
||||||
}
|
}
|
||||||
@@ -65,13 +66,18 @@ public abstract class NadekoInteraction
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public MessageComponent CreateComponent()
|
public virtual MessageComponent CreateComponent()
|
||||||
{
|
{
|
||||||
var comp = new ComponentBuilder()
|
var comp = new ComponentBuilder()
|
||||||
.WithButton(new ButtonBuilder(style: ButtonStyle.Secondary, emote: Emote, customId: Name));
|
.WithButton(GetButtonBuilder());
|
||||||
|
|
||||||
return comp.Build();
|
return comp.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ButtonBuilder GetButtonBuilder()
|
||||||
|
=> new ButtonBuilder(style: ButtonStyle.Secondary, emote: Emote, customId: Name, label: Text);
|
||||||
|
|
||||||
public abstract Task ExecuteOnActionAsync(SocketMessageComponent smc);
|
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();
|
||||||
|
// }
|
||||||
|
// }
|
@@ -20,6 +20,7 @@ public class NadekoInteractionBuilder
|
|||||||
// {
|
// {
|
||||||
// this.isOwn = isOwn;
|
// this.isOwn = isOwn;
|
||||||
// return this;
|
// return this;
|
||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
public NadekoInteractionBuilder WithAction(in Func<SocketMessageComponent, Task> fn)
|
public NadekoInteractionBuilder WithAction(in Func<SocketMessageComponent, Task> fn)
|
||||||
@@ -28,7 +29,7 @@ public class NadekoInteractionBuilder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NadekoActionInteraction Build(DiscordSocketClient client, ulong userId)
|
public NadekoButtonActionInteraction Build(DiscordSocketClient client, ulong userId)
|
||||||
{
|
{
|
||||||
if (iData is null)
|
if (iData is null)
|
||||||
throw new InvalidOperationException("You have to specify the data before building the interaction");
|
throw new InvalidOperationException("You have to specify the data before building the interaction");
|
||||||
|
@@ -5,4 +5,4 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Emote">Emote which will show on a button</param>
|
/// <param name="Emote">Emote which will show on a button</param>
|
||||||
/// <param name="CustomId">Custom interaction id</param>
|
/// <param name="CustomId">Custom interaction id</param>
|
||||||
public record NadekoInteractionData(IEmote Emote, string CustomId);
|
public record NadekoInteractionData(IEmote Emote, string CustomId, string? Text = null);
|
@@ -3,11 +3,11 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interaction which only the author can use
|
/// Interaction which only the author can use
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class NadekoOwnInteraction : NadekoInteraction
|
public abstract class NadekoButtonOwnInteraction : NadekoButtonInteraction
|
||||||
{
|
{
|
||||||
protected readonly ulong _authorId;
|
protected readonly ulong _authorId;
|
||||||
|
|
||||||
protected NadekoOwnInteraction(DiscordSocketClient client, ulong authorId) : base(client)
|
protected NadekoButtonOwnInteraction(DiscordSocketClient client, ulong authorId) : base(client)
|
||||||
=> _authorId = authorId;
|
=> _authorId = authorId;
|
||||||
|
|
||||||
protected override ValueTask<bool> Validate(SocketMessageComponent smc)
|
protected override ValueTask<bool> Validate(SocketMessageComponent smc)
|
||||||
|
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);
|
||||||
|
}
|
@@ -36,7 +36,7 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
string error,
|
string error,
|
||||||
string url = null,
|
string url = null,
|
||||||
string footer = null,
|
string footer = null,
|
||||||
NadekoInteraction inter = null)
|
NadekoButtonInteraction inter = null)
|
||||||
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
|
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
|
||||||
|
|
||||||
public Task<IUserMessage> SendConfirmAsync(
|
public Task<IUserMessage> SendConfirmAsync(
|
||||||
@@ -47,32 +47,32 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
|
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
|
||||||
|
|
||||||
//
|
//
|
||||||
public Task<IUserMessage> SendErrorAsync(string text, NadekoInteraction inter = null)
|
public Task<IUserMessage> SendErrorAsync(string text, NadekoButtonInteraction inter = null)
|
||||||
=> ctx.Channel.SendAsync(_eb, text, MessageType.Error, inter);
|
=> ctx.Channel.SendAsync(_eb, text, MessageType.Error, inter);
|
||||||
public Task<IUserMessage> SendConfirmAsync(string text, NadekoInteraction inter = null)
|
public Task<IUserMessage> SendConfirmAsync(string text, NadekoButtonInteraction inter = null)
|
||||||
=> ctx.Channel.SendAsync(_eb, text, MessageType.Ok, inter);
|
=> ctx.Channel.SendAsync(_eb, text, MessageType.Ok, inter);
|
||||||
public Task<IUserMessage> SendPendingAsync(string text, NadekoInteraction inter = null)
|
public Task<IUserMessage> SendPendingAsync(string text, NadekoButtonInteraction inter = null)
|
||||||
=> ctx.Channel.SendAsync(_eb, text, MessageType.Pending, inter);
|
=> ctx.Channel.SendAsync(_eb, text, MessageType.Pending, inter);
|
||||||
|
|
||||||
|
|
||||||
// localized normal
|
// localized normal
|
||||||
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||||
=> SendErrorAsync(GetText(str), inter);
|
=> SendErrorAsync(GetText(str), inter);
|
||||||
|
|
||||||
public Task<IUserMessage> PendingLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
public Task<IUserMessage> PendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||||
=> SendPendingAsync(GetText(str), inter);
|
=> SendPendingAsync(GetText(str), inter);
|
||||||
|
|
||||||
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||||
=> SendConfirmAsync(GetText(str), inter);
|
=> SendConfirmAsync(GetText(str), inter);
|
||||||
|
|
||||||
// localized replies
|
// localized replies
|
||||||
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||||
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||||
|
|
||||||
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||||
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||||
|
|
||||||
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
|
||||||
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||||
|
|
||||||
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
using SixLabors.ImageSharp.PixelFormats;
|
#nullable disable warnings
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
|
|
||||||
public sealed record SmartEmbedArrayElementText : SmartEmbedTextBase
|
public sealed record SmartEmbedArrayElementText : SmartEmbedTextBase
|
||||||
@@ -37,11 +37,11 @@ public sealed record SmartEmbedText : SmartEmbedTextBase
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private SmartEmbedText(IEmbed eb, string plainText = null)
|
private SmartEmbedText(IEmbed eb, string? plainText = null)
|
||||||
: base(eb)
|
: base(eb)
|
||||||
=> (PlainText, Color) = (plainText, eb.Color?.RawValue ?? 0);
|
=> (PlainText, Color) = (plainText, eb.Color?.RawValue ?? 0);
|
||||||
|
|
||||||
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
|
public static SmartEmbedText FromEmbed(IEmbed eb, string? plainText = null)
|
||||||
=> new(eb, plainText);
|
=> new(eb, plainText);
|
||||||
|
|
||||||
protected override EmbedBuilder GetEmbedInternal()
|
protected override EmbedBuilder GetEmbedInternal()
|
||||||
|
@@ -14,7 +14,10 @@ public sealed record SmartEmbedTextArray : SmartText
|
|||||||
if (Embeds is null)
|
if (Embeds is null)
|
||||||
return Array.Empty<EmbedBuilder>();
|
return Array.Empty<EmbedBuilder>();
|
||||||
|
|
||||||
return Embeds.Map(em => em.GetEmbed());
|
return Embeds
|
||||||
|
.Where(x => x.IsValid)
|
||||||
|
.Select(em => em.GetEmbed())
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NormalizeFields()
|
public void NormalizeFields()
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
@@ -45,7 +44,6 @@ public abstract record SmartText
|
|||||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||||
};
|
};
|
||||||
|
|
||||||
[CanBeNull]
|
|
||||||
public static SmartText CreateFrom(string input)
|
public static SmartText CreateFrom(string input)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
@@ -70,7 +68,7 @@ public abstract record SmartText
|
|||||||
|
|
||||||
var obj = root.ToObject<SmartEmbedText>();
|
var obj = root.ToObject<SmartEmbedText>();
|
||||||
|
|
||||||
if (obj is null)
|
if (obj is null || !(obj.IsValid || !string.IsNullOrWhiteSpace(obj.PlainText)))
|
||||||
return new SmartPlainText(input);
|
return new SmartPlainText(input);
|
||||||
|
|
||||||
obj.NormalizeFields();
|
obj.NormalizeFields();
|
||||||
|
@@ -3,6 +3,8 @@ using NadekoBot.Services.Database.Models;
|
|||||||
|
|
||||||
namespace NadekoBot.Db.Models;
|
namespace NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
|
||||||
|
// FUTURE remove LastLevelUp from here and UserXpStats
|
||||||
public class DiscordUser : DbEntity
|
public class DiscordUser : DbEntity
|
||||||
{
|
{
|
||||||
public ulong UserId { get; set; }
|
public ulong UserId { get; set; }
|
||||||
|
@@ -84,7 +84,7 @@ public class GuildConfig : DbEntity
|
|||||||
|
|
||||||
public List<ShopEntry> ShopEntries { get; set; }
|
public List<ShopEntry> ShopEntries { get; set; }
|
||||||
public ulong? GameVoiceChannel { get; set; }
|
public ulong? GameVoiceChannel { get; set; }
|
||||||
public bool VerboseErrors { get; set; }
|
public bool VerboseErrors { get; set; } = true;
|
||||||
|
|
||||||
public StreamRoleSettings StreamRole { get; set; }
|
public StreamRoleSettings StreamRole { get; set; }
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
@@ -4,7 +4,7 @@ namespace NadekoBot.Services.Database.Models;
|
|||||||
public class RewardedUser : DbEntity
|
public class RewardedUser : DbEntity
|
||||||
{
|
{
|
||||||
public ulong UserId { get; set; }
|
public ulong UserId { get; set; }
|
||||||
public string PatreonUserId { get; set; }
|
public string PlatformUserId { get; set; }
|
||||||
public int AmountRewardedThisMonth { get; set; }
|
public long AmountRewardedThisMonth { get; set; }
|
||||||
public DateTime LastReward { get; set; }
|
public DateTime LastReward { get; set; }
|
||||||
}
|
}
|
@@ -1,6 +1,5 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
@@ -26,7 +25,7 @@ public abstract class NadekoContext : DbContext
|
|||||||
public DbSet<ClubInfo> Clubs { get; set; }
|
public DbSet<ClubInfo> Clubs { get; set; }
|
||||||
public DbSet<ClubBans> ClubBans { get; set; }
|
public DbSet<ClubBans> ClubBans { get; set; }
|
||||||
public DbSet<ClubApplicants> ClubApplicants { get; set; }
|
public DbSet<ClubApplicants> ClubApplicants { get; set; }
|
||||||
|
|
||||||
|
|
||||||
//logging
|
//logging
|
||||||
public DbSet<LogSetting> LogSettings { get; set; }
|
public DbSet<LogSetting> LogSettings { get; set; }
|
||||||
@@ -51,19 +50,23 @@ public abstract class NadekoContext : DbContext
|
|||||||
public DbSet<AutoTranslateUser> AutoTranslateUsers { get; set; }
|
public DbSet<AutoTranslateUser> AutoTranslateUsers { get; set; }
|
||||||
|
|
||||||
public DbSet<Permissionv2> Permissions { get; set; }
|
public DbSet<Permissionv2> Permissions { get; set; }
|
||||||
|
|
||||||
public DbSet<BankUser> BankUsers { get; set; }
|
public DbSet<BankUser> BankUsers { get; set; }
|
||||||
|
|
||||||
public DbSet<ReactionRoleV2> ReactionRoles { get; set; }
|
public DbSet<ReactionRoleV2> ReactionRoles { get; set; }
|
||||||
|
|
||||||
|
public DbSet<PatronUser> Patrons { get; set; }
|
||||||
|
|
||||||
|
public DbSet<PatronQuota> PatronQuotas { get; set; }
|
||||||
|
|
||||||
#region Mandatory Provider-Specific Values
|
#region Mandatory Provider-Specific Values
|
||||||
|
|
||||||
protected abstract string CurrencyTransactionOtherIdDefaultValue { get; }
|
protected abstract string CurrencyTransactionOtherIdDefaultValue { get; }
|
||||||
protected abstract string DiscordUserLastXpGainDefaultValue { get; }
|
protected abstract string DiscordUserLastXpGainDefaultValue { get; }
|
||||||
protected abstract string LastLevelUpDefaultValue { get; }
|
protected abstract string LastLevelUpDefaultValue { get; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#region QUOTES
|
#region QUOTES
|
||||||
@@ -77,7 +80,11 @@ public abstract class NadekoContext : DbContext
|
|||||||
#region GuildConfig
|
#region GuildConfig
|
||||||
|
|
||||||
var configEntity = modelBuilder.Entity<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);
|
modelBuilder.Entity<AntiSpamSetting>().HasOne(x => x.GuildConfig).WithOne(x => x.AntiSpamSetting);
|
||||||
|
|
||||||
@@ -193,13 +200,6 @@ public abstract class NadekoContext : DbContext
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region PatreonRewards
|
|
||||||
|
|
||||||
var pr = modelBuilder.Entity<RewardedUser>();
|
|
||||||
pr.HasIndex(x => x.PatreonUserId).IsUnique();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region XpStats
|
#region XpStats
|
||||||
|
|
||||||
var xps = modelBuilder.Entity<UserXpStats>();
|
var xps = modelBuilder.Entity<UserXpStats>();
|
||||||
@@ -369,12 +369,13 @@ public abstract class NadekoContext : DbContext
|
|||||||
.IsUnique(false);
|
.IsUnique(false);
|
||||||
|
|
||||||
rr2.HasIndex(x => new
|
rr2.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.MessageId,
|
x.MessageId,
|
||||||
x.Emote
|
x.Emote
|
||||||
}).IsUnique();
|
})
|
||||||
|
.IsUnique();
|
||||||
});
|
});
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region LogSettings
|
#region LogSettings
|
||||||
@@ -419,7 +420,37 @@ public abstract class NadekoContext : DbContext
|
|||||||
modelBuilder.Entity<BankUser>(bu => bu.HasIndex(x => x.UserId).IsUnique());
|
modelBuilder.Entity<BankUser>(bu => bu.HasIndex(x => x.UserId).IsUnique());
|
||||||
|
|
||||||
#endregion
|
#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
|
#if DEBUG
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -16,7 +16,7 @@ namespace NadekoBot.Migrations.Mysql
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "6.0.4")
|
.HasAnnotation("ProductVersion", "6.0.5")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||||
@@ -186,10 +186,10 @@ namespace NadekoBot.Migrations.Mysql
|
|||||||
.HasDefaultValue(0)
|
.HasDefaultValue(0)
|
||||||
.HasColumnName("notifyonlevelup");
|
.HasColumnName("notifyonlevelup");
|
||||||
|
|
||||||
b.Property<int>("TotalXp")
|
b.Property<long>("TotalXp")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("int")
|
.HasColumnType("bigint")
|
||||||
.HasDefaultValue(0)
|
.HasDefaultValue(0L)
|
||||||
.HasColumnName("totalxp");
|
.HasColumnName("totalxp");
|
||||||
|
|
||||||
b.Property<ulong>("UserId")
|
b.Property<ulong>("UserId")
|
||||||
@@ -265,6 +265,74 @@ namespace NadekoBot.Migrations.Mysql
|
|||||||
b.ToTable("followedstream", (string)null);
|
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.Services.Database.Models.AntiAltSetting", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1138,7 +1206,9 @@ namespace NadekoBot.Migrations.Mysql
|
|||||||
.HasColumnName("timezoneid");
|
.HasColumnName("timezoneid");
|
||||||
|
|
||||||
b.Property<bool>("VerboseErrors")
|
b.Property<bool>("VerboseErrors")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("tinyint(1)")
|
.HasColumnType("tinyint(1)")
|
||||||
|
.HasDefaultValue(true)
|
||||||
.HasColumnName("verboseerrors");
|
.HasColumnName("verboseerrors");
|
||||||
|
|
||||||
b.Property<bool>("VerbosePermissions")
|
b.Property<bool>("VerbosePermissions")
|
||||||
@@ -1962,8 +2032,8 @@ namespace NadekoBot.Migrations.Mysql
|
|||||||
.HasColumnType("int")
|
.HasColumnType("int")
|
||||||
.HasColumnName("id");
|
.HasColumnName("id");
|
||||||
|
|
||||||
b.Property<int>("AmountRewardedThisMonth")
|
b.Property<long>("AmountRewardedThisMonth")
|
||||||
.HasColumnType("int")
|
.HasColumnType("bigint")
|
||||||
.HasColumnName("amountrewardedthismonth");
|
.HasColumnName("amountrewardedthismonth");
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
@@ -1974,9 +2044,9 @@ namespace NadekoBot.Migrations.Mysql
|
|||||||
.HasColumnType("datetime(6)")
|
.HasColumnType("datetime(6)")
|
||||||
.HasColumnName("lastreward");
|
.HasColumnName("lastreward");
|
||||||
|
|
||||||
b.Property<string>("PatreonUserId")
|
b.Property<string>("PlatformUserId")
|
||||||
.HasColumnType("varchar(255)")
|
.HasColumnType("varchar(255)")
|
||||||
.HasColumnName("patreonuserid");
|
.HasColumnName("platformuserid");
|
||||||
|
|
||||||
b.Property<ulong>("UserId")
|
b.Property<ulong>("UserId")
|
||||||
.HasColumnType("bigint unsigned")
|
.HasColumnType("bigint unsigned")
|
||||||
@@ -1985,9 +2055,9 @@ namespace NadekoBot.Migrations.Mysql
|
|||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_rewardedusers");
|
.HasName("pk_rewardedusers");
|
||||||
|
|
||||||
b.HasIndex("PatreonUserId")
|
b.HasIndex("PlatformUserId")
|
||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasDatabaseName("ix_rewardedusers_patreonuserid");
|
.HasDatabaseName("ix_rewardedusers_platformuserid");
|
||||||
|
|
||||||
b.ToTable("rewardedusers", (string)null);
|
b.ToTable("rewardedusers", (string)null);
|
||||||
});
|
});
|
||||||
@@ -2404,8 +2474,8 @@ namespace NadekoBot.Migrations.Mysql
|
|||||||
.HasColumnType("int")
|
.HasColumnType("int")
|
||||||
.HasColumnName("id");
|
.HasColumnName("id");
|
||||||
|
|
||||||
b.Property<int>("AwardedXp")
|
b.Property<long>("AwardedXp")
|
||||||
.HasColumnType("int")
|
.HasColumnType("bigint")
|
||||||
.HasColumnName("awardedxp");
|
.HasColumnName("awardedxp");
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
@@ -2430,8 +2500,8 @@ namespace NadekoBot.Migrations.Mysql
|
|||||||
.HasColumnType("bigint unsigned")
|
.HasColumnType("bigint unsigned")
|
||||||
.HasColumnName("userid");
|
.HasColumnName("userid");
|
||||||
|
|
||||||
b.Property<int>("Xp")
|
b.Property<long>("Xp")
|
||||||
.HasColumnType("int")
|
.HasColumnType("bigint")
|
||||||
.HasColumnName("xp");
|
.HasColumnName("xp");
|
||||||
|
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,7 +17,7 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "6.0.4")
|
.HasAnnotation("ProductVersion", "6.0.5")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -194,10 +194,10 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
.HasDefaultValue(0)
|
.HasDefaultValue(0)
|
||||||
.HasColumnName("notifyonlevelup");
|
.HasColumnName("notifyonlevelup");
|
||||||
|
|
||||||
b.Property<int>("TotalXp")
|
b.Property<long>("TotalXp")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("integer")
|
.HasColumnType("bigint")
|
||||||
.HasDefaultValue(0)
|
.HasDefaultValue(0L)
|
||||||
.HasColumnName("totalxp");
|
.HasColumnName("totalxp");
|
||||||
|
|
||||||
b.Property<decimal>("UserId")
|
b.Property<decimal>("UserId")
|
||||||
@@ -275,6 +275,74 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.ToTable("followedstream", (string)null);
|
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.Services.Database.Models.AntiAltSetting", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1194,7 +1262,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
.HasColumnName("timezoneid");
|
.HasColumnName("timezoneid");
|
||||||
|
|
||||||
b.Property<bool>("VerboseErrors")
|
b.Property<bool>("VerboseErrors")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("boolean")
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true)
|
||||||
.HasColumnName("verboseerrors");
|
.HasColumnName("verboseerrors");
|
||||||
|
|
||||||
b.Property<bool>("VerbosePermissions")
|
b.Property<bool>("VerbosePermissions")
|
||||||
@@ -2058,8 +2128,8 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<int>("AmountRewardedThisMonth")
|
b.Property<long>("AmountRewardedThisMonth")
|
||||||
.HasColumnType("integer")
|
.HasColumnType("bigint")
|
||||||
.HasColumnName("amountrewardedthismonth");
|
.HasColumnName("amountrewardedthismonth");
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
@@ -2070,9 +2140,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("lastreward");
|
.HasColumnName("lastreward");
|
||||||
|
|
||||||
b.Property<string>("PatreonUserId")
|
b.Property<string>("PlatformUserId")
|
||||||
.HasColumnType("text")
|
.HasColumnType("text")
|
||||||
.HasColumnName("patreonuserid");
|
.HasColumnName("platformuserid");
|
||||||
|
|
||||||
b.Property<decimal>("UserId")
|
b.Property<decimal>("UserId")
|
||||||
.HasColumnType("numeric(20,0)")
|
.HasColumnType("numeric(20,0)")
|
||||||
@@ -2081,9 +2151,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_rewardedusers");
|
.HasName("pk_rewardedusers");
|
||||||
|
|
||||||
b.HasIndex("PatreonUserId")
|
b.HasIndex("PlatformUserId")
|
||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasDatabaseName("ix_rewardedusers_patreonuserid");
|
.HasDatabaseName("ix_rewardedusers_platformuserid");
|
||||||
|
|
||||||
b.ToTable("rewardedusers", (string)null);
|
b.ToTable("rewardedusers", (string)null);
|
||||||
});
|
});
|
||||||
@@ -2526,8 +2596,8 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<int>("AwardedXp")
|
b.Property<long>("AwardedXp")
|
||||||
.HasColumnType("integer")
|
.HasColumnType("bigint")
|
||||||
.HasColumnName("awardedxp");
|
.HasColumnName("awardedxp");
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
@@ -2552,8 +2622,8 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
.HasColumnType("numeric(20,0)")
|
.HasColumnType("numeric(20,0)")
|
||||||
.HasColumnName("userid");
|
.HasColumnName("userid");
|
||||||
|
|
||||||
b.Property<int>("Xp")
|
b.Property<long>("Xp")
|
||||||
.HasColumnType("integer")
|
.HasColumnType("bigint")
|
||||||
.HasColumnName("xp");
|
.HasColumnName("xp");
|
||||||
|
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
|
@@ -87,7 +87,7 @@ namespace NadekoBot.Migrations
|
|||||||
name: "VoicePresenceChannelId",
|
name: "VoicePresenceChannelId",
|
||||||
table: "LogSettings");
|
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.Sql("UPDATE GuildConfigs SET LogSettingId = null WHERE LogSettingId NOT IN (SELECT Id from LogSettings)");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -15,7 +15,7 @@ namespace NadekoBot.Migrations
|
|||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.3");
|
modelBuilder.HasAnnotation("ProductVersion", "6.0.5");
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||||
{
|
{
|
||||||
@@ -149,10 +149,10 @@ namespace NadekoBot.Migrations
|
|||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasDefaultValue(0);
|
.HasDefaultValue(0);
|
||||||
|
|
||||||
b.Property<int>("TotalXp")
|
b.Property<long>("TotalXp")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasDefaultValue(0);
|
.HasDefaultValue(0L);
|
||||||
|
|
||||||
b.Property<ulong>("UserId")
|
b.Property<ulong>("UserId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
@@ -209,6 +209,59 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("FollowedStream");
|
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.Services.Database.Models.AntiAltSetting", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -890,7 +943,9 @@ namespace NadekoBot.Migrations
|
|||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<bool>("VerboseErrors")
|
b.Property<bool>("VerboseErrors")
|
||||||
.HasColumnType("INTEGER");
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
b.Property<bool>("VerbosePermissions")
|
b.Property<bool>("VerbosePermissions")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
@@ -1531,7 +1586,7 @@ namespace NadekoBot.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("AmountRewardedThisMonth")
|
b.Property<long>("AmountRewardedThisMonth")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
@@ -1540,7 +1595,7 @@ namespace NadekoBot.Migrations
|
|||||||
b.Property<DateTime>("LastReward")
|
b.Property<DateTime>("LastReward")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("PatreonUserId")
|
b.Property<string>("PlatformUserId")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<ulong>("UserId")
|
b.Property<ulong>("UserId")
|
||||||
@@ -1548,7 +1603,7 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("PatreonUserId")
|
b.HasIndex("PlatformUserId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("RewardedUsers");
|
b.ToTable("RewardedUsers");
|
||||||
@@ -1877,7 +1932,7 @@ namespace NadekoBot.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("AwardedXp")
|
b.Property<long>("AwardedXp")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
@@ -1897,7 +1952,7 @@ namespace NadekoBot.Migrations
|
|||||||
b.Property<ulong>("UserId")
|
b.Property<ulong>("UserId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("Xp")
|
b.Property<long>("Xp")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
@@ -23,6 +23,8 @@ public class DangerousCommandsService : INService
|
|||||||
// IsClubAdmin = false,
|
// IsClubAdmin = false,
|
||||||
TotalXp = 0
|
TotalXp = 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ctx.UserXpStats.DeleteAsync();
|
||||||
await ctx.ClubApplicants.DeleteAsync();
|
await ctx.ClubApplicants.DeleteAsync();
|
||||||
await ctx.ClubBans.DeleteAsync();
|
await ctx.ClubBans.DeleteAsync();
|
||||||
await ctx.Clubs.DeleteAsync();
|
await ctx.Clubs.DeleteAsync();
|
||||||
|
@@ -259,7 +259,8 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
Description = pt.Text
|
Description = pt.Text
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (text is SmartEmbedText set)
|
|
||||||
|
if (text is SmartEmbedText set)
|
||||||
{
|
{
|
||||||
text = set with
|
text = set with
|
||||||
{
|
{
|
||||||
|
@@ -23,12 +23,13 @@ public class PruneService : INService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
IMessage[] msgs;
|
IMessage[] msgs;
|
||||||
IMessage lastMessage = null;
|
IMessage lastMessage = null;
|
||||||
msgs = (await channel.GetMessagesAsync(50).FlattenAsync()).Where(predicate).Take(amount).ToArray();
|
msgs = (await channel.GetMessagesAsync(50).FlattenAsync()).Where(predicate).Take(amount).ToArray();
|
||||||
while (amount > 0 && msgs.Any())
|
while (amount > 0 && msgs.Any())
|
||||||
{
|
{
|
||||||
lastMessage = msgs[msgs.Length - 1];
|
lastMessage = msgs[^1];
|
||||||
|
|
||||||
var bulkDeletable = new List<IMessage>();
|
var bulkDeletable = new List<IMessage>();
|
||||||
var singleDeletable = new List<IMessage>();
|
var singleDeletable = new List<IMessage>();
|
||||||
@@ -36,17 +37,23 @@ public class PruneService : INService
|
|||||||
{
|
{
|
||||||
_logService.AddDeleteIgnore(x.Id);
|
_logService.AddDeleteIgnore(x.Id);
|
||||||
|
|
||||||
if (DateTime.UtcNow - x.CreatedAt < _twoWeeks)
|
if (now - x.CreatedAt < _twoWeeks)
|
||||||
bulkDeletable.Add(x);
|
bulkDeletable.Add(x);
|
||||||
else
|
else
|
||||||
singleDeletable.Add(x);
|
singleDeletable.Add(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bulkDeletable.Count > 0)
|
if (bulkDeletable.Count > 0)
|
||||||
await Task.WhenAll(Task.Delay(1000), channel.DeleteMessagesAsync(bulkDeletable));
|
{
|
||||||
|
await channel.DeleteMessagesAsync(bulkDeletable);
|
||||||
|
await Task.Delay(2000);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var group in singleDeletable.Chunk(5))
|
foreach (var group in singleDeletable.Chunk(5))
|
||||||
await Task.WhenAll(Task.Delay(5000), group.Select(x => x.DeleteAsync()).WhenAll());
|
{
|
||||||
|
await group.Select(x => x.DeleteAsync()).WhenAll();
|
||||||
|
await Task.Delay(5000);
|
||||||
|
}
|
||||||
|
|
||||||
//this isn't good, because this still work as if i want to remove only specific user's messages from the last
|
//this isn't good, because this still work as if i want to remove only specific user's messages from the last
|
||||||
//100 messages, Maybe this needs to be reduced by msgs.Length instead of 100
|
//100 messages, Maybe this needs to be reduced by msgs.Length instead of 100
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
using NadekoBot.Modules.Utility.Patronage;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
using System.Collections;
|
using OneOf;
|
||||||
|
using OneOf.Types;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Administration.Services;
|
namespace NadekoBot.Modules.Administration.Services;
|
||||||
|
|
||||||
@@ -9,18 +11,16 @@ public interface IReactionRoleService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a single reaction role
|
/// Adds a single reaction role
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="guildId"></param>
|
/// <param name="guild">Guild where to add a reaction role</param>
|
||||||
/// <param name="msg"></param>
|
/// <param name="msg">Message to which to add a reaction role</param>
|
||||||
/// <param name="channel"></param>
|
|
||||||
/// <param name="emote"></param>
|
/// <param name="emote"></param>
|
||||||
/// <param name="role"></param>
|
/// <param name="role"></param>
|
||||||
/// <param name="group"></param>
|
/// <param name="group"></param>
|
||||||
/// <param name="levelReq"></param>
|
/// <param name="levelReq"></param>
|
||||||
/// <returns></returns>
|
/// <returns>The result of the operation</returns>
|
||||||
Task<bool> AddReactionRole(
|
Task<OneOf<Success, FeatureLimit>> AddReactionRole(
|
||||||
ulong guildId,
|
IGuild guild,
|
||||||
IMessage msg,
|
IMessage msg,
|
||||||
ITextChannel channel,
|
|
||||||
string emote,
|
string emote,
|
||||||
IRole role,
|
IRole role,
|
||||||
int group = 0,
|
int group = 0,
|
||||||
|
@@ -15,7 +15,6 @@ public partial class Administration
|
|||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[NoPublicBot]
|
|
||||||
[UserPerm(GuildPerm.ManageRoles)]
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
[BotPerm(GuildPerm.ManageRoles)]
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
public async partial Task ReactionRoleAdd(
|
public async partial Task ReactionRoleAdd(
|
||||||
@@ -46,27 +45,26 @@ public partial class Administration
|
|||||||
|
|
||||||
var emote = emoteStr.ToIEmote();
|
var emote = emoteStr.ToIEmote();
|
||||||
await msg.AddReactionAsync(emote);
|
await msg.AddReactionAsync(emote);
|
||||||
var succ = await _rero.AddReactionRole(ctx.Guild.Id,
|
var res = await _rero.AddReactionRole(ctx.Guild,
|
||||||
msg,
|
msg,
|
||||||
(ITextChannel)ctx.Channel,
|
|
||||||
emoteStr,
|
emoteStr,
|
||||||
role,
|
role,
|
||||||
group,
|
group,
|
||||||
levelReq);
|
levelReq);
|
||||||
|
|
||||||
if (succ)
|
await res.Match(
|
||||||
{
|
_ => ctx.OkAsync(),
|
||||||
await ctx.OkAsync();
|
fl =>
|
||||||
}
|
{
|
||||||
else
|
_ = msg.RemoveReactionAsync(emote, ctx.Client.CurrentUser);
|
||||||
{
|
return !fl.IsPatronLimit
|
||||||
await ctx.ErrorAsync();
|
? ReplyErrorLocalizedAsync(strs.limit_reached(fl.Quota))
|
||||||
}
|
: ReplyPendingLocalizedAsync(strs.feature_limit_reached_owner(fl.Quota, fl.Name));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[NoPublicBot]
|
|
||||||
[UserPerm(GuildPerm.ManageRoles)]
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
[BotPerm(GuildPerm.ManageRoles)]
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
public async partial Task ReactionRolesList()
|
public async partial Task ReactionRolesList()
|
||||||
@@ -109,7 +107,6 @@ public partial class Administration
|
|||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[NoPublicBot]
|
|
||||||
[UserPerm(GuildPerm.ManageRoles)]
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
[BotPerm(GuildPerm.ManageRoles)]
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
public async partial Task ReactionRolesRemove(ulong messageId)
|
public async partial Task ReactionRolesRemove(ulong messageId)
|
||||||
@@ -123,7 +120,6 @@ public partial class Administration
|
|||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[NoPublicBot]
|
|
||||||
[UserPerm(GuildPerm.ManageRoles)]
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
[BotPerm(GuildPerm.ManageRoles)]
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
public async partial Task ReactionRolesDeleteAll()
|
public async partial Task ReactionRolesDeleteAll()
|
||||||
@@ -134,7 +130,6 @@ public partial class Administration
|
|||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[NoPublicBot]
|
|
||||||
[UserPerm(GuildPerm.ManageRoles)]
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
[BotPerm(GuildPerm.ManageRoles)]
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
[Ratelimit(60)]
|
[Ratelimit(60)]
|
||||||
|
@@ -2,8 +2,11 @@
|
|||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Modules.Utility.Patronage;
|
||||||
using NadekoBot.Modules.Xp.Extensions;
|
using NadekoBot.Modules.Xp.Extensions;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using OneOf.Types;
|
||||||
|
using OneOf;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Administration.Services;
|
namespace NadekoBot.Modules.Administration.Services;
|
||||||
|
|
||||||
@@ -16,20 +19,33 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
|||||||
private ConcurrentDictionary<ulong, List<ReactionRoleV2>> _cache;
|
private ConcurrentDictionary<ulong, List<ReactionRoleV2>> _cache;
|
||||||
private readonly object _cacheLock = new();
|
private readonly object _cacheLock = new();
|
||||||
private readonly SemaphoreSlim _assignementLock = new(1, 1);
|
private readonly SemaphoreSlim _assignementLock = new(1, 1);
|
||||||
|
private readonly IPatronageService _ps;
|
||||||
|
|
||||||
public ReactionRolesService(DiscordSocketClient client, DbService db, IBotCredentials creds)
|
private static readonly FeatureLimitKey _reroFLKey = new()
|
||||||
|
{
|
||||||
|
Key = "rero:max_count",
|
||||||
|
PrettyName = "Reaction Role"
|
||||||
|
};
|
||||||
|
|
||||||
|
public ReactionRolesService(
|
||||||
|
DiscordSocketClient client,
|
||||||
|
DbService db,
|
||||||
|
IBotCredentials creds,
|
||||||
|
IPatronageService ps)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
|
_ps = ps;
|
||||||
_client = client;
|
_client = client;
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
_cache = new();
|
_cache = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var reros = await uow.GetTable<ReactionRoleV2>()
|
var reros = await uow.GetTable<ReactionRoleV2>()
|
||||||
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId, _creds.TotalShards, _client.ShardId))
|
.Where(
|
||||||
|
x => Linq2DbExpressions.GuildOnShard(x.GuildId, _creds.TotalShards, _client.ShardId))
|
||||||
.ToListAsyncLinqToDB();
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
foreach (var group in reros.GroupBy(x => x.MessageId))
|
foreach (var group in reros.GroupBy(x => x.MessageId))
|
||||||
@@ -126,12 +142,12 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
|||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
var levelData = await ctx.GetTable<UserXpStats>()
|
var levelData = await ctx.GetTable<UserXpStats>()
|
||||||
.GetLevelDataFor(user.GuildId, user.Id);
|
.GetLevelDataFor(user.GuildId, user.Id);
|
||||||
|
|
||||||
if (levelData.Level < rero.LevelReq)
|
if (levelData.Level < rero.LevelReq)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove all other roles from the same group from the user
|
// remove all other roles from the same group from the user
|
||||||
// execept in group 0, which is a special, non-exclusive group
|
// execept in group 0, which is a special, non-exclusive group
|
||||||
if (rero.Group != 0)
|
if (rero.Group != 0)
|
||||||
@@ -141,7 +157,7 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
|||||||
.Select(x => x.RoleId)
|
.Select(x => x.RoleId)
|
||||||
.Distinct();
|
.Distinct();
|
||||||
|
|
||||||
|
|
||||||
try { await user.RemoveRolesAsync(exclusive); }
|
try { await user.RemoveRolesAsync(exclusive); }
|
||||||
catch { }
|
catch { }
|
||||||
|
|
||||||
@@ -181,18 +197,16 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a single reaction role
|
/// Adds a single reaction role
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="guildId"></param>
|
/// <param name="guild">Guild where to add a reaction role</param>
|
||||||
/// <param name="msg"></param>
|
/// <param name="msg">Message to which to add a reaction role</param>
|
||||||
/// <param name="channel"></param>
|
|
||||||
/// <param name="emote"></param>
|
/// <param name="emote"></param>
|
||||||
/// <param name="role"></param>
|
/// <param name="role"></param>
|
||||||
/// <param name="group"></param>
|
/// <param name="group"></param>
|
||||||
/// <param name="levelReq"></param>
|
/// <param name="levelReq"></param>
|
||||||
/// <returns></returns>
|
/// <returns>The result of the operation</returns>
|
||||||
public async Task<bool> AddReactionRole(
|
public async Task<OneOf<Success, FeatureLimit>> AddReactionRole(
|
||||||
ulong guildId,
|
IGuild guild,
|
||||||
IMessage msg,
|
IMessage msg,
|
||||||
ITextChannel channel,
|
|
||||||
string emote,
|
string emote,
|
||||||
IRole role,
|
IRole role,
|
||||||
int group = 0,
|
int group = 0,
|
||||||
@@ -205,44 +219,46 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
|||||||
throw new ArgumentOutOfRangeException(nameof(group));
|
throw new ArgumentOutOfRangeException(nameof(group));
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
|
|
||||||
|
await using var tran = await ctx.Database.BeginTransactionAsync();
|
||||||
var activeReactionRoles = await ctx.GetTable<ReactionRoleV2>()
|
var activeReactionRoles = await ctx.GetTable<ReactionRoleV2>()
|
||||||
.Where(x => x.GuildId == guildId)
|
.Where(x => x.GuildId == guild.Id)
|
||||||
.CountAsync();
|
.CountAsync();
|
||||||
|
|
||||||
|
var result = await _ps.TryGetFeatureLimitAsync(_reroFLKey, guild.OwnerId, 50);
|
||||||
|
if (result.Quota != -1 && activeReactionRoles >= result.Quota)
|
||||||
|
return result;
|
||||||
|
|
||||||
if (activeReactionRoles >= 50)
|
await ctx.GetTable<ReactionRoleV2>()
|
||||||
return false;
|
.InsertOrUpdateAsync(() => new()
|
||||||
|
{
|
||||||
|
GuildId = guild.Id,
|
||||||
|
ChannelId = msg.Channel.Id,
|
||||||
|
|
||||||
var changed = await ctx.GetTable<ReactionRoleV2>()
|
MessageId = msg.Id,
|
||||||
.InsertOrUpdateAsync(() => new()
|
Emote = emote,
|
||||||
{
|
|
||||||
GuildId = guildId,
|
|
||||||
ChannelId = channel.Id,
|
|
||||||
|
|
||||||
MessageId = msg.Id,
|
RoleId = role.Id,
|
||||||
Emote = emote,
|
Group = group,
|
||||||
|
LevelReq = levelReq
|
||||||
|
},
|
||||||
|
(old) => new()
|
||||||
|
{
|
||||||
|
RoleId = role.Id,
|
||||||
|
Group = group,
|
||||||
|
LevelReq = levelReq
|
||||||
|
},
|
||||||
|
() => new()
|
||||||
|
{
|
||||||
|
MessageId = msg.Id,
|
||||||
|
Emote = emote,
|
||||||
|
});
|
||||||
|
|
||||||
RoleId = role.Id,
|
await tran.CommitAsync();
|
||||||
Group = group,
|
|
||||||
LevelReq = levelReq
|
|
||||||
},
|
|
||||||
(old) => new()
|
|
||||||
{
|
|
||||||
RoleId = role.Id,
|
|
||||||
Group = group,
|
|
||||||
LevelReq = levelReq
|
|
||||||
},
|
|
||||||
() => new()
|
|
||||||
{
|
|
||||||
MessageId = msg.Id,
|
|
||||||
Emote = emote,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (changed == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var obj = new ReactionRoleV2()
|
var obj = new ReactionRoleV2()
|
||||||
{
|
{
|
||||||
GuildId = guildId,
|
GuildId = guild.Id,
|
||||||
MessageId = msg.Id,
|
MessageId = msg.Id,
|
||||||
Emote = emote,
|
Emote = emote,
|
||||||
RoleId = role.Id,
|
RoleId = role.Id,
|
||||||
@@ -265,7 +281,7 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return new Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -326,7 +342,10 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
|||||||
return output.Length;
|
return output.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<IEmote>> TransferReactionRolesAsync(ulong guildId, ulong fromMessageId, ulong toMessageId)
|
public async Task<IReadOnlyCollection<IEmote>> TransferReactionRolesAsync(
|
||||||
|
ulong guildId,
|
||||||
|
ulong fromMessageId,
|
||||||
|
ulong toMessageId)
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
var updated = ctx.GetTable<ReactionRoleV2>()
|
var updated = ctx.GetTable<ReactionRoleV2>()
|
||||||
|
@@ -149,7 +149,7 @@ public partial class Administration
|
|||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageRoles)]
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
[BotPerm(GuildPerm.ManageRoles)]
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
public async partial Task RoleHoist(IRole role)
|
public async partial Task RoleHoist([Leftover] IRole role)
|
||||||
{
|
{
|
||||||
var newHoisted = !role.IsHoisted;
|
var newHoisted = !role.IsHoisted;
|
||||||
await role.ModifyAsync(r => r.Hoist = newHoisted);
|
await role.ModifyAsync(r => r.Hoist = newHoisted);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace NadekoBot.Modules.NadekoExpressions;
|
namespace NadekoBot.Modules.NadekoExpressions;
|
||||||
|
|
||||||
@@ -266,8 +266,8 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
|
|||||||
public async partial Task ExprClear()
|
public async partial Task ExprClear()
|
||||||
{
|
{
|
||||||
if (await PromptUserConfirmAsync(_eb.Create()
|
if (await PromptUserConfirmAsync(_eb.Create()
|
||||||
.WithTitle("Custom reaction clear")
|
.WithTitle("Expression clear")
|
||||||
.WithDescription("This will delete all custom reactions on this server.")))
|
.WithDescription("This will delete all expressions on this server.")))
|
||||||
{
|
{
|
||||||
var count = _service.DeleteAllExpressions(ctx.Guild.Id);
|
var count = _service.DeleteAllExpressions(ctx.Guild.Id);
|
||||||
await ReplyConfirmLocalizedAsync(strs.exprs_cleared(count));
|
await ReplyConfirmLocalizedAsync(strs.exprs_cleared(count));
|
||||||
@@ -334,4 +334,4 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
|
|||||||
|
|
||||||
await ctx.OkAsync();
|
await ctx.OkAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -65,7 +65,7 @@ public partial class Gambling
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
await ReplyErrorLocalizedAsync(strs.unable_to_dm_user);
|
await ReplyErrorLocalizedAsync(strs.cant_dm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling.Bank;
|
namespace NadekoBot.Modules.Gambling.Bank;
|
||||||
|
|
||||||
|
@@ -1,17 +1,13 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Modules.Gambling;
|
namespace NadekoBot.Modules.Gambling;
|
||||||
|
|
||||||
public class CashInteraction
|
public class CashInteraction : NInteraction
|
||||||
{
|
{
|
||||||
public static NadekoInteractionData Data =
|
protected override NadekoInteractionData Data
|
||||||
new NadekoInteractionData(new Emoji("🏦"), "cash:bank_show_balance");
|
=> new NadekoInteractionData(new Emoji("🏦"), "cash:bank_show_balance");
|
||||||
|
|
||||||
public static NadekoInteraction CreateInstance(
|
public CashInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action)
|
||||||
DiscordSocketClient client,
|
: base(client, userId, action)
|
||||||
ulong userId,
|
{
|
||||||
Func<SocketMessageComponent, Task> action)
|
}
|
||||||
=> new NadekoInteractionBuilder()
|
|
||||||
.WithData(Data)
|
|
||||||
.WithAction(action)
|
|
||||||
.Build(client, userId);
|
|
||||||
}
|
}
|
@@ -3,11 +3,13 @@ using LinqToDB;
|
|||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Modules.Utility.Patronage;
|
||||||
using NadekoBot.Modules.Gambling.Bank;
|
using NadekoBot.Modules.Gambling.Bank;
|
||||||
using NadekoBot.Modules.Gambling.Common;
|
using NadekoBot.Modules.Gambling.Common;
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
using NadekoBot.Services.Currency;
|
using NadekoBot.Services.Currency;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
private readonly DownloadTracker _tracker;
|
private readonly DownloadTracker _tracker;
|
||||||
private readonly GamblingConfigService _configService;
|
private readonly GamblingConfigService _configService;
|
||||||
private readonly IBankService _bank;
|
private readonly IBankService _bank;
|
||||||
|
private readonly IPatronageService _ps;
|
||||||
|
|
||||||
private IUserMessage rdMsg;
|
private IUserMessage rdMsg;
|
||||||
|
|
||||||
@@ -52,7 +55,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
DownloadTracker tracker,
|
DownloadTracker tracker,
|
||||||
GamblingConfigService configService,
|
GamblingConfigService configService,
|
||||||
IBankService bank)
|
IBankService bank,
|
||||||
|
IPatronageService ps)
|
||||||
: base(configService)
|
: base(configService)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
@@ -60,6 +64,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
_cache = cache;
|
_cache = cache;
|
||||||
_client = client;
|
_client = client;
|
||||||
_bank = bank;
|
_bank = bank;
|
||||||
|
_ps = ps;
|
||||||
|
|
||||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||||
_enUsCulture.NumberDecimalDigits = 0;
|
_enUsCulture.NumberDecimalDigits = 0;
|
||||||
@@ -102,6 +107,12 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
await ctx.Channel.EmbedAsync(embed);
|
await ctx.Channel.EmbedAsync(embed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly FeatureLimitKey _timelyKey = new FeatureLimitKey()
|
||||||
|
{
|
||||||
|
Key = "timely:extra_percent",
|
||||||
|
PrettyName = "Timely"
|
||||||
|
};
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async partial Task Timely()
|
public async partial Task Timely()
|
||||||
{
|
{
|
||||||
@@ -119,6 +130,10 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var result = await _ps.TryGetFeatureLimitAsync(_timelyKey, ctx.User.Id, 0);
|
||||||
|
|
||||||
|
val = (int)(val * (1 + (result.Quota * 0.01f)));
|
||||||
|
|
||||||
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
|
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
|
||||||
|
|
||||||
await ReplyConfirmLocalizedAsync(strs.timely(N(val), period));
|
await ReplyConfirmLocalizedAsync(strs.timely(N(val), period));
|
||||||
@@ -331,8 +346,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
.Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true));
|
.Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private NadekoInteraction CreateCashInteraction()
|
private NadekoButtonInteraction CreateCashInteraction()
|
||||||
=> CashInteraction.CreateInstance(_client, ctx.User.Id, BankAction);
|
=> new CashInteraction(_client, ctx.User.Id, BankAction).GetInteraction();
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
@@ -363,7 +378,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await _cs.TransferAsync(_eb, ctx.User, receiver, amount, msg))
|
if (!await _cs.TransferAsync(_eb, ctx.User, receiver, amount, msg, N(amount)))
|
||||||
{
|
{
|
||||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||||
return;
|
return;
|
||||||
@@ -780,4 +795,31 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
|
|
||||||
await ctx.Channel.EmbedAsync(embed);
|
await ctx.Channel.EmbedAsync(embed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly ImmutableArray<string> _emojis =
|
||||||
|
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async partial Task WheelOfFortune(ShmartNumber amount)
|
||||||
|
{
|
||||||
|
if (!await CheckBetMandatory(amount))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!await _cs.RemoveAsync(ctx.User.Id, amount, new("wheel", "bet")))
|
||||||
|
{
|
||||||
|
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _service.WheelOfFortuneSpinAsync(ctx.User.Id, amount);
|
||||||
|
|
||||||
|
var wofMultipliers = Config.WheelOfFortune.Multipliers;
|
||||||
|
await SendConfirmAsync(Format.Bold($@"{ctx.User} won: {N(result.Amount)}
|
||||||
|
|
||||||
|
『{wofMultipliers[1]}』 『{wofMultipliers[0]}』 『{wofMultipliers[7]}』
|
||||||
|
|
||||||
|
『{wofMultipliers[2]}』 {_emojis[result.Index]} 『{wofMultipliers[6]}』
|
||||||
|
|
||||||
|
『{wofMultipliers[3]}』 『{wofMultipliers[4]}』 『{wofMultipliers[5]}』"));
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,49 +0,0 @@
|
|||||||
#nullable disable
|
|
||||||
using NadekoBot.Modules.Gambling.Common;
|
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling;
|
|
||||||
|
|
||||||
public partial class Gambling
|
|
||||||
{
|
|
||||||
public partial class WheelOfFortuneCommands : GamblingSubmodule<GamblingService>
|
|
||||||
{
|
|
||||||
private static readonly ImmutableArray<string> _emojis =
|
|
||||||
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
|
|
||||||
|
|
||||||
private readonly ICurrencyService _cs;
|
|
||||||
private readonly DbService _db;
|
|
||||||
|
|
||||||
public WheelOfFortuneCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConfService)
|
|
||||||
: base(gamblingConfService)
|
|
||||||
{
|
|
||||||
_cs = cs;
|
|
||||||
_db = db;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
public async partial Task WheelOfFortune(ShmartNumber amount)
|
|
||||||
{
|
|
||||||
if (!await CheckBetMandatory(amount))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!await _cs.RemoveAsync(ctx.User.Id, amount, new("wheel", "bet")))
|
|
||||||
{
|
|
||||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _service.WheelOfFortuneSpinAsync(ctx.User.Id, amount);
|
|
||||||
|
|
||||||
var wofMultipliers = Config.WheelOfFortune.Multipliers;
|
|
||||||
await SendConfirmAsync(Format.Bold($@"{ctx.User} won: {N(result.Amount)}
|
|
||||||
|
|
||||||
『{wofMultipliers[1]}』 『{wofMultipliers[0]}』 『{wofMultipliers[7]}』
|
|
||||||
|
|
||||||
『{wofMultipliers[2]}』 {_emojis[result.Index]} 『{wofMultipliers[6]}』
|
|
||||||
|
|
||||||
『{wofMultipliers[3]}』 『{wofMultipliers[4]}』 『{wofMultipliers[5]}』"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,8 +1,11 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
using NadekoBot.Modules.Games.Common.ChatterBot;
|
using NadekoBot.Modules.Games.Common.ChatterBot;
|
||||||
|
using NadekoBot.Modules.Permissions;
|
||||||
using NadekoBot.Modules.Permissions.Common;
|
using NadekoBot.Modules.Permissions.Common;
|
||||||
using NadekoBot.Modules.Permissions.Services;
|
using NadekoBot.Modules.Permissions.Services;
|
||||||
|
using NadekoBot.Modules.Utility.Patronage;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games.Services;
|
namespace NadekoBot.Modules.Games.Services;
|
||||||
|
|
||||||
@@ -13,6 +16,8 @@ public class ChatterBotService : IExecOnMessage
|
|||||||
public int Priority
|
public int Priority
|
||||||
=> 1;
|
=> 1;
|
||||||
|
|
||||||
|
private readonly FeatureLimitKey _flKey;
|
||||||
|
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly PermissionService _perms;
|
private readonly PermissionService _perms;
|
||||||
private readonly CommandHandler _cmd;
|
private readonly CommandHandler _cmd;
|
||||||
@@ -20,6 +25,8 @@ public class ChatterBotService : IExecOnMessage
|
|||||||
private readonly IBotCredentials _creds;
|
private readonly IBotCredentials _creds;
|
||||||
private readonly IEmbedBuilderService _eb;
|
private readonly IEmbedBuilderService _eb;
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
|
private readonly IPatronageService _ps;
|
||||||
|
private readonly CmdCdService _ccs;
|
||||||
|
|
||||||
public ChatterBotService(
|
public ChatterBotService(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
@@ -29,7 +36,9 @@ public class ChatterBotService : IExecOnMessage
|
|||||||
IBotStrings strings,
|
IBotStrings strings,
|
||||||
IHttpClientFactory factory,
|
IHttpClientFactory factory,
|
||||||
IBotCredentials creds,
|
IBotCredentials creds,
|
||||||
IEmbedBuilderService eb)
|
IEmbedBuilderService eb,
|
||||||
|
IPatronageService ps,
|
||||||
|
CmdCdService cmdCdService)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_perms = perms;
|
_perms = perms;
|
||||||
@@ -38,8 +47,17 @@ public class ChatterBotService : IExecOnMessage
|
|||||||
_creds = creds;
|
_creds = creds;
|
||||||
_eb = eb;
|
_eb = eb;
|
||||||
_httpFactory = factory;
|
_httpFactory = factory;
|
||||||
|
_ps = ps;
|
||||||
|
_ccs = cmdCdService;
|
||||||
|
|
||||||
ChatterBotGuilds = new(bot.AllGuildConfigs.Where(gc => gc.CleverbotEnabled)
|
_flKey = new FeatureLimitKey()
|
||||||
|
{
|
||||||
|
Key = CleverBotResponseStr.CLEVERBOT_RESPONSE,
|
||||||
|
PrettyName = "Cleverbot Replies"
|
||||||
|
};
|
||||||
|
|
||||||
|
ChatterBotGuilds = new(bot.AllGuildConfigs
|
||||||
|
.Where(gc => gc.CleverbotEnabled)
|
||||||
.ToDictionary(gc => gc.GuildId,
|
.ToDictionary(gc => gc.GuildId,
|
||||||
_ => new Lazy<IChatterBotSession>(() => CreateSession(), true)));
|
_ => new Lazy<IChatterBotSession>(() => CreateSession(), true)));
|
||||||
}
|
}
|
||||||
@@ -48,7 +66,9 @@ public class ChatterBotService : IExecOnMessage
|
|||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(_creds.CleverbotApiKey))
|
if (!string.IsNullOrWhiteSpace(_creds.CleverbotApiKey))
|
||||||
return new OfficialCleverbotSession(_creds.CleverbotApiKey, _httpFactory);
|
return new OfficialCleverbotSession(_creds.CleverbotApiKey, _httpFactory);
|
||||||
return new CleverbotIoSession("GAh3wUfzDCpDpdpT", "RStKgqn7tcO9blbrv4KbXM8NDlb7H37C", _httpFactory);
|
|
||||||
|
Log.Information("Cleverbot will not work as the api key is missing.");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string PrepareMessage(IUserMessage msg, out IChatterBotSession cleverbot)
|
public string PrepareMessage(IUserMessage msg, out IChatterBotSession cleverbot)
|
||||||
@@ -78,27 +98,11 @@ public class ChatterBotService : IExecOnMessage
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> TryAsk(IChatterBotSession cleverbot, ITextChannel channel, string message)
|
|
||||||
{
|
|
||||||
await channel.TriggerTypingAsync();
|
|
||||||
|
|
||||||
var response = await cleverbot.Think(message);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await channel.SendConfirmAsync(_eb, response.SanitizeMentions(true));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await channel.SendConfirmAsync(_eb, response.SanitizeMentions(true)); // try twice :\
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ExecOnMessageAsync(IGuild guild, IUserMessage usrMsg)
|
public async Task<bool> ExecOnMessageAsync(IGuild guild, IUserMessage usrMsg)
|
||||||
{
|
{
|
||||||
if (guild is not SocketGuild sg)
|
if (guild is not SocketGuild sg)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var message = PrepareMessage(usrMsg, out var cbs);
|
var message = PrepareMessage(usrMsg, out var cbs);
|
||||||
@@ -106,7 +110,10 @@ public class ChatterBotService : IExecOnMessage
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
var pc = _perms.GetCacheFor(guild.Id);
|
var pc = _perms.GetCacheFor(guild.Id);
|
||||||
if (!pc.Permissions.CheckPermissions(usrMsg, "cleverbot", "Games".ToLowerInvariant(), out var index))
|
if (!pc.Permissions.CheckPermissions(usrMsg,
|
||||||
|
"cleverbot",
|
||||||
|
"games",
|
||||||
|
out var index))
|
||||||
{
|
{
|
||||||
if (pc.Verbose)
|
if (pc.Verbose)
|
||||||
{
|
{
|
||||||
@@ -122,24 +129,78 @@ public class ChatterBotService : IExecOnMessage
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cleverbotExecuted = await TryAsk(cbs, (ITextChannel)usrMsg.Channel, message);
|
if (await _ccs.TryBlock(sg, usrMsg.Author, CleverBotResponseStr.CLEVERBOT_RESPONSE))
|
||||||
if (cleverbotExecuted)
|
|
||||||
{
|
{
|
||||||
Log.Information(@"CleverBot Executed
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var channel = (ITextChannel)usrMsg.Channel;
|
||||||
|
var conf = _ps.GetConfig();
|
||||||
|
if (!_creds.IsOwner(sg.OwnerId) && conf.IsEnabled)
|
||||||
|
{
|
||||||
|
var quota = await _ps.TryGetFeatureLimitAsync(_flKey, sg.OwnerId, 0);
|
||||||
|
|
||||||
|
uint? daily = quota.Quota is int dVal and < 0
|
||||||
|
? (uint)-dVal
|
||||||
|
: null;
|
||||||
|
|
||||||
|
uint? monthly = quota.Quota is int mVal and >= 0
|
||||||
|
? (uint)mVal
|
||||||
|
: null;
|
||||||
|
|
||||||
|
var maybeLimit = await _ps.TryIncrementQuotaCounterAsync(sg.OwnerId,
|
||||||
|
sg.OwnerId == usrMsg.Author.Id,
|
||||||
|
FeatureType.Limit,
|
||||||
|
_flKey.Key,
|
||||||
|
null,
|
||||||
|
daily,
|
||||||
|
monthly);
|
||||||
|
|
||||||
|
if (maybeLimit.TryPickT1(out var ql, out var counters))
|
||||||
|
{
|
||||||
|
if (ql.Quota == 0)
|
||||||
|
{
|
||||||
|
await channel.SendErrorAsync(_eb,
|
||||||
|
null!,
|
||||||
|
text:
|
||||||
|
"In order to use the cleverbot feature, the owner of this server should be [Patron Tier X](https://patreon.com/join/nadekobot) on patreon.",
|
||||||
|
footer:
|
||||||
|
"You may disable the cleverbot feature, and this message via '.cleverbot' command");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await channel.SendErrorAsync(_eb,
|
||||||
|
null!,
|
||||||
|
$"You've reached your quota limit of **{ql.Quota}** responses {ql.QuotaPeriod.ToFullName()} for the cleverbot feature.",
|
||||||
|
footer: "You may wait for the quota reset or .");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = channel.TriggerTypingAsync();
|
||||||
|
var response = await cbs.Think(message);
|
||||||
|
await channel.SendConfirmAsync(_eb,
|
||||||
|
title: null,
|
||||||
|
response.SanitizeMentions(true)
|
||||||
|
// , footer: counter > 0 ? counter.ToString() : null
|
||||||
|
);
|
||||||
|
|
||||||
|
Log.Information(@"CleverBot Executed
|
||||||
Server: {GuildName} [{GuildId}]
|
Server: {GuildName} [{GuildId}]
|
||||||
Channel: {ChannelName} [{ChannelId}]
|
Channel: {ChannelName} [{ChannelId}]
|
||||||
UserId: {Author} [{AuthorId}]
|
UserId: {Author} [{AuthorId}]
|
||||||
Message: {Content}",
|
Message: {Content}",
|
||||||
guild.Name,
|
guild.Name,
|
||||||
guild.Id,
|
guild.Id,
|
||||||
usrMsg.Channel?.Name,
|
usrMsg.Channel?.Name,
|
||||||
usrMsg.Channel?.Id,
|
usrMsg.Channel?.Id,
|
||||||
usrMsg.Author,
|
usrMsg.Author,
|
||||||
usrMsg.Author.Id,
|
usrMsg.Author.Id,
|
||||||
usrMsg.Content);
|
usrMsg.Content);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@@ -14,7 +14,6 @@ public partial class Games
|
|||||||
public ChatterBotCommands(DbService db)
|
public ChatterBotCommands(DbService db)
|
||||||
=> _db = db;
|
=> _db = db;
|
||||||
|
|
||||||
[NoPublicBot]
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
#nullable disable
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games.Common.ChatterBot;
|
|
||||||
|
|
||||||
public class ChatterBotSession : IChatterBotSession
|
|
||||||
{
|
|
||||||
private static NadekoRandom Rng { get; } = new();
|
|
||||||
|
|
||||||
private string ApiEndpoint
|
|
||||||
=> "http://api.program-o.com/v2/chatbot/"
|
|
||||||
+ $"?bot_id={_botId}&"
|
|
||||||
+ "say={0}&"
|
|
||||||
+ $"convo_id=nadekobot_{_chatterBotId}&"
|
|
||||||
+ "format=json";
|
|
||||||
|
|
||||||
private readonly string _chatterBotId;
|
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
|
||||||
private readonly int _botId = 6;
|
|
||||||
|
|
||||||
public ChatterBotSession(IHttpClientFactory httpFactory)
|
|
||||||
{
|
|
||||||
_chatterBotId = Rng.Next(0, 1000000).ToString().ToBase64();
|
|
||||||
_httpFactory = httpFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> Think(string message)
|
|
||||||
{
|
|
||||||
using var http = _httpFactory.CreateClient();
|
|
||||||
var res = await http.GetStringAsync(string.Format(ApiEndpoint, message));
|
|
||||||
var cbr = JsonConvert.DeserializeObject<ChatterBotResponse>(res);
|
|
||||||
return cbr.BotSay.Replace("<br/>", "\n", StringComparison.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -5,16 +5,4 @@ public class CleverbotResponse
|
|||||||
{
|
{
|
||||||
public string Cs { get; set; }
|
public string Cs { get; set; }
|
||||||
public string Output { get; set; }
|
public string Output { get; set; }
|
||||||
}
|
|
||||||
|
|
||||||
public class CleverbotIoCreateResponse
|
|
||||||
{
|
|
||||||
public string Status { get; set; }
|
|
||||||
public string Nick { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CleverbotIoAskResponse
|
|
||||||
{
|
|
||||||
public string Status { get; set; }
|
|
||||||
public string Response { get; set; }
|
|
||||||
}
|
}
|
@@ -35,57 +35,4 @@ public class OfficialCleverbotSession : IChatterBotSession
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class CleverbotIoSession : IChatterBotSession
|
|
||||||
{
|
|
||||||
private readonly string _key;
|
|
||||||
private readonly string _user;
|
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
|
||||||
private readonly AsyncLazy<string> _nick;
|
|
||||||
|
|
||||||
private readonly string _createEndpoint = "https://cleverbot.io/1.0/create";
|
|
||||||
private readonly string _askEndpoint = "https://cleverbot.io/1.0/ask";
|
|
||||||
|
|
||||||
public CleverbotIoSession(string user, string key, IHttpClientFactory factory)
|
|
||||||
{
|
|
||||||
_key = key;
|
|
||||||
_user = user;
|
|
||||||
_httpFactory = factory;
|
|
||||||
|
|
||||||
_nick = new(GetNick);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> GetNick()
|
|
||||||
{
|
|
||||||
using var http = _httpFactory.CreateClient();
|
|
||||||
using var msg = new FormUrlEncodedContent(new[]
|
|
||||||
{
|
|
||||||
new KeyValuePair<string, string>("user", _user), new KeyValuePair<string, string>("key", _key)
|
|
||||||
});
|
|
||||||
using var data = await http.PostAsync(_createEndpoint, msg);
|
|
||||||
var str = await data.Content.ReadAsStringAsync();
|
|
||||||
var obj = JsonConvert.DeserializeObject<CleverbotIoCreateResponse>(str);
|
|
||||||
if (obj.Status != "success")
|
|
||||||
throw new OperationCanceledException(obj.Status);
|
|
||||||
|
|
||||||
return obj.Nick;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> Think(string input)
|
|
||||||
{
|
|
||||||
using var http = _httpFactory.CreateClient();
|
|
||||||
using var msg = new FormUrlEncodedContent(new[]
|
|
||||||
{
|
|
||||||
new KeyValuePair<string, string>("user", _user), new KeyValuePair<string, string>("key", _key),
|
|
||||||
new KeyValuePair<string, string>("nick", await _nick), new KeyValuePair<string, string>("text", input)
|
|
||||||
});
|
|
||||||
using var data = await http.PostAsync(_askEndpoint, msg);
|
|
||||||
var str = await data.Content.ReadAsStringAsync();
|
|
||||||
var obj = JsonConvert.DeserializeObject<CleverbotIoAskResponse>(str);
|
|
||||||
if (obj.Status != "success")
|
|
||||||
throw new OperationCanceledException(obj.Status);
|
|
||||||
|
|
||||||
return obj.Response;
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -212,7 +212,7 @@ public partial class Help : NadekoModule<HelpService>
|
|||||||
cmds = cmds.Where(x => succ.Contains(x)).ToList();
|
cmds = cmds.Where(x => succ.Contains(x)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdsWithGroup = cmds.GroupBy(c => c.Module.Name.Replace("Commands", "", StringComparison.InvariantCulture))
|
var cmdsWithGroup = cmds.GroupBy(c => c.Module.GetGroupName())
|
||||||
.OrderBy(x => x.Key == x.First().Module.Name ? int.MaxValue : x.Count())
|
.OrderBy(x => x.Key == x.First().Module.Name ? int.MaxValue : x.Count())
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@@ -294,7 +294,7 @@ public partial class Help : NadekoModule<HelpService>
|
|||||||
|
|
||||||
if (fail.StartsWith(prefix))
|
if (fail.StartsWith(prefix))
|
||||||
fail = fail.Substring(prefix.Length);
|
fail = fail.Substring(prefix.Length);
|
||||||
|
|
||||||
var group = _cmds.Modules
|
var group = _cmds.Modules
|
||||||
.SelectMany(x => x.Submodules)
|
.SelectMany(x => x.Submodules)
|
||||||
.Where(x => !string.IsNullOrWhiteSpace(x.Group))
|
.Where(x => !string.IsNullOrWhiteSpace(x.Group))
|
||||||
@@ -393,11 +393,6 @@ public partial class Help : NadekoModule<HelpService>
|
|||||||
};
|
};
|
||||||
|
|
||||||
using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config);
|
using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config);
|
||||||
using var oldVersionObject = await dlClient.GetObjectAsync(new()
|
|
||||||
{
|
|
||||||
BucketName = "nadeko-pictures",
|
|
||||||
Key = "cmds/versions.json"
|
|
||||||
});
|
|
||||||
|
|
||||||
using (var client = new AmazonS3Client(accessKey, secretAcccessKey, config))
|
using (var client = new AmazonS3Client(accessKey, secretAcccessKey, config))
|
||||||
{
|
{
|
||||||
@@ -407,14 +402,29 @@ public partial class Help : NadekoModule<HelpService>
|
|||||||
ContentType = "application/json",
|
ContentType = "application/json",
|
||||||
ContentBody = uploadData,
|
ContentBody = uploadData,
|
||||||
// either use a path provided in the argument or the default one for public nadeko, other/cmds.json
|
// either use a path provided in the argument or the default one for public nadeko, other/cmds.json
|
||||||
Key = $"cmds/{StatsService.BOT_VERSION}.json",
|
Key = $"cmds/v4/{StatsService.BOT_VERSION}.json",
|
||||||
CannedACL = S3CannedACL.PublicRead
|
CannedACL = S3CannedACL.PublicRead
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var ms = new MemoryStream();
|
|
||||||
await oldVersionObject.ResponseStream.CopyToAsync(ms);
|
var versionListString = "[]";
|
||||||
var versionListString = Encoding.UTF8.GetString(ms.ToArray());
|
try
|
||||||
|
{
|
||||||
|
using var oldVersionObject = await dlClient.GetObjectAsync(new()
|
||||||
|
{
|
||||||
|
BucketName = "nadeko-pictures",
|
||||||
|
Key = "cmds/v4/versions.json"
|
||||||
|
});
|
||||||
|
|
||||||
|
await using var ms = new MemoryStream();
|
||||||
|
await oldVersionObject.ResponseStream.CopyToAsync(ms);
|
||||||
|
versionListString = Encoding.UTF8.GetString(ms.ToArray());
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Log.Information("No old version list found. Creating a new one.");
|
||||||
|
}
|
||||||
|
|
||||||
var versionList = JsonSerializer.Deserialize<List<string>>(versionListString);
|
var versionList = JsonSerializer.Deserialize<List<string>>(versionListString);
|
||||||
if (versionList is not null && !versionList.Contains(StatsService.BOT_VERSION))
|
if (versionList is not null && !versionList.Contains(StatsService.BOT_VERSION))
|
||||||
@@ -435,7 +445,7 @@ public partial class Help : NadekoModule<HelpService>
|
|||||||
ContentType = "application/json",
|
ContentType = "application/json",
|
||||||
ContentBody = versionListString,
|
ContentBody = versionListString,
|
||||||
// either use a path provided in the argument or the default one for public nadeko, other/cmds.json
|
// either use a path provided in the argument or the default one for public nadeko, other/cmds.json
|
||||||
Key = "cmds/versions.json",
|
Key = "cmds/v4/versions.json",
|
||||||
CannedACL = S3CannedACL.PublicRead
|
CannedACL = S3CannedACL.PublicRead
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -455,9 +465,71 @@ public partial class Help : NadekoModule<HelpService>
|
|||||||
[Cmd]
|
[Cmd]
|
||||||
public async partial Task Guide()
|
public async partial Task Guide()
|
||||||
=> await ConfirmLocalizedAsync(strs.guide("https://nadeko.bot/commands",
|
=> await ConfirmLocalizedAsync(strs.guide("https://nadeko.bot/commands",
|
||||||
"http://nadekobot.readthedocs.io/en/latest/"));
|
"https://nadekobot.readthedocs.io/en/latest/"));
|
||||||
|
|
||||||
|
|
||||||
|
private Task SelfhostAction(SocketMessageComponent smc)
|
||||||
|
=> smc.RespondConfirmAsync(_eb,
|
||||||
|
@"- In case you don't want or cannot Donate to NadekoBot project, but you
|
||||||
|
- NadekoBot is a completely free and fully [open source](https://gitlab.com/kwoth/nadekobot) project which means you can run your own ""selfhosted"" instance on your computer or server for free.
|
||||||
|
|
||||||
|
*Keep in mind that running the bot on your computer means that the bot will be offline when you turn off your computer*
|
||||||
|
|
||||||
|
- You can find the selfhosting guides by using the `.guide` command and clicking on the second link that pops up.
|
||||||
|
- If you decide to selfhost the bot, still consider [supporting the project](https://patreon.com/join/nadekobot) to keep the development going :)",
|
||||||
|
true);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
[OnlyPublicBot]
|
||||||
public async partial Task Donate()
|
public async partial Task Donate()
|
||||||
=> await ReplyConfirmLocalizedAsync(strs.donate(PATREON_URL, PAYPAL_URL));
|
{
|
||||||
|
var selfhostInter = new DonateSelfhostingInteraction(_client, ctx.User.Id, SelfhostAction);
|
||||||
|
|
||||||
|
var eb = _eb.Create(ctx)
|
||||||
|
.WithOkColor()
|
||||||
|
.WithTitle("Thank you for considering to donate to the NadekoBot project!");
|
||||||
|
|
||||||
|
eb
|
||||||
|
.WithDescription("NadekoBot relies on donations to keep the servers, services and APIs running.\n"
|
||||||
|
+ "Donating will give you access to some exclusive features. You can read about them on the [patreon page](https://patreon.com/join/nadekobot)")
|
||||||
|
.AddField("Donation Instructions",
|
||||||
|
$@"
|
||||||
|
🗒️ Before pledging it is recommended to open your DMs as Nadeko will send you a welcome message with instructions after you pledge has been processed and confirmed.
|
||||||
|
|
||||||
|
**Step 1:** ❤️ Pledge on Patreon ❤️
|
||||||
|
|
||||||
|
`1.` Go to <https://patreon.com/join/nadekobot> and choose a tier.
|
||||||
|
`2.` Make sure your payment is processed and accepted.
|
||||||
|
|
||||||
|
**Step 2** 🤝 Connect your Discord account 🤝
|
||||||
|
|
||||||
|
`1.` Go to your profile settings on Patreon and connect your Discord account to it.
|
||||||
|
*please make sure you're logged into the correct Discord account*
|
||||||
|
|
||||||
|
If you do not know how to do it, you may follow instructions in this link:
|
||||||
|
<https://support.patreon.com/hc/en-us/articles/212052266-How-do-I-connect-Discord-to-Patreon-Patron->
|
||||||
|
|
||||||
|
**Step 3** ⏰ Wait a short while (usually 1-3 minutes) ⏰
|
||||||
|
|
||||||
|
Nadeko will DM you the welcome instructions, and you may start using the patron-only commands and features!
|
||||||
|
🎉 **Enjoy!** 🎉
|
||||||
|
")
|
||||||
|
.AddField("Troubleshooting",
|
||||||
|
@"
|
||||||
|
*In case you didn't receive the rewards within 5 minutes:*
|
||||||
|
`1.` Make sure your DMs are open to everyone. Maybe your pledge was processed successfully but the bot was unable to DM you. Use the `.patron` command to check your status.
|
||||||
|
`2.` Make sure you've connected the CORRECT Discord account. Quite often users log in to different Discord accounts in their browser. You may also try disconnecting and reconnecting your account.
|
||||||
|
`3.` Make sure your payment has been processed and not declined by Patreon.
|
||||||
|
`4.` If any of the previous steps don't help, you can join the nadeko support server <https://discord.nadeko.bot> and ask for help in the #help channel");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await (await ctx.User.CreateDMChannelAsync()).EmbedAsync(eb, inter: selfhostInter.GetInteraction());
|
||||||
|
_ = ctx.OkAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await ReplyErrorLocalizedAsync(strs.cant_dm);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -131,6 +131,25 @@ public class HelpService : IExecNoCommand, INService
|
|||||||
|
|
||||||
if (cmd.Preconditions.Any(x => x is OwnerOnlyAttribute))
|
if (cmd.Preconditions.Any(x => x is OwnerOnlyAttribute))
|
||||||
toReturn.Add("Bot Owner Only");
|
toReturn.Add("Bot Owner Only");
|
||||||
|
|
||||||
|
if(cmd.Preconditions.Any(x => x is NoPublicBotAttribute)
|
||||||
|
|| cmd.Module
|
||||||
|
.Preconditions
|
||||||
|
.Any(x => x is NoPublicBotAttribute)
|
||||||
|
|| cmd.Module.GetTopLevelModule()
|
||||||
|
.Preconditions
|
||||||
|
.Any(x => x is NoPublicBotAttribute))
|
||||||
|
toReturn.Add("No Public Bot");
|
||||||
|
|
||||||
|
if (cmd.Preconditions
|
||||||
|
.Any(x => x is OnlyPublicBotAttribute)
|
||||||
|
|| cmd.Module
|
||||||
|
.Preconditions
|
||||||
|
.Any(x => x is OnlyPublicBotAttribute)
|
||||||
|
|| cmd.Module.GetTopLevelModule()
|
||||||
|
.Preconditions
|
||||||
|
.Any(x => x is OnlyPublicBotAttribute))
|
||||||
|
toReturn.Add("Only Public Bot");
|
||||||
|
|
||||||
var userPerm = (UserPermAttribute)cmd.Preconditions.FirstOrDefault(ca => ca is UserPermAttribute);
|
var userPerm = (UserPermAttribute)cmd.Preconditions.FirstOrDefault(ca => ca is UserPermAttribute);
|
||||||
|
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
namespace NadekoBot.Modules.Help;
|
||||||
|
|
||||||
|
public class DonateSelfhostingInteraction : NInteraction
|
||||||
|
{
|
||||||
|
protected override NadekoInteractionData Data
|
||||||
|
=> new NadekoInteractionData(new Emoji("🖥️"), "donate:selfhosting", "Selfhosting");
|
||||||
|
|
||||||
|
public DonateSelfhostingInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action)
|
||||||
|
: base(client, userId, action)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
namespace NadekoBot.Modules.Help;
|
||||||
|
|
||||||
|
public class DonateTroubleshootInteraction : NInteraction
|
||||||
|
{
|
||||||
|
protected override NadekoInteractionData Data
|
||||||
|
=> new NadekoInteractionData(new Emoji("❓"), "donate:troubleshoot", "Troubleshoot");
|
||||||
|
|
||||||
|
public DonateTroubleshootInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action)
|
||||||
|
: base(client, userId, action)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -119,7 +119,7 @@ public sealed partial class YtLoader
|
|||||||
var responseSpan = response.AsSpan()[140_000..];
|
var responseSpan = response.AsSpan()[140_000..];
|
||||||
var startIndex = responseSpan.IndexOf(_ytResultInitialData);
|
var startIndex = responseSpan.IndexOf(_ytResultInitialData);
|
||||||
if (startIndex == -1)
|
if (startIndex == -1)
|
||||||
return null; // todo future try selecting html
|
return null; // FUTURE try selecting html
|
||||||
startIndex += _ytResultInitialData.Length;
|
startIndex += _ytResultInitialData.Length;
|
||||||
|
|
||||||
var endIndex =
|
var endIndex =
|
||||||
|
@@ -219,7 +219,7 @@ public sealed class MusicPlayer : IMusicPlayer
|
|||||||
errorCount = 0;
|
errorCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo future windows multimedia api
|
// FUTURE windows multimedia api
|
||||||
|
|
||||||
// wait for slightly less than the latency
|
// wait for slightly less than the latency
|
||||||
Thread.Sleep(delay);
|
Thread.Sleep(delay);
|
||||||
|
@@ -42,8 +42,6 @@ public class RadioResolver : IRadioResolver
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.Contains(".pls"))
|
if (query.Contains(".pls"))
|
||||||
//File1=http://armitunes.com:8000/
|
|
||||||
//Regex.Match(query)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -59,11 +57,6 @@ public class RadioResolver : IRadioResolver
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.Contains(".m3u"))
|
if (query.Contains(".m3u"))
|
||||||
/*
|
|
||||||
# This is a comment
|
|
||||||
C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
|
|
||||||
C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
|
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -79,7 +72,6 @@ public class RadioResolver : IRadioResolver
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.Contains(".asx"))
|
if (query.Contains(".asx"))
|
||||||
//<ref href="http://armitunes.com:8000"/>
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -95,12 +87,6 @@ public class RadioResolver : IRadioResolver
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.Contains(".xspf"))
|
if (query.Contains(".xspf"))
|
||||||
/*
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<playlist version="1" xmlns="http://xspf.org/ns/0/">
|
|
||||||
<trackList>
|
|
||||||
<track><location>file:///mp3s/song_1.mp3</location></track>
|
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@@ -264,18 +264,18 @@ public class SearchImageCacher : INService
|
|||||||
return new();
|
return new();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IImageDownloader GetImageDownloader(Booru booru, HttpClient http)
|
private IImageDownloader GetImageDownloader(Booru booru)
|
||||||
=> booru switch
|
=> booru switch
|
||||||
{
|
{
|
||||||
Booru.Danbooru => new DanbooruImageDownloader(http),
|
Booru.Danbooru => new DanbooruImageDownloader(_httpFactory),
|
||||||
Booru.Yandere => new YandereImageDownloader(http),
|
Booru.Yandere => new YandereImageDownloader(_httpFactory),
|
||||||
Booru.Konachan => new KonachanImageDownloader(http),
|
Booru.Konachan => new KonachanImageDownloader(_httpFactory),
|
||||||
Booru.Safebooru => new SafebooruImageDownloader(http),
|
Booru.Safebooru => new SafebooruImageDownloader(_httpFactory),
|
||||||
Booru.E621 => new E621ImageDownloader(http),
|
Booru.E621 => new E621ImageDownloader(_httpFactory),
|
||||||
Booru.Derpibooru => new DerpibooruImageDownloader(http),
|
Booru.Derpibooru => new DerpibooruImageDownloader(_httpFactory),
|
||||||
Booru.Gelbooru => new GelbooruImageDownloader(http),
|
Booru.Gelbooru => new GelbooruImageDownloader(_httpFactory),
|
||||||
Booru.Rule34 => new Rule34ImageDownloader(http),
|
Booru.Rule34 => new Rule34ImageDownloader(_httpFactory),
|
||||||
Booru.Sankaku => new SankakuImageDownloader(http),
|
Booru.Sankaku => new SankakuImageDownloader(_httpFactory),
|
||||||
_ => throw new NotImplementedException($"{booru} downloader not implemented.")
|
_ => throw new NotImplementedException($"{booru} downloader not implemented.")
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -290,8 +290,7 @@ public class SearchImageCacher : INService
|
|||||||
{
|
{
|
||||||
Log.Information("Downloading from {Type} (page {Page})...", type, page);
|
Log.Information("Downloading from {Type} (page {Page})...", type, page);
|
||||||
|
|
||||||
using var http = _httpFactory.CreateClient();
|
var downloader = GetImageDownloader(type);
|
||||||
var downloader = GetImageDownloader(type, http);
|
|
||||||
|
|
||||||
var images = await downloader.DownloadImageDataAsync(tags, page, isExplicit, cancel);
|
var images = await downloader.DownloadImageDataAsync(tags, page, isExplicit, cancel);
|
||||||
if (images.Count == 0)
|
if (images.Count == 0)
|
||||||
|
@@ -9,7 +9,7 @@ public sealed class DanbooruImageDownloader : DapiImageDownloader
|
|||||||
private static readonly ConcurrentDictionary<string, bool> _existentTags = new();
|
private static readonly ConcurrentDictionary<string, bool> _existentTags = new();
|
||||||
private static readonly ConcurrentDictionary<string, bool> _nonexistentTags = new();
|
private static readonly ConcurrentDictionary<string, bool> _nonexistentTags = new();
|
||||||
|
|
||||||
public DanbooruImageDownloader(HttpClient http)
|
public DanbooruImageDownloader(IHttpClientFactory http)
|
||||||
: base(Booru.Danbooru, http, "http://danbooru.donmai.us")
|
: base(Booru.Danbooru, http, "http://danbooru.donmai.us")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,8 @@ public sealed class DanbooruImageDownloader : DapiImageDownloader
|
|||||||
if (_nonexistentTags.ContainsKey(tag))
|
if (_nonexistentTags.ContainsKey(tag))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var tags = await _http.GetFromJsonAsync<DapiTag[]>(
|
using var http = _http.CreateClient();
|
||||||
|
var tags = await http.GetFromJsonAsync<DapiTag[]>(
|
||||||
_baseUrl + "/tags.json" + $"?search[name_or_alias_matches]={tag}",
|
_baseUrl + "/tags.json" + $"?search[name_or_alias_matches]={tag}",
|
||||||
_serializerOptions,
|
_serializerOptions,
|
||||||
cancel);
|
cancel);
|
||||||
|
@@ -7,7 +7,7 @@ public abstract class DapiImageDownloader : ImageDownloader<DapiImageObject>
|
|||||||
{
|
{
|
||||||
protected readonly string _baseUrl;
|
protected readonly string _baseUrl;
|
||||||
|
|
||||||
public DapiImageDownloader(Booru booru, HttpClient http, string baseUrl)
|
public DapiImageDownloader(Booru booru, IHttpClientFactory http, string baseUrl)
|
||||||
: base(booru, http)
|
: base(booru, http)
|
||||||
=> _baseUrl = baseUrl;
|
=> _baseUrl = baseUrl;
|
||||||
|
|
||||||
@@ -43,7 +43,8 @@ public abstract class DapiImageDownloader : ImageDownloader<DapiImageObject>
|
|||||||
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
||||||
|
|
||||||
var uri = $"{_baseUrl}/posts.json?limit=200&tags={tagString}&page={page}";
|
var uri = $"{_baseUrl}/posts.json?limit=200&tags={tagString}&page={page}";
|
||||||
var imageObjects = await _http.GetFromJsonAsync<DapiImageObject[]>(uri, _serializerOptions, cancel);
|
using var http = _http.CreateClient();
|
||||||
|
var imageObjects = await http.GetFromJsonAsync<DapiImageObject[]>(uri, _serializerOptions, cancel);
|
||||||
if (imageObjects is null)
|
if (imageObjects is null)
|
||||||
return new();
|
return new();
|
||||||
return imageObjects.Where(x => x.FileUrl is not null).ToList();
|
return imageObjects.Where(x => x.FileUrl is not null).ToList();
|
||||||
|
@@ -5,7 +5,7 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
|||||||
|
|
||||||
public class DerpibooruImageDownloader : ImageDownloader<DerpiImageObject>
|
public class DerpibooruImageDownloader : ImageDownloader<DerpiImageObject>
|
||||||
{
|
{
|
||||||
public DerpibooruImageDownloader(HttpClient http)
|
public DerpibooruImageDownloader(IHttpClientFactory http)
|
||||||
: base(Booru.Derpibooru, http)
|
: base(Booru.Derpibooru, http)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,8 @@ public class DerpibooruImageDownloader : ImageDownloader<DerpiImageObject>
|
|||||||
$"https://www.derpibooru.org/api/v1/json/search/images?q={tagString.Replace('+', ',')}&per_page=49&page={page}";
|
$"https://www.derpibooru.org/api/v1/json/search/images?q={tagString.Replace('+', ',')}&per_page=49&page={page}";
|
||||||
using var req = new HttpRequestMessage(HttpMethod.Get, uri);
|
using var req = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||||
req.Headers.AddFakeHeaders();
|
req.Headers.AddFakeHeaders();
|
||||||
using var res = await _http.SendAsync(req, cancel);
|
using var http = _http.CreateClient();
|
||||||
|
using var res = await http.SendAsync(req, cancel);
|
||||||
res.EnsureSuccessStatusCode();
|
res.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
var container = await res.Content.ReadFromJsonAsync<DerpiContainer>(_serializerOptions, cancel);
|
var container = await res.Content.ReadFromJsonAsync<DerpiContainer>(_serializerOptions, cancel);
|
||||||
|
@@ -5,7 +5,7 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
|||||||
|
|
||||||
public class E621ImageDownloader : ImageDownloader<E621Object>
|
public class E621ImageDownloader : ImageDownloader<E621Object>
|
||||||
{
|
{
|
||||||
public E621ImageDownloader(HttpClient http)
|
public E621ImageDownloader(IHttpClientFactory http)
|
||||||
: base(Booru.E621, http)
|
: base(Booru.E621, http)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,8 @@ public class E621ImageDownloader : ImageDownloader<E621Object>
|
|||||||
var uri = $"https://e621.net/posts.json?limit=32&tags={tagString}&page={page}";
|
var uri = $"https://e621.net/posts.json?limit=32&tags={tagString}&page={page}";
|
||||||
using var req = new HttpRequestMessage(HttpMethod.Get, uri);
|
using var req = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||||
req.Headers.AddFakeHeaders();
|
req.Headers.AddFakeHeaders();
|
||||||
using var res = await _http.SendAsync(req, cancel);
|
using var http = _http.CreateClient();
|
||||||
|
using var res = await http.SendAsync(req, cancel);
|
||||||
res.EnsureSuccessStatusCode();
|
res.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
var data = await res.Content.ReadFromJsonAsync<E621Response>(_serializerOptions, cancel);
|
var data = await res.Content.ReadFromJsonAsync<E621Response>(_serializerOptions, cancel);
|
||||||
|
@@ -6,7 +6,7 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
|||||||
|
|
||||||
public class GelbooruImageDownloader : ImageDownloader<DapiImageObject>
|
public class GelbooruImageDownloader : ImageDownloader<DapiImageObject>
|
||||||
{
|
{
|
||||||
public GelbooruImageDownloader(HttpClient http)
|
public GelbooruImageDownloader(IHttpClientFactory http)
|
||||||
: base(Booru.Gelbooru, http)
|
: base(Booru.Gelbooru, http)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,8 @@ public class GelbooruImageDownloader : ImageDownloader<DapiImageObject>
|
|||||||
+ $"&tags={tagString}"
|
+ $"&tags={tagString}"
|
||||||
+ $"&pid={page}";
|
+ $"&pid={page}";
|
||||||
using var req = new HttpRequestMessage(HttpMethod.Get, uri);
|
using var req = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||||
using var res = await _http.SendAsync(req, cancel);
|
using var http = _http.CreateClient();
|
||||||
|
using var res = await http.SendAsync(req, cancel);
|
||||||
res.EnsureSuccessStatusCode();
|
res.EnsureSuccessStatusCode();
|
||||||
var resString = await res.Content.ReadAsStringAsync(cancel);
|
var resString = await res.Content.ReadAsStringAsync(cancel);
|
||||||
if (string.IsNullOrWhiteSpace(resString))
|
if (string.IsNullOrWhiteSpace(resString))
|
||||||
|
@@ -8,7 +8,7 @@ public abstract class ImageDownloader<T> : IImageDownloader
|
|||||||
where T : IImageData
|
where T : IImageData
|
||||||
{
|
{
|
||||||
public Booru Booru { get; }
|
public Booru Booru { get; }
|
||||||
protected readonly HttpClient _http;
|
protected readonly IHttpClientFactory _http;
|
||||||
|
|
||||||
protected readonly JsonSerializerOptions _serializerOptions = new()
|
protected readonly JsonSerializerOptions _serializerOptions = new()
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,7 @@ public abstract class ImageDownloader<T> : IImageDownloader
|
|||||||
NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString
|
NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString
|
||||||
};
|
};
|
||||||
|
|
||||||
public ImageDownloader(Booru booru, HttpClient http)
|
public ImageDownloader(Booru booru, IHttpClientFactory http)
|
||||||
{
|
{
|
||||||
_http = http;
|
_http = http;
|
||||||
Booru = booru;
|
Booru = booru;
|
||||||
|
@@ -7,7 +7,7 @@ public sealed class KonachanImageDownloader : ImageDownloader<DapiImageObject>
|
|||||||
{
|
{
|
||||||
private readonly string _baseUrl;
|
private readonly string _baseUrl;
|
||||||
|
|
||||||
public KonachanImageDownloader(HttpClient http)
|
public KonachanImageDownloader(IHttpClientFactory http)
|
||||||
: base(Booru.Konachan, http)
|
: base(Booru.Konachan, http)
|
||||||
=> _baseUrl = "https://konachan.com";
|
=> _baseUrl = "https://konachan.com";
|
||||||
|
|
||||||
@@ -19,7 +19,8 @@ public sealed class KonachanImageDownloader : ImageDownloader<DapiImageObject>
|
|||||||
{
|
{
|
||||||
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
||||||
var uri = $"{_baseUrl}/post.json?s=post&q=index&limit=200&tags={tagString}&page={page}";
|
var uri = $"{_baseUrl}/post.json?s=post&q=index&limit=200&tags={tagString}&page={page}";
|
||||||
var imageObjects = await _http.GetFromJsonAsync<DapiImageObject[]>(uri, _serializerOptions, cancel);
|
using var http = _http.CreateClient();
|
||||||
|
var imageObjects = await http.GetFromJsonAsync<DapiImageObject[]>(uri, _serializerOptions, cancel);
|
||||||
if (imageObjects is null)
|
if (imageObjects is null)
|
||||||
return new();
|
return new();
|
||||||
return imageObjects.Where(x => x.FileUrl is not null).ToList();
|
return imageObjects.Where(x => x.FileUrl is not null).ToList();
|
||||||
|
@@ -5,7 +5,7 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
|||||||
|
|
||||||
public class Rule34ImageDownloader : ImageDownloader<Rule34Object>
|
public class Rule34ImageDownloader : ImageDownloader<Rule34Object>
|
||||||
{
|
{
|
||||||
public Rule34ImageDownloader(HttpClient http)
|
public Rule34ImageDownloader(IHttpClientFactory http)
|
||||||
: base(Booru.Rule34, http)
|
: base(Booru.Rule34, http)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -17,9 +17,21 @@ public class Rule34ImageDownloader : ImageDownloader<Rule34Object>
|
|||||||
CancellationToken cancel = default)
|
CancellationToken cancel = default)
|
||||||
{
|
{
|
||||||
var tagString = ImageDownloaderHelper.GetTagString(tags);
|
var tagString = ImageDownloaderHelper.GetTagString(tags);
|
||||||
var uri = "https://rule34.xxx/index.php?page=dapi&s=post&q=index&json=1&limit=100"
|
var uri = $"https://api.rule34.xxx//index.php?page=dapi&s=post"
|
||||||
+ $"&tags={tagString}&pid={page}";
|
+ $"&q=index"
|
||||||
var images = await _http.GetFromJsonAsync<List<Rule34Object>>(uri, _serializerOptions, cancel);
|
+ $"&json=1"
|
||||||
|
+ $"&limit=100"
|
||||||
|
+ $"&tags={tagString}"
|
||||||
|
+ $"&pid={page}";
|
||||||
|
|
||||||
|
using var http = _http.CreateClient();
|
||||||
|
http.DefaultRequestHeaders
|
||||||
|
.TryAddWithoutValidation("cookie", "cf_clearance=Gg3bVffg9fOL_.9fIdKmu5PJS86eTI.yTrhbR8z2tPc-1652310659-0-250");
|
||||||
|
|
||||||
|
http.DefaultRequestHeaders
|
||||||
|
.TryAddWithoutValidation("user-agent",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36");
|
||||||
|
var images = await http.GetFromJsonAsync<List<Rule34Object>>(uri, _serializerOptions, cancel);
|
||||||
|
|
||||||
if (images is null)
|
if (images is null)
|
||||||
return new();
|
return new();
|
||||||
|
@@ -5,7 +5,7 @@ namespace NadekoBot.Modules.Nsfw.Common;
|
|||||||
|
|
||||||
public class SafebooruImageDownloader : ImageDownloader<SafebooruElement>
|
public class SafebooruImageDownloader : ImageDownloader<SafebooruElement>
|
||||||
{
|
{
|
||||||
public SafebooruImageDownloader(HttpClient http)
|
public SafebooruImageDownloader(IHttpClientFactory http)
|
||||||
: base(Booru.Safebooru, http)
|
: base(Booru.Safebooru, http)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,9 @@ public class SafebooruImageDownloader : ImageDownloader<SafebooruElement>
|
|||||||
var tagString = ImageDownloaderHelper.GetTagString(tags);
|
var tagString = ImageDownloaderHelper.GetTagString(tags);
|
||||||
var uri =
|
var uri =
|
||||||
$"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=200&tags={tagString}&json=1&pid={page}";
|
$"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=200&tags={tagString}&json=1&pid={page}";
|
||||||
var images = await _http.GetFromJsonAsync<List<SafebooruElement>>(uri, _serializerOptions, cancel);
|
|
||||||
|
using var http = _http.CreateClient();
|
||||||
|
var images = await http.GetFromJsonAsync<List<SafebooruElement>>(uri, _serializerOptions, cancel);
|
||||||
if (images is null)
|
if (images is null)
|
||||||
return new();
|
return new();
|
||||||
|
|
||||||
|
@@ -7,11 +7,10 @@ public sealed class SankakuImageDownloader : ImageDownloader<SankakuImageObject>
|
|||||||
{
|
{
|
||||||
private readonly string _baseUrl;
|
private readonly string _baseUrl;
|
||||||
|
|
||||||
public SankakuImageDownloader(HttpClient http)
|
public SankakuImageDownloader(IHttpClientFactory http)
|
||||||
: base(Booru.Sankaku, http)
|
: base(Booru.Sankaku, http)
|
||||||
{
|
{
|
||||||
_baseUrl = "https://capi-v2.sankakucomplex.com";
|
_baseUrl = "https://capi-v2.sankakucomplex.com";
|
||||||
_http.AddFakeHeaders();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<List<SankakuImageObject>> DownloadImagesAsync(
|
public override async Task<List<SankakuImageObject>> DownloadImagesAsync(
|
||||||
@@ -24,9 +23,12 @@ public sealed class SankakuImageDownloader : ImageDownloader<SankakuImageObject>
|
|||||||
var tagString = ImageDownloaderHelper.GetTagString(tags);
|
var tagString = ImageDownloaderHelper.GetTagString(tags);
|
||||||
|
|
||||||
var uri = $"{_baseUrl}/posts?tags={tagString}&limit=50";
|
var uri = $"{_baseUrl}/posts?tags={tagString}&limit=50";
|
||||||
var data = await _http.GetStringAsync(uri);
|
|
||||||
|
using var http = _http.CreateClient();
|
||||||
|
http.AddFakeHeaders();
|
||||||
|
var data = await http.GetStringAsync(uri, cancel);
|
||||||
return JsonSerializer.Deserialize<SankakuImageObject[]>(data, _serializerOptions)
|
return JsonSerializer.Deserialize<SankakuImageObject[]>(data, _serializerOptions)
|
||||||
.Where(x => !string.IsNullOrWhiteSpace(x.FileUrl) && x.FileType.StartsWith("image"))
|
?.Where(x => !string.IsNullOrWhiteSpace(x.FileUrl) && x.FileType.StartsWith("image"))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -7,7 +7,7 @@ public sealed class YandereImageDownloader : ImageDownloader<DapiImageObject>
|
|||||||
{
|
{
|
||||||
private readonly string _baseUrl;
|
private readonly string _baseUrl;
|
||||||
|
|
||||||
public YandereImageDownloader(HttpClient http)
|
public YandereImageDownloader(IHttpClientFactory http)
|
||||||
: base(Booru.Yandere, http)
|
: base(Booru.Yandere, http)
|
||||||
=> _baseUrl = "https://yande.re";
|
=> _baseUrl = "https://yande.re";
|
||||||
|
|
||||||
@@ -20,7 +20,9 @@ public sealed class YandereImageDownloader : ImageDownloader<DapiImageObject>
|
|||||||
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
var tagString = ImageDownloaderHelper.GetTagString(tags, isExplicit);
|
||||||
|
|
||||||
var uri = $"{_baseUrl}/post.json?limit=200&tags={tagString}&page={page}";
|
var uri = $"{_baseUrl}/post.json?limit=200&tags={tagString}&page={page}";
|
||||||
var imageObjects = await _http.GetFromJsonAsync<DapiImageObject[]>(uri, _serializerOptions, cancel);
|
|
||||||
|
using var http = _http.CreateClient();
|
||||||
|
var imageObjects = await http.GetFromJsonAsync<DapiImageObject[]>(uri, _serializerOptions, cancel);
|
||||||
if (imageObjects is null)
|
if (imageObjects is null)
|
||||||
return new();
|
return new();
|
||||||
return imageObjects.Where(x => x.FileUrl is not null).ToList();
|
return imageObjects.Where(x => x.FileUrl is not null).ToList();
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Nsfw.Common;
|
namespace NadekoBot.Modules.Nsfw.Common;
|
||||||
|
|
||||||
public class Rule34Object : IImageData
|
public class Rule34Object : IImageData
|
||||||
@@ -7,7 +9,9 @@ public class Rule34Object : IImageData
|
|||||||
public int Directory { get; init; }
|
public int Directory { get; init; }
|
||||||
public string Tags { get; init; }
|
public string Tags { get; init; }
|
||||||
public int Score { get; init; }
|
public int Score { get; init; }
|
||||||
|
[JsonPropertyName("file_url")]
|
||||||
|
public string FileUrl { get; init; }
|
||||||
|
|
||||||
public ImageData ToCachedImageData(Booru type)
|
public ImageData ToCachedImageData(Booru type)
|
||||||
=> new($"https://img.rule34.xxx//images/{Directory}/{Image}", Booru.Rule34, Tags.Split(' '), Score.ToString());
|
=> new(FileUrl, Booru.Rule34, Tags.Split(' '), Score.ToString());
|
||||||
}
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
#nullable disable
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Permissions;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 1)]
|
||||||
|
public readonly struct CleverBotResponseStr
|
||||||
|
{
|
||||||
|
public const string CLEVERBOT_RESPONSE = "cleverbot:response";
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
#nullable disable
|
||||||
|
using NadekoBot.Common.TypeReaders;
|
||||||
|
using static NadekoBot.Common.TypeReaders.TypeReaderResult;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Permissions;
|
||||||
|
|
||||||
|
public class CleverbotResponseCmdCdTypeReader : NadekoTypeReader<CleverBotResponseStr>
|
||||||
|
{
|
||||||
|
public override ValueTask<TypeReaderResult<CleverBotResponseStr>> ReadAsync(
|
||||||
|
ICommandContext ctx,
|
||||||
|
string input)
|
||||||
|
=> input.ToLowerInvariant() == CleverBotResponseStr.CLEVERBOT_RESPONSE
|
||||||
|
? new(FromSuccess(new CleverBotResponseStr()))
|
||||||
|
: new(FromError<CleverBotResponseStr>(CommandError.ParseFailed, "Not a valid cleverbot"));
|
||||||
|
}
|
@@ -27,9 +27,7 @@ public partial class Permissions
|
|||||||
_db = db;
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
private async Task CmdCooldownInternal(string cmdName, int secs)
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
public async partial Task CmdCooldown(CommandOrCrInfo command, int secs)
|
|
||||||
{
|
{
|
||||||
var channel = (ITextChannel)ctx.Channel;
|
var channel = (ITextChannel)ctx.Channel;
|
||||||
if (secs is < 0 or > 3600)
|
if (secs is < 0 or > 3600)
|
||||||
@@ -38,7 +36,7 @@ public partial class Permissions
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var name = command.Name.ToLowerInvariant();
|
var name = cmdName.ToLowerInvariant();
|
||||||
await using (var uow = _db.GetDbContext())
|
await using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
var config = uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.CommandCooldowns));
|
var config = uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.CommandCooldowns));
|
||||||
@@ -71,6 +69,18 @@ public partial class Permissions
|
|||||||
else
|
else
|
||||||
await ReplyConfirmLocalizedAsync(strs.cmdcd_add(Format.Bold(name), Format.Bold(secs.ToString())));
|
await ReplyConfirmLocalizedAsync(strs.cmdcd_add(Format.Bold(name), Format.Bold(secs.ToString())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[Priority(0)]
|
||||||
|
public partial Task CmdCooldown(CleverBotResponseStr command, int secs)
|
||||||
|
=> CmdCooldownInternal(CleverBotResponseStr.CLEVERBOT_RESPONSE, secs);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[Priority(1)]
|
||||||
|
public partial Task CmdCooldown(CommandOrCrInfo command, int secs)
|
||||||
|
=> CmdCooldownInternal(command.Name, secs);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
|
@@ -157,18 +157,21 @@ public partial class Searches
|
|||||||
var marketCap = usd.MarketCap.ToString("C0", localCulture);
|
var marketCap = usd.MarketCap.ToString("C0", localCulture);
|
||||||
var dominance = (usd.MarketCapDominance / 100).ToString("P2", localCulture);
|
var dominance = (usd.MarketCapDominance / 100).ToString("P2", localCulture);
|
||||||
|
|
||||||
|
await using var sparkline = await _service.GetSparklineAsync(crypto.Id, usd.PercentChange7d >= 0);
|
||||||
|
var fileName = $"{crypto.Slug}_7d.png";
|
||||||
|
|
||||||
var toSend = _eb.Create()
|
var toSend = _eb.Create()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithAuthor($"#{crypto.CmcRank}")
|
.WithAuthor($"#{crypto.CmcRank}")
|
||||||
.WithTitle($"{crypto.Name} ({crypto.Symbol})")
|
.WithTitle($"{crypto.Name} ({crypto.Symbol})")
|
||||||
.WithUrl($"https://coinmarketcap.com/currencies/{crypto.Slug}/")
|
.WithUrl($"https://coinmarketcap.com/currencies/{crypto.Slug}/")
|
||||||
.WithThumbnailUrl( $"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png")
|
.WithThumbnailUrl($"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png")
|
||||||
.AddField(GetText(strs.market_cap), marketCap, true)
|
.AddField(GetText(strs.market_cap), marketCap, true)
|
||||||
.AddField(GetText(strs.price), price, true)
|
.AddField(GetText(strs.price), price, true)
|
||||||
.AddField(GetText(strs.volume_24h), volume, true)
|
.AddField(GetText(strs.volume_24h), volume, true)
|
||||||
.AddField(GetText(strs.change_7d_24h), $"{sevenDay} / {lastDay}", true)
|
.AddField(GetText(strs.change_7d_24h), $"{sevenDay} / {lastDay}", true)
|
||||||
.AddField(GetText(strs.market_cap_dominance), dominance, true)
|
.AddField(GetText(strs.market_cap_dominance), dominance, true)
|
||||||
.WithImageUrl($"https://s3.coinmarketcap.com/generated/sparklines/web/7d/usd/{crypto.Id}.png");
|
.WithImageUrl($"attachment://{fileName}");
|
||||||
|
|
||||||
if (crypto.CirculatingSupply is double cs)
|
if (crypto.CirculatingSupply is double cs)
|
||||||
{
|
{
|
||||||
@@ -185,9 +188,9 @@ public partial class Searches
|
|||||||
toSend.AddField(GetText(strs.circulating_supply), csStr, true);
|
toSend.AddField(GetText(strs.circulating_supply), csStr, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await ctx.Channel.EmbedAsync(toSend);
|
await ctx.Channel.SendFileAsync(sparkline, fileName, embed: toSend.Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,13 @@
|
|||||||
#nullable disable
|
#nullable enable
|
||||||
using NadekoBot.Modules.Searches.Common;
|
using NadekoBot.Modules.Searches.Common;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using System.Globalization;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
|
using System.Xml;
|
||||||
|
using Color = SixLabors.ImageSharp.Color;
|
||||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||||
using StringExtensions = NadekoBot.Extensions.StringExtensions;
|
using StringExtensions = NadekoBot.Extensions.StringExtensions;
|
||||||
|
|
||||||
@@ -21,7 +28,75 @@ public class CryptoService : INService
|
|||||||
_creds = creds;
|
_creds = creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(CmcResponseData Data, CmcResponseData Nearest)> GetCryptoData(string name)
|
private PointF[] GetSparklinePointsFromSvgText(string svgText)
|
||||||
|
{
|
||||||
|
var xml = new XmlDocument();
|
||||||
|
xml.LoadXml(svgText);
|
||||||
|
|
||||||
|
var gElement = xml["svg"]?["g"];
|
||||||
|
if (gElement is null)
|
||||||
|
return Array.Empty<PointF>();
|
||||||
|
|
||||||
|
Span<PointF> points = new PointF[gElement.ChildNodes.Count];
|
||||||
|
var cnt = 0;
|
||||||
|
|
||||||
|
bool GetValuesFromAttributes(XmlAttributeCollection attrs,
|
||||||
|
out float x1,
|
||||||
|
out float y1,
|
||||||
|
out float x2,
|
||||||
|
out float y2)
|
||||||
|
{
|
||||||
|
(x1, y1, x2, y2) = (0, 0, 0, 0);
|
||||||
|
return attrs["x1"]?.Value is string x1Str
|
||||||
|
&& float.TryParse(x1Str, NumberStyles.Any, CultureInfo.InvariantCulture, out x1)
|
||||||
|
&& attrs["y1"]?.Value is string y1Str
|
||||||
|
&& float.TryParse(y1Str, NumberStyles.Any, CultureInfo.InvariantCulture, out y1)
|
||||||
|
&& attrs["x2"]?.Value is string x2Str
|
||||||
|
&& float.TryParse(x2Str, NumberStyles.Any, CultureInfo.InvariantCulture, out x2)
|
||||||
|
&& attrs["y2"]?.Value is string y2Str
|
||||||
|
&& float.TryParse(y2Str, NumberStyles.Any, CultureInfo.InvariantCulture, out y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (XmlElement x in gElement.ChildNodes)
|
||||||
|
{
|
||||||
|
if (x.Name != "line")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (GetValuesFromAttributes(x.Attributes, out var x1, out var y1, out var x2, out var y2))
|
||||||
|
{
|
||||||
|
points[cnt++] = new(x1, y1);
|
||||||
|
// this point will be set twice to the same value
|
||||||
|
// on all points except the last one
|
||||||
|
if(cnt + 1 < points.Length)
|
||||||
|
points[cnt + 1] = new(x2, y2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cnt == 0)
|
||||||
|
return Array.Empty<PointF>();
|
||||||
|
|
||||||
|
return points.Slice(0, cnt).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SixLabors.ImageSharp.Image<Rgba32> GenerateSparklineChart(PointF[] points, bool up)
|
||||||
|
{
|
||||||
|
const int width = 164;
|
||||||
|
const int height = 48;
|
||||||
|
|
||||||
|
var img = new Image<Rgba32>(width, height, Color.Transparent);
|
||||||
|
var color = up
|
||||||
|
? Color.Green
|
||||||
|
: Color.FromRgb(220, 0, 0);
|
||||||
|
|
||||||
|
img.Mutate(x =>
|
||||||
|
{
|
||||||
|
x.DrawLines(color, 2, points);
|
||||||
|
});
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(CmcResponseData? Data, CmcResponseData? Nearest)> GetCryptoData(string name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
return (null, null);
|
return (null, null);
|
||||||
@@ -29,10 +104,10 @@ public class CryptoService : INService
|
|||||||
name = name.ToUpperInvariant();
|
name = name.ToUpperInvariant();
|
||||||
var cryptos = await GetCryptoDataInternal();
|
var cryptos = await GetCryptoDataInternal();
|
||||||
|
|
||||||
if (cryptos is null)
|
if (cryptos is null or { Count: 0 })
|
||||||
return (null, null);
|
return (null, null);
|
||||||
|
|
||||||
var crypto = cryptos?.FirstOrDefault(x
|
var crypto = cryptos.FirstOrDefault(x
|
||||||
=> x.Slug.ToUpperInvariant() == name
|
=> x.Slug.ToUpperInvariant() == name
|
||||||
|| x.Name.ToUpperInvariant() == name
|
|| x.Name.ToUpperInvariant() == name
|
||||||
|| x.Symbol.ToUpperInvariant() == name);
|
|| x.Symbol.ToUpperInvariant() == name);
|
||||||
@@ -50,7 +125,7 @@ public class CryptoService : INService
|
|||||||
return (null, nearest.Elem);
|
return (null, nearest.Elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<CmcResponseData>> GetCryptoDataInternal()
|
public async Task<List<CmcResponseData>?> GetCryptoDataInternal()
|
||||||
{
|
{
|
||||||
await _getCryptoLock.WaitAsync();
|
await _getCryptoLock.WaitAsync();
|
||||||
try
|
try
|
||||||
@@ -79,6 +154,9 @@ public class CryptoService : INService
|
|||||||
"",
|
"",
|
||||||
TimeSpan.FromHours(2));
|
TimeSpan.FromHours(2));
|
||||||
|
|
||||||
|
if (fullStrData is null)
|
||||||
|
return default;
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<CryptoResponse>(fullStrData)?.Data ?? new();
|
return JsonSerializer.Deserialize<CryptoResponse>(fullStrData)?.Data ?? new();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -91,4 +169,50 @@ public class CryptoService : INService
|
|||||||
_getCryptoLock.Release();
|
_getCryptoLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Stream?> GetSparklineAsync(int id, bool up)
|
||||||
|
{
|
||||||
|
var key = $"crypto:sparkline:{id}";
|
||||||
|
|
||||||
|
// attempt to get from cache
|
||||||
|
var db = _cache.Redis.GetDatabase();
|
||||||
|
byte[] bytes = await db.StringGetAsync(key);
|
||||||
|
// if it succeeds, return it
|
||||||
|
if (bytes is { Length: > 0 })
|
||||||
|
{
|
||||||
|
return bytes.ToStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it fails, generate a new one
|
||||||
|
var points = await DownloadSparklinePointsAsync(id);
|
||||||
|
if (points is null)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
var sparkline = GenerateSparklineChart(points, up);
|
||||||
|
|
||||||
|
// add to cache for 1h and return it
|
||||||
|
|
||||||
|
var stream = sparkline.ToStream();
|
||||||
|
await db.StringSetAsync(key, stream.ToArray(), expiry: TimeSpan.FromHours(1));
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<PointF[]?> DownloadSparklinePointsAsync(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var http = _httpFactory.CreateClient();
|
||||||
|
var str = await http.GetStringAsync(
|
||||||
|
$"https://s3.coinmarketcap.com/generated/sparklines/web/7d/usd/{id}.svg");
|
||||||
|
var points = GetSparklinePointsFromSvgText(str);
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex,
|
||||||
|
"Exception occurred while downloading sparkline points: {ErrorMessage}",
|
||||||
|
ex.Message);
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,9 @@
|
|||||||
using System.Text.Json;
|
using CsvHelper;
|
||||||
using YahooFinanceApi;
|
using CsvHelper.Configuration;
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Searches;
|
namespace NadekoBot.Modules.Searches;
|
||||||
|
|
||||||
@@ -17,19 +21,14 @@ public sealed class DefaultStockDataService : IStockDataService, INService
|
|||||||
if (!query.IsAlphaNumeric())
|
if (!query.IsAlphaNumeric())
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
var symbols = await Yahoo.Symbols(query)
|
using var http = _httpClientFactory.CreateClient();
|
||||||
.Fields(Field.LongName,
|
var data = await http.GetFromJsonAsync<YahooQueryModel>(
|
||||||
Field.Symbol,
|
$"https://query1.finance.yahoo.com/v7/finance/quote?symbols={query}");
|
||||||
Field.RegularMarketPrice,
|
|
||||||
Field.RegularMarketPreviousClose,
|
|
||||||
Field.MarketCap,
|
|
||||||
Field.FiftyDayAverageChangePercent,
|
|
||||||
Field.TwoHundredDayAverageChangePercent,
|
|
||||||
Field.AverageDailyVolume10Day,
|
|
||||||
Field.FullExchangeName)
|
|
||||||
.QueryAsync();
|
|
||||||
|
|
||||||
var symbol = symbols.Values.FirstOrDefault();
|
if (data is null)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
var symbol = data.QuoteResponse.Result.FirstOrDefault();
|
||||||
|
|
||||||
if (symbol is null)
|
if (symbol is null)
|
||||||
return default;
|
return default;
|
||||||
@@ -79,11 +78,25 @@ public sealed class DefaultStockDataService : IStockDataService, INService
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CsvConfiguration _csvConfig = new(CultureInfo.InvariantCulture)
|
||||||
|
{
|
||||||
|
PrepareHeaderForMatch = args => args.Header.Humanize(LetterCasing.Title)
|
||||||
|
};
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<CandleData>> GetCandleDataAsync(string query)
|
public async Task<IReadOnlyCollection<CandleData>> GetCandleDataAsync(string query)
|
||||||
{
|
{
|
||||||
var candles = await Yahoo.GetHistoricalAsync(query, DateTime.Now.Subtract(30.Days()));
|
using var http = _httpClientFactory.CreateClient();
|
||||||
|
await using var resStream = await http.GetStreamAsync(
|
||||||
|
$"https://query1.finance.yahoo.com/v7/finance/download/{query}"
|
||||||
|
+ $"?period1={DateTime.UtcNow.Subtract(30.Days()).ToTimestamp().Seconds}"
|
||||||
|
+ $"&period2={DateTime.UtcNow.ToTimestamp().Seconds}"
|
||||||
|
+ "&interval=1d");
|
||||||
|
|
||||||
return candles
|
using var textReader = new StreamReader(resStream);
|
||||||
|
using var csv = new CsvReader(textReader, _csvConfig);
|
||||||
|
var records = csv.GetRecords<YahooFinanceCandleData>().ToArray();
|
||||||
|
|
||||||
|
return records
|
||||||
.Map(static x => new CandleData(x.Open, x.Close, x.High, x.Low, x.Volume));
|
.Map(static x => new CandleData(x.Open, x.Close, x.High, x.Low, x.Volume));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
#nullable disable
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Searches;
|
||||||
|
|
||||||
|
public class QuoteResponse
|
||||||
|
{
|
||||||
|
public class ResultModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("longName")]
|
||||||
|
public string LongName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("regularMarketPrice")]
|
||||||
|
public double RegularMarketPrice { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("regularMarketPreviousClose")]
|
||||||
|
public double RegularMarketPreviousClose { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("fullExchangeName")]
|
||||||
|
public string FullExchangeName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("averageDailyVolume10Day")]
|
||||||
|
public int AverageDailyVolume10Day { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("fiftyDayAverageChangePercent")]
|
||||||
|
public double FiftyDayAverageChangePercent { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("twoHundredDayAverageChangePercent")]
|
||||||
|
public double TwoHundredDayAverageChangePercent { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("marketCap")]
|
||||||
|
public long MarketCap { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("symbol")]
|
||||||
|
public string Symbol { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("result")]
|
||||||
|
public List<ResultModel> Result { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("error")]
|
||||||
|
public object Error { get; set; }
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
namespace NadekoBot.Modules.Searches;
|
||||||
|
|
||||||
|
public class YahooFinanceCandleData
|
||||||
|
{
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public decimal Open { get; set; }
|
||||||
|
public decimal High { get; set; }
|
||||||
|
public decimal Low { get; set; }
|
||||||
|
public decimal Close { get; set; }
|
||||||
|
public decimal AdjClose { get; set; }
|
||||||
|
public long Volume { get; set; }
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Searches;
|
||||||
|
|
||||||
|
public class YahooQueryModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("quoteResponse")]
|
||||||
|
public QuoteResponse QuoteResponse { get; set; } = null!;
|
||||||
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using CodeHollow.FeedReader;
|
using CodeHollow.FeedReader;
|
||||||
using CodeHollow.FeedReader.Feeds;
|
using CodeHollow.FeedReader.Feeds;
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
@@ -10,11 +12,12 @@ namespace NadekoBot.Modules.Searches.Services;
|
|||||||
public class FeedsService : INService
|
public class FeedsService : INService
|
||||||
{
|
{
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly ConcurrentDictionary<string, HashSet<FeedSub>> _subs;
|
private readonly ConcurrentDictionary<string, List<FeedSub>> _subs;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly IEmbedBuilderService _eb;
|
private readonly IEmbedBuilderService _eb;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, DateTime> _lastPosts = new();
|
private readonly ConcurrentDictionary<string, DateTime> _lastPosts = new();
|
||||||
|
private readonly Dictionary<string, uint> _errorCounters = new();
|
||||||
|
|
||||||
public FeedsService(
|
public FeedsService(
|
||||||
Bot bot,
|
Bot bot,
|
||||||
@@ -33,7 +36,7 @@ public class FeedsService : INService
|
|||||||
.ToList()
|
.ToList()
|
||||||
.SelectMany(x => x.FeedSubs)
|
.SelectMany(x => x.FeedSubs)
|
||||||
.GroupBy(x => x.Url.ToLower())
|
.GroupBy(x => x.Url.ToLower())
|
||||||
.ToDictionary(x => x.Key, x => x.ToHashSet())
|
.ToDictionary(x => x.Key, x => x.ToList())
|
||||||
.ToConcurrent();
|
.ToConcurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +46,38 @@ public class FeedsService : INService
|
|||||||
_ = Task.Run(TrackFeeds);
|
_ = Task.Run(TrackFeeds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearErrors(string url)
|
||||||
|
=> _errorCounters.Remove(url);
|
||||||
|
|
||||||
|
private async Task<uint> AddError(string url, List<int> ids)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var newValue = _errorCounters[url] = _errorCounters.GetValueOrDefault(url) + 1;
|
||||||
|
|
||||||
|
if (newValue >= 100)
|
||||||
|
{
|
||||||
|
// remove from db
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
await ctx.GetTable<FeedSub>()
|
||||||
|
.DeleteAsync(x => ids.Contains(x.Id));
|
||||||
|
|
||||||
|
// remove from the local cache
|
||||||
|
_subs.TryRemove(url, out _);
|
||||||
|
|
||||||
|
// reset the error counter
|
||||||
|
ClearErrors(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Error adding rss errors...");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<EmbedBuilder> TrackFeeds()
|
public async Task<EmbedBuilder> TrackFeeds()
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
@@ -53,7 +88,7 @@ public class FeedsService : INService
|
|||||||
if (kvp.Value.Count == 0)
|
if (kvp.Value.Count == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var rssUrl = kvp.Key;
|
var rssUrl = kvp.Value.First().Url;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var feed = await FeedReader.ReadAsync(rssUrl);
|
var feed = await FeedReader.ReadAsync(rssUrl);
|
||||||
@@ -134,21 +169,32 @@ public class FeedsService : INService
|
|||||||
embed.WithDescription(desc.TrimTo(2048));
|
embed.WithDescription(desc.TrimTo(2048));
|
||||||
|
|
||||||
//send the created embed to all subscribed channels
|
//send the created embed to all subscribed channels
|
||||||
var feedSendTasks = kvp.Value.Where(x => x.GuildConfig is not null)
|
var feedSendTasks = kvp.Value
|
||||||
|
.Where(x => x.GuildConfig is not null)
|
||||||
.Select(x => _client.GetGuild(x.GuildConfig.GuildId)
|
.Select(x => _client.GetGuild(x.GuildConfig.GuildId)
|
||||||
?.GetTextChannel(x.ChannelId))
|
?.GetTextChannel(x.ChannelId))
|
||||||
.Where(x => x is not null)
|
.Where(x => x is not null)
|
||||||
.Select(x => x.EmbedAsync(embed));
|
.Select(x => x.EmbedAsync(embed));
|
||||||
|
|
||||||
allSendTasks.Add(feedSendTasks.WhenAll());
|
allSendTasks.Add(feedSendTasks.WhenAll());
|
||||||
|
|
||||||
|
// as data retrieval was sucessful, reset error counter
|
||||||
|
ClearErrors(rssUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
var errorCount = await AddError(rssUrl, kvp.Value.Select(x => x.Id).ToList());
|
||||||
|
|
||||||
|
Log.Warning("An error occured while getting rss stream ({ErrorCount} / 100) {RssFeed}"
|
||||||
|
+ "\n {Message}",
|
||||||
|
errorCount,
|
||||||
|
rssUrl,
|
||||||
|
$"[{ex.GetType().Name}]: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(Task.WhenAll(allSendTasks), Task.Delay(10000));
|
await Task.WhenAll(Task.WhenAll(allSendTasks), Task.Delay(30000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +230,7 @@ public class FeedsService : INService
|
|||||||
foreach (var feed in gc.FeedSubs)
|
foreach (var feed in gc.FeedSubs)
|
||||||
{
|
{
|
||||||
_subs.AddOrUpdate(feed.Url.ToLower(),
|
_subs.AddOrUpdate(feed.Url.ToLower(),
|
||||||
new HashSet<FeedSub>
|
new List<FeedSub>
|
||||||
{
|
{
|
||||||
feed
|
feed
|
||||||
},
|
},
|
||||||
@@ -212,7 +258,7 @@ public class FeedsService : INService
|
|||||||
return false;
|
return false;
|
||||||
var toRemove = items[index];
|
var toRemove = items[index];
|
||||||
_subs.AddOrUpdate(toRemove.Url.ToLower(),
|
_subs.AddOrUpdate(toRemove.Url.ToLower(),
|
||||||
new HashSet<FeedSub>(),
|
new List<FeedSub>(),
|
||||||
(_, old) =>
|
(_, old) =>
|
||||||
{
|
{
|
||||||
old.Remove(toRemove);
|
old.Remove(toRemove);
|
||||||
|
@@ -0,0 +1,65 @@
|
|||||||
|
using NadekoBot.Modules.Searches.GoogleScrape;
|
||||||
|
using NadekoBot.Modules.Searches.Youtube;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Searches;
|
||||||
|
|
||||||
|
public sealed class DefaultSearchServiceFactory : ISearchServiceFactory, INService
|
||||||
|
{
|
||||||
|
private readonly SearchesConfigService _scs;
|
||||||
|
private readonly SearxSearchService _sss;
|
||||||
|
private readonly GoogleSearchService _gss;
|
||||||
|
|
||||||
|
private readonly YtdlpYoutubeSearchService _ytdlp;
|
||||||
|
private readonly YtdlYoutubeSearchService _ytdl;
|
||||||
|
private readonly YoutubeDataApiSearchService _ytdata;
|
||||||
|
private readonly InvidiousYtSearchService _iYtSs;
|
||||||
|
private readonly GoogleScrapeService _gscs;
|
||||||
|
|
||||||
|
public DefaultSearchServiceFactory(
|
||||||
|
SearchesConfigService scs,
|
||||||
|
GoogleSearchService gss,
|
||||||
|
GoogleScrapeService gscs,
|
||||||
|
SearxSearchService sss,
|
||||||
|
YtdlpYoutubeSearchService ytdlp,
|
||||||
|
YtdlYoutubeSearchService ytdl,
|
||||||
|
YoutubeDataApiSearchService ytdata,
|
||||||
|
InvidiousYtSearchService iYtSs)
|
||||||
|
{
|
||||||
|
_scs = scs;
|
||||||
|
_sss = sss;
|
||||||
|
_gss = gss;
|
||||||
|
_gscs = gscs;
|
||||||
|
_iYtSs = iYtSs;
|
||||||
|
|
||||||
|
_ytdlp = ytdlp;
|
||||||
|
_ytdl = ytdl;
|
||||||
|
_ytdata = ytdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISearchService GetSearchService(string? hint = null)
|
||||||
|
=> _scs.Data.WebSearchEngine switch
|
||||||
|
{
|
||||||
|
WebSearchEngine.Google => _gss,
|
||||||
|
WebSearchEngine.Google_Scrape => _gscs,
|
||||||
|
WebSearchEngine.Searx => _sss,
|
||||||
|
_ => _gss
|
||||||
|
};
|
||||||
|
|
||||||
|
public ISearchService GetImageSearchService(string? hint = null)
|
||||||
|
=> _scs.Data.ImgSearchEngine switch
|
||||||
|
{
|
||||||
|
ImgSearchEngine.Google => _gss,
|
||||||
|
ImgSearchEngine.Searx => _sss,
|
||||||
|
_ => _gss
|
||||||
|
};
|
||||||
|
|
||||||
|
public IYoutubeSearchService GetYoutubeSearchService(string? hint = null)
|
||||||
|
=> _scs.Data.YtProvider switch
|
||||||
|
{
|
||||||
|
YoutubeSearcher.YtDataApiv3 => _ytdata,
|
||||||
|
YoutubeSearcher.Ytdlp => _ytdlp,
|
||||||
|
YoutubeSearcher.Ytdl => _ytdl,
|
||||||
|
YoutubeSearcher.Invidious => _iYtSs,
|
||||||
|
_ => _ytdl
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,65 @@
|
|||||||
|
// using AngleSharp.Html.Dom;
|
||||||
|
// using MorseCode.ITask;
|
||||||
|
// using NadekoBot.Modules.Searches.Common;
|
||||||
|
// using System.Net;
|
||||||
|
//
|
||||||
|
// namespace NadekoBot.Modules.Searches.DuckDuckGo;
|
||||||
|
//
|
||||||
|
// public sealed class DuckDuckGoSeachService : SearchServiceBase
|
||||||
|
// {
|
||||||
|
// private static readonly HtmlParser _googleParser = new(new()
|
||||||
|
// {
|
||||||
|
// IsScripting = false,
|
||||||
|
// IsEmbedded = false,
|
||||||
|
// IsSupportingProcessingInstructions = false,
|
||||||
|
// IsKeepingSourceReferences = false,
|
||||||
|
// IsNotSupportingFrames = true
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// public override async ITask<SearchResultData> SearchAsync(string query)
|
||||||
|
// {
|
||||||
|
// query = WebUtility.UrlEncode(query)?.Replace(' ', '+');
|
||||||
|
//
|
||||||
|
// var fullQueryLink = "https://html.duckduckgo.com/html";
|
||||||
|
//
|
||||||
|
// using var http = _httpFactory.CreateClient();
|
||||||
|
// http.DefaultRequestHeaders.Clear();
|
||||||
|
// http.DefaultRequestHeaders.Add("User-Agent",
|
||||||
|
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36");
|
||||||
|
//
|
||||||
|
// using var formData = new MultipartFormDataContent();
|
||||||
|
// formData.Add(new StringContent(query), "q");
|
||||||
|
// using var response = await http.PostAsync(fullQueryLink, formData);
|
||||||
|
// var content = await response.Content.ReadAsStringAsync();
|
||||||
|
//
|
||||||
|
// using var document = await _googleParser.ParseDocumentAsync(content);
|
||||||
|
// var searchResults = document.QuerySelector(".results");
|
||||||
|
// var elems = searchResults.QuerySelectorAll(".result");
|
||||||
|
//
|
||||||
|
// if (!elems.Any())
|
||||||
|
// return default;
|
||||||
|
//
|
||||||
|
// var results = elems.Select(elem =>
|
||||||
|
// {
|
||||||
|
// if (elem.QuerySelector(".result__a") is not IHtmlAnchorElement anchor)
|
||||||
|
// return null;
|
||||||
|
//
|
||||||
|
// var href = anchor.Href;
|
||||||
|
// var name = anchor.TextContent;
|
||||||
|
//
|
||||||
|
// if (string.IsNullOrWhiteSpace(href) || string.IsNullOrWhiteSpace(name))
|
||||||
|
// return null;
|
||||||
|
//
|
||||||
|
// var txt = elem.QuerySelector(".result__snippet")?.TextContent;
|
||||||
|
//
|
||||||
|
// if (string.IsNullOrWhiteSpace(txt))
|
||||||
|
// return null;
|
||||||
|
//
|
||||||
|
// return new GoogleSearchResult(name, href, txt);
|
||||||
|
// })
|
||||||
|
// .Where(x => x is not null)
|
||||||
|
// .ToList();
|
||||||
|
//
|
||||||
|
// return new(results.AsReadOnly(), fullQueryLink, "0");
|
||||||
|
// }
|
||||||
|
// }
|
@@ -0,0 +1,22 @@
|
|||||||
|
using NadekoBot.Modules.Searches;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services;
|
||||||
|
|
||||||
|
public sealed class GoogleCustomSearchResult : ISearchResult
|
||||||
|
{
|
||||||
|
ISearchResultInformation ISearchResult.Info
|
||||||
|
=> Info;
|
||||||
|
|
||||||
|
public string? Answer
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
IReadOnlyCollection<ISearchResultEntry> ISearchResult.Entries
|
||||||
|
=> Entries ?? Array.Empty<OfficialGoogleSearchResultEntry>();
|
||||||
|
|
||||||
|
[JsonPropertyName("searchInformation")]
|
||||||
|
public GoogleSearchResultInformation Info { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("items")]
|
||||||
|
public IReadOnlyCollection<OfficialGoogleSearchResultEntry>? Entries { get; init; }
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services;
|
||||||
|
|
||||||
|
public sealed class GoogleImageData
|
||||||
|
{
|
||||||
|
[JsonPropertyName("contextLink")]
|
||||||
|
public string ContextLink { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("thumbnailLink")]
|
||||||
|
public string ThumbnailLink { get; init; } = null!;
|
||||||
|
}
|
@@ -0,0 +1,19 @@
|
|||||||
|
using NadekoBot.Modules.Searches;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services;
|
||||||
|
|
||||||
|
public sealed class GoogleImageResult : IImageSearchResult
|
||||||
|
{
|
||||||
|
ISearchResultInformation IImageSearchResult.Info
|
||||||
|
=> Info;
|
||||||
|
|
||||||
|
IReadOnlyCollection<IImageSearchResultEntry> IImageSearchResult.Entries
|
||||||
|
=> Entries ?? Array.Empty<GoogleImageResultEntry>();
|
||||||
|
|
||||||
|
[JsonPropertyName("searchInformation")]
|
||||||
|
public GoogleSearchResultInformation Info { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("items")]
|
||||||
|
public IReadOnlyCollection<GoogleImageResultEntry>? Entries { get; init; }
|
||||||
|
}
|
@@ -0,0 +1,13 @@
|
|||||||
|
using NadekoBot.Modules.Searches;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services;
|
||||||
|
|
||||||
|
public sealed class GoogleImageResultEntry : IImageSearchResultEntry
|
||||||
|
{
|
||||||
|
[JsonPropertyName("link")]
|
||||||
|
public string Link { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("image")]
|
||||||
|
public GoogleImageData Image { get; init; } = null!;
|
||||||
|
}
|
@@ -0,0 +1,13 @@
|
|||||||
|
using NadekoBot.Modules.Searches;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services;
|
||||||
|
|
||||||
|
public sealed class GoogleSearchResultInformation : ISearchResultInformation
|
||||||
|
{
|
||||||
|
[JsonPropertyName("formattedTotalResults")]
|
||||||
|
public string TotalResults { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("formattedSearchTime")]
|
||||||
|
public string SearchTime { get; init; } = null!;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user