From 97f1405a941b8d9334e4f34d4165cd73bf945c15 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Sat, 7 Dec 2024 16:46:06 +0000 Subject: [PATCH] add: added addrolereward and removerolereward events for .notify add: added .notify with no params showing events with descriptions add: .winlb docs: updated docs dev: updated discord.net, redid migrations --- CHANGELOG.md | 38 ++++++ docs/config-guide.md | 2 +- docs/donate.md | 2 +- docs/guides/windows-guide.md | 79 +----------- docs/guides/windows-source-guide.md | 77 ++++++++++++ docs/index.md | 2 + src/Nadeko.Medusa/Nadeko.Medusa.csproj | 2 +- src/NadekoBot/Db/Models/Notify.cs | 2 + src/NadekoBot/Db/NadekoContext.cs | 19 +-- src/NadekoBot/Migrations/MigrationQueries.cs | 10 ++ ...050_awardedxp-temprole-notify.Designer.cs} | 5 +- ...241207150050_awardedxp-temprole-notify.cs} | 11 ++ .../PostgreSqlContextModelSnapshot.cs | 3 + ...041_awardedxp-temprole-notify.Designer.cs} | 4 +- ...241207150041_awardedxp-temprole-notify.cs} | 10 ++ .../NadekoSqliteContextModelSnapshot.cs | 2 + .../Notify/Models/AddRoleRewardNotifyModel.cs | 36 ++++++ .../Notify/Models/LevelUpNotifyModel.cs | 8 +- .../Notify/Models/ProtectionNotifyModel.cs | 34 ++++++ .../Models/RemoveRoleRewardNotifyModel.cs | 36 ++++++ .../Administration/Notify/NotifyCommands.cs | 96 ++++++++++++++- .../Notify/NotifyModelExtensions.cs | 8 ++ .../Administration/Notify/NotifyService.cs | 24 ++++ .../Protection/ProtectionService.cs | 30 ----- .../Modules/Gambling/BetStatsCommands.cs | 113 ++++++++++++++---- .../Modules/Gambling/UserBetStatsService.cs | 14 ++- src/NadekoBot/Modules/Searches/Searches.cs | 94 +++++++++------ src/NadekoBot/Modules/Xp/XpService.cs | 40 ++++++- src/NadekoBot/NadekoBot.csproj | 2 +- src/NadekoBot/_common/DoAsUserMessage.cs | 3 + src/NadekoBot/_common/Services/UserService.cs | 2 +- src/NadekoBot/data/aliases.yml | 15 ++- .../data/strings/commands/commands.en-US.yml | 39 +++++- .../strings/responses/responses.en-US.json | 25 ++-- 34 files changed, 687 insertions(+), 200 deletions(-) create mode 100644 docs/guides/windows-source-guide.md rename src/NadekoBot/Migrations/PostgreSql/{20241205052146_awardedxp-temprole-notify.Designer.cs => 20241207150050_awardedxp-temprole-notify.Designer.cs} (99%) rename src/NadekoBot/Migrations/PostgreSql/{20241205052146_awardedxp-temprole-notify.cs => 20241207150050_awardedxp-temprole-notify.cs} (91%) rename src/NadekoBot/Migrations/Sqlite/{20241205052137_awardedxp-temprole-notify.Designer.cs => 20241207150041_awardedxp-temprole-notify.Designer.cs} (99%) rename src/NadekoBot/Migrations/Sqlite/{20241205052137_awardedxp-temprole-notify.cs => 20241207150041_awardedxp-temprole-notify.cs} (91%) create mode 100644 src/NadekoBot/Modules/Administration/Notify/Models/AddRoleRewardNotifyModel.cs create mode 100644 src/NadekoBot/Modules/Administration/Notify/Models/ProtectionNotifyModel.cs create mode 100644 src/NadekoBot/Modules/Administration/Notify/Models/RemoveRoleRewardNotifyModel.cs create mode 100644 src/NadekoBot/Modules/Administration/Notify/NotifyModelExtensions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3546cdcf9..36fc242f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,44 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o +## [5.3.0] - 07.12.2024 + +## Added + +- Added `.minesweeper` / `.mw` command - spoiler-based minesweeper minigame. Just for fun +- Added `.temprole` command - add a role to a user for a certain amount of time, after which the role will be removed +- Added `.xplevelset` - you can now set a level for a user in your server +- Added `.winlb` command - leaderboard of top gambling wins +- Added `.notify` command + - Specify an event to be notified about, and the bot will post the specified message in the current channel when the event occurs + - A few events supported right now: + - `UserLevelUp` when user levels up in the server + - `AddRoleReward` when a role is added to a user through .xpreward system + - `RemoveRoleReward` when a role is removed from a user through .xpreward system + - `Protection` when antialt, antiraid or antispam protection is triggered +- Added `.banner` command to see someone's banner +- Selfhosters: + - Added `.dmmod` and `.dmcmd` - you can now disable or enable whether commands or modules can be executed in bot's DMs + +## Changed + +- Giveaway improvements + - Now mentions winners in a separate message + - Shows the timestamp of when the giveaway ends +- Xp Changes + - Removed awarded xp (the number in the brackets on the xp card) + - Awarded xp, (or the new level set) now directly apply to user's real xp + - Server xp notifications are now set by the server admin/manager in a specified channel +- `.sclr show` will now show hex code of the current color +- Queueing a song will now restart the playback if the queue is on the last track and stopped (there were no more tracks to play) + +## Fixed + +- .setstream and .setactivity will now pause .ropl (rotating statuses) + +## Removed + + ## [5.2.4] - 27.11.2024 ## Fixed diff --git a/docs/config-guide.md b/docs/config-guide.md index 935dad6bb..44b7b5976 100644 --- a/docs/config-guide.md +++ b/docs/config-guide.md @@ -1,5 +1,5 @@ # Config -`.config` is the new `.bce`, it gives you a fast and easy way to edit most bot settings/values. Use `.h .config` for explanation. +`.config` gives you a fast and easy way to edit most bot settings/values. Use `.h .config` for explanation. Use `.config` to see the list of editable config files Use `.config ` to see the list of settable properties on that config diff --git a/docs/donate.md b/docs/donate.md index dc8d7ce08..a071b0be6 100644 --- a/docs/donate.md +++ b/docs/donate.md @@ -9,7 +9,7 @@ Donating to us also gives you the following benefits: - A hoisted **Donators role** in our [Discord server][discord-server] - Access to exclusive **#noticed** text and voice channels -- **1000 flowers** on the public bot per dollar donated (after fees) +- **3000 flowers** on the public bot per dollar donated (after fees) - **Expressions** on the public bot for [Patreon pledges][patreon] of $5 or higher ## Patreon diff --git a/docs/guides/windows-guide.md b/docs/guides/windows-guide.md index f2e273a40..1637aeb5a 100644 --- a/docs/guides/windows-guide.md +++ b/docs/guides/windows-guide.md @@ -1,11 +1,11 @@ ## Setting Up NadekoBot on Windows With the Updater | Table of Contents| -| :---------------------------------------------------------------------------------------------------------------------------| -| [Prerequisites](#prerequisites) | -| [Setup](#setup) | -| [Starting the Bot](#starting-the-bot) | -| [Updating Nadeko](#updating-nadeko) | +| :-| +| [Prerequisites](#prerequisites) | +| [Setup](#setup) | +| [Starting the Bot](#starting-the-bot) | +| [Updating Nadeko](#updating-nadeko) | | [Manually Installing the Prerequisites from the Updater](#music-prerequisites) | *Note: If you want to make changes to Nadeko's source code, please follow the [From Source](#windows-from-source) guide instead.* @@ -13,7 +13,6 @@ #### Prerequisites - Windows 10 or later (64-bit) -- [Create a Discord Bot application and invite the bot to your server](../creds-guide.md) **Optional** @@ -29,8 +28,7 @@ ![Create a new bot](https://i.imgur.com/JxtRk9e.png "Create a new bot") - Click on **`DOWNLOAD`** at the lower right ![Bot Setup](https://i.imgur.com/HqAl36p.png "Bot Setup") -- **Note: Redis is optional. install Redis manually here: [Redis] Download and run the **`.msi`** file.** -- If you will use the music module, click on **`Install`** next to **`FFMPEG`** and **`Youtube-DLP`**. +- If you want to use the music module, click on **`Install`** next to **`FFMPEG`** and **`Youtube-DLP`**. - If any dependencies fail to install, you can temporarily disable your Windows Defender/AV until you install them. If you don't want to, then read [the last section of this guide](#Manual-Prerequisite-Installation). - When installation is finished, click on **`CREDS`** to the left of **`RUN`** at the lower right. - Follow the guide on how to [Set up the creds.yml](../../creds-guide) file. @@ -52,71 +50,6 @@ - Launch the bot - You've updated and are running again, easy as that! -#### Manual Prerequisite Installation - -You can still install them manually: - -- [Redis] (OPTIONAL) - Download and run the **`.msi`** file -- [ffmpeg-32bit] | [ffmpeg-64bit] - Download the **appropriate version** for your system (32 bit if you're running a 32 bit OS, or 64 if you're running a 64bit OS). Unzip it, and move `ffmpeg.exe` to a path that's in your PATH environment variable. If you don't know what that is, then just move the `ffmpeg.exe` file to NadekoBot/system -- [youtube-dlp] - Click to download the `yt-dlp.exe` file then put `yt-dlp.exe` in a path that's in your PATH environment variable. If you don't know what that is, then just move the `yt-dlp.exe` file to NadekoBot/system - -## **⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠** - -### Windows From Source - -##### Prerequisites - -**Install these before proceeding or your bot will not work!** -- [.net 8](https://dotnet.microsoft.com/en-us/download) - needed to compile and run the bot -- [git](https://git-scm.com/downloads) - needed to clone the repository (you can also download the zip manually and extract it, but this guide assumes you're using git) -- [Redis] (OPTIONAL)- to cache things needed by some features and persist through restarts - -##### Installation Instructions - -Open PowerShell (press windows button on your keyboard and type powershell, it should show up; alternatively, right click the start menu and select Windows PowerShell), and navigate to the location where you want to install the bot (for example `cd ~/Desktop/`) - -1. `git clone https://gitlab.com/kwoth/nadekobot -b v5 --depth 1` -2. `cd nadekobot` -3. `dotnet publish -c Release -o output/ src/NadekoBot/` -4. `cd output` -5. `cp creds_example.yml creds.yml` -6. Open `creds.yml` with your favorite text editor (Please don't use Notepad or WordPad. You can use Notepad++, VSCode, Atom, Sublime, or something similar) -7. [Enter your bot's token](#creds-guide) -8. Run the bot `dotnet NadekoBot.dll` -9. 🎉 - -##### Update Instructions - -Open PowerShell as described above and run the following commands: - -1. Stop the bot - - ⚠️ Make sure you don't have your database, credentials or any other nadekobot folder open in some application, this might prevent some of the steps from executing succesfully -2. Navigate to your bot's folder, example: - - `cd ~/Desktop/nadekobot` -3. Pull the new version, and make sure you're on the v5 branch - - *⚠️ the first 3 lines can be omitted if you're already on v5. If you're updating from v4, you must run them* - - `git remote set-branches origin '*'` - - `git fetch -v --depth=1` - - `git checkout v5` - - `git pull` - - ⚠️ If this fails, you may want to stash or remove your code changes if you don't know how to resolve merge conflicts -4. **Backup** old output in case your data is overwritten - - `cp -r -fo output/ output-old` -5. Build the bot again - - `dotnet publish -c Release -o output/ src/NadekoBot/` -6. Remove old strings and aliases to avoid overwriting the updated versions of those files - - ⚠ If you've modified said files, back them up instead - - `rm output-old/data/aliases.yml` - - `rm -r output-old/data/strings` -7. Copy old data - - `cp -Recurse .\output-old\data\ .\output\ -Force` -8. Copy creds.yml - - `cp output-old/creds.yml output/` -9. Run the bot - - `cd output` - - `dotnet NadekoBot.dll` - -🎉 Enjoy #### Music prerequisites In order to use music commands, you need ffmpeg and yt-dlp installed. diff --git a/docs/guides/windows-source-guide.md b/docs/guides/windows-source-guide.md new file mode 100644 index 000000000..afb684841 --- /dev/null +++ b/docs/guides/windows-source-guide.md @@ -0,0 +1,77 @@ +## Setting Up NadekoBot on Windows from source + +1. Prerequisites + +- Windows 10 or later (64-bit) +- [.net 8 sdk](https://dotnet.microsoft.com/download/dotnet/8.0) +- If you want nadeko to play music: [Visual C++ 2010 (x86)] and [Visual C++ 2017 (x64)] (both are required, you may install them later) +- [git](https://git-scm.com/downloads) - needed to clone the repository (you can also download the zip manually and extract it, but this guide assumes you're using git) +- **Optional** Any code editor, for example [Visual Studio Code](https://code.visualstudio.com/Download) + - You'll need to at least modify creds.yml, notepad is inadequate + + +##### Installation Instructions + +Open PowerShell (press windows button on your keyboard and type powershell, it should show up; alternatively, right click the start menu and select Windows PowerShell), and + + +0. Navigate to the location where you want to install the bot + - for example, type `cd ~/Desktop/` and press enter +1. `git clone https://gitlab.com/kwoth/nadekobot -b v5 --depth 1` +2. `cd nadekobot` +3. `dotnet publish -c Release -o output/ src/NadekoBot/` +4. `cd output` +5. `cp creds_example.yml creds.yml` +6. "You're done installing, you may now proceed to set up your bot's credentials by following the [#creds-guide] + - Once done, come back here and run the last command +8. Run the bot `dotnet NadekoBot.dll` +9. 🎉 Enjoy + +##### Update Instructions + +Open PowerShell as described above and run the following commands: + +1. Stop the bot + - ⚠️ Make sure you don't have your database, credentials or any other nadekobot folder open in some application, this might prevent some of the steps from executing succesfully +2. Navigate to your bot's folder, example: + - `cd ~/Desktop/nadekobot` +3. Pull the new version, and make sure you're on the v5 branch + - *⚠️ If you're on v4, you must run these commands, if not, you may skip them.* + - `git remote set-branches origin '*'` + - `git fetch -v --depth=1` + - `git checkout v5` + - `git pull` + - ⚠️ If this fails, you may want to stash or remove your code changes if you don't know how to resolve merge conflicts +4. **Backup** old output in case your data is overwritten + - `cp -r -fo output/ output-old` +5. Build the bot again + - `dotnet publish -c Release -o output/ src/NadekoBot/` +6. Remove old strings and aliases to avoid overwriting the updated versions of those files + - ⚠ If you've modified said files, back them up instead + - `rm output-old/data/aliases.yml` + - `rm -r output-old/data/strings` +7. Copy old data, and new strings + - `cp -Recurse -Force .\output-old\data\ .\output\` + - `cp -Recurse -Force src/NadekoBot/data/strings/ output/data/` +8. Copy creds.yml + - `cp output-old/creds.yml output/` +9. Run the bot + - `cd output` + - `dotnet NadekoBot.dll` + +🎉 Enjoy + +#### Music prerequisites +In order to use music commands, you need ffmpeg and yt-dlp installed. +- [ffmpeg-32bit] | [ffmpeg-64bit] - Download the **appropriate version** for your system (32 bit if you're running a 32 bit OS, or 64 if you're running a 64bit OS). Unzip it, and move `ffmpeg.exe` to a path that's in your PATH environment variable. If you don't know what that is, just move the `ffmpeg.exe` file to `NadekoBot/output`. +- [youtube-dlp] - Click to download the `yt-dlp.exe` file, then move `yt-dlp.exe` to a path that's in your PATH environment variable. If you don't know what that is, just move the `yt-dlp.exe` file to `NadekoBot/system`. + +[Updater]: https://dl.nadeko.bot/v3/ +[Notepad++]: https://notepad-plus-plus.org/ +[.net]: https://dotnet.microsoft.com/download/dotnet/8.0 +[Redis]: https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.msi +[Visual C++ 2010 (x86)]: https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe +[Visual C++ 2017 (x64)]: https://aka.ms/vs/15/release/vc_redist.x64.exe +[ffmpeg-32bit]: https://cdn.nadeko.bot/dl/ffmpeg-32.zip +[ffmpeg-64bit]: https://cdn.nadeko.bot/dl/ffmpeg-64.zip +[youtube-dlp]: https://github.com/yt-dlp/yt-dlp/releases diff --git a/docs/index.md b/docs/index.md index 950985cab..a409781ea 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,6 +16,7 @@ To self-host your own Nadeko, use the guides below: - [:material-microsoft-windows: Windows guide][windows-guide] - [:material-linux: Linux guide][linux-guide] - [:material-apple: Mac OS guide][macos-guide] +- [:material-microsoft-windows: Windows (from source) guide][windows-source-guide] In case you need any help, join our [Discord server][discord-server] where we may provide support. @@ -32,6 +33,7 @@ If you're unsure whether something is an issue, ask in our support server first. [invite]: https://invite.nadeko.bot/ [commands]: https://nadeko.bot/commands/ [windows-guide]: ./guides/windows-guide.md +[windows-source-guide]: ./guides/windows-source-guide.md [linux-guide]: ./guides/linux-guide.md [macos-guide]: ./guides/osx-guide.md [from-source-guide]: ./guides/from-source.md diff --git a/src/Nadeko.Medusa/Nadeko.Medusa.csproj b/src/Nadeko.Medusa/Nadeko.Medusa.csproj index a38edcb00..0dc6ca6ee 100644 --- a/src/Nadeko.Medusa/Nadeko.Medusa.csproj +++ b/src/Nadeko.Medusa/Nadeko.Medusa.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/NadekoBot/Db/Models/Notify.cs b/src/NadekoBot/Db/Models/Notify.cs index ec8d629ff..6cf1136f5 100644 --- a/src/NadekoBot/Db/Models/Notify.cs +++ b/src/NadekoBot/Db/Models/Notify.cs @@ -19,4 +19,6 @@ public enum NotifyType { LevelUp = 0, Protection = 1, Prot = 1, + AddRoleReward = 2, + RemoveRoleReward = 3, } \ No newline at end of file diff --git a/src/NadekoBot/Db/NadekoContext.cs b/src/NadekoBot/Db/NadekoContext.cs index b2af16e65..8cedb1f30 100644 --- a/src/NadekoBot/Db/NadekoContext.cs +++ b/src/NadekoBot/Db/NadekoContext.cs @@ -164,13 +164,18 @@ public abstract class NadekoContext : DbContext #region UserBetStats - modelBuilder.Entity() - .HasIndex(x => new - { - x.UserId, - x.Game - }) - .IsUnique(); + modelBuilder.Entity(ubs => + { + ubs.HasIndex(x => new + { + x.UserId, + x.Game + }) + .IsUnique(); + + ubs.HasIndex(x => x.MaxWin) + .IsUnique(false); + }); #endregion diff --git a/src/NadekoBot/Migrations/MigrationQueries.cs b/src/NadekoBot/Migrations/MigrationQueries.cs index 24c26f54d..13f8b8b24 100644 --- a/src/NadekoBot/Migrations/MigrationQueries.cs +++ b/src/NadekoBot/Migrations/MigrationQueries.cs @@ -5,6 +5,16 @@ namespace NadekoBot.Migrations; public static class MigrationQueries { + public static void MergeAwardedXp(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(""" + UPDATE UserXpStats + SET Xp = AwardedXp + Xp, + AwardedXp = 0 + WHERE AwardedXp > 0; + """); + } + public static void MigrateSar(MigrationBuilder migrationBuilder) { migrationBuilder.Sql(""" diff --git a/src/NadekoBot/Migrations/PostgreSql/20241205052146_awardedxp-temprole-notify.Designer.cs b/src/NadekoBot/Migrations/PostgreSql/20241207150050_awardedxp-temprole-notify.Designer.cs similarity index 99% rename from src/NadekoBot/Migrations/PostgreSql/20241205052146_awardedxp-temprole-notify.Designer.cs rename to src/NadekoBot/Migrations/PostgreSql/20241207150050_awardedxp-temprole-notify.Designer.cs index 9eb8e5fc5..f19e0d7dc 100644 --- a/src/NadekoBot/Migrations/PostgreSql/20241205052146_awardedxp-temprole-notify.Designer.cs +++ b/src/NadekoBot/Migrations/PostgreSql/20241207150050_awardedxp-temprole-notify.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace NadekoBot.Migrations.PostgreSql { [DbContext(typeof(PostgreSqlContext))] - [Migration("20241205052146_awardedxp-temprole-notify")] + [Migration("20241207150050_awardedxp-temprole-notify")] partial class awardedxptemprolenotify { /// @@ -3485,6 +3485,9 @@ namespace NadekoBot.Migrations.PostgreSql b.HasKey("Id") .HasName("pk_userbetstats"); + b.HasIndex("MaxWin") + .HasDatabaseName("ix_userbetstats_maxwin"); + b.HasIndex("UserId", "Game") .IsUnique() .HasDatabaseName("ix_userbetstats_userid_game"); diff --git a/src/NadekoBot/Migrations/PostgreSql/20241205052146_awardedxp-temprole-notify.cs b/src/NadekoBot/Migrations/PostgreSql/20241207150050_awardedxp-temprole-notify.cs similarity index 91% rename from src/NadekoBot/Migrations/PostgreSql/20241205052146_awardedxp-temprole-notify.cs rename to src/NadekoBot/Migrations/PostgreSql/20241207150050_awardedxp-temprole-notify.cs index 4cfd5e8a9..f3ce9e73f 100644 --- a/src/NadekoBot/Migrations/PostgreSql/20241205052146_awardedxp-temprole-notify.cs +++ b/src/NadekoBot/Migrations/PostgreSql/20241207150050_awardedxp-temprole-notify.cs @@ -16,6 +16,8 @@ namespace NadekoBot.Migrations.PostgreSql name: "ix_userxpstats_awardedxp", table: "userxpstats"); + MigrationQueries.MergeAwardedXp(migrationBuilder); + migrationBuilder.DropColumn( name: "awardedxp", table: "userxpstats"); @@ -59,6 +61,11 @@ namespace NadekoBot.Migrations.PostgreSql table.UniqueConstraint("ak_temprole_guildid_userid_roleid", x => new { x.guildid, x.userid, x.roleid }); }); + migrationBuilder.CreateIndex( + name: "ix_userbetstats_maxwin", + table: "userbetstats", + column: "maxwin"); + migrationBuilder.CreateIndex( name: "ix_temprole_expiresat", table: "temprole", @@ -74,6 +81,10 @@ namespace NadekoBot.Migrations.PostgreSql migrationBuilder.DropTable( name: "temprole"); + migrationBuilder.DropIndex( + name: "ix_userbetstats_maxwin", + table: "userbetstats"); + migrationBuilder.AddColumn( name: "awardedxp", table: "userxpstats", diff --git a/src/NadekoBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs b/src/NadekoBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs index 637fcd8bb..6fd03a25d 100644 --- a/src/NadekoBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs @@ -3482,6 +3482,9 @@ namespace NadekoBot.Migrations.PostgreSql b.HasKey("Id") .HasName("pk_userbetstats"); + b.HasIndex("MaxWin") + .HasDatabaseName("ix_userbetstats_maxwin"); + b.HasIndex("UserId", "Game") .IsUnique() .HasDatabaseName("ix_userbetstats_userid_game"); diff --git a/src/NadekoBot/Migrations/Sqlite/20241205052137_awardedxp-temprole-notify.Designer.cs b/src/NadekoBot/Migrations/Sqlite/20241207150041_awardedxp-temprole-notify.Designer.cs similarity index 99% rename from src/NadekoBot/Migrations/Sqlite/20241205052137_awardedxp-temprole-notify.Designer.cs rename to src/NadekoBot/Migrations/Sqlite/20241207150041_awardedxp-temprole-notify.Designer.cs index 5872afd4e..f217cccb0 100644 --- a/src/NadekoBot/Migrations/Sqlite/20241205052137_awardedxp-temprole-notify.Designer.cs +++ b/src/NadekoBot/Migrations/Sqlite/20241207150041_awardedxp-temprole-notify.Designer.cs @@ -11,7 +11,7 @@ using NadekoBot.Db; namespace NadekoBot.Migrations { [DbContext(typeof(SqliteContext))] - [Migration("20241205052137_awardedxp-temprole-notify")] + [Migration("20241207150041_awardedxp-temprole-notify")] partial class awardedxptemprolenotify { /// @@ -2593,6 +2593,8 @@ namespace NadekoBot.Migrations b.HasKey("Id"); + b.HasIndex("MaxWin"); + b.HasIndex("UserId", "Game") .IsUnique(); diff --git a/src/NadekoBot/Migrations/Sqlite/20241205052137_awardedxp-temprole-notify.cs b/src/NadekoBot/Migrations/Sqlite/20241207150041_awardedxp-temprole-notify.cs similarity index 91% rename from src/NadekoBot/Migrations/Sqlite/20241205052137_awardedxp-temprole-notify.cs rename to src/NadekoBot/Migrations/Sqlite/20241207150041_awardedxp-temprole-notify.cs index 9828d13be..aeb5adfd3 100644 --- a/src/NadekoBot/Migrations/Sqlite/20241205052137_awardedxp-temprole-notify.cs +++ b/src/NadekoBot/Migrations/Sqlite/20241207150041_awardedxp-temprole-notify.cs @@ -15,6 +15,7 @@ namespace NadekoBot.Migrations name: "IX_UserXpStats_AwardedXp", table: "UserXpStats"); + MigrationQueries.MergeAwardedXp(migrationBuilder); migrationBuilder.DropColumn( name: "AwardedXp", table: "UserXpStats"); @@ -58,6 +59,11 @@ namespace NadekoBot.Migrations table.UniqueConstraint("AK_TempRole_GuildId_UserId_RoleId", x => new { x.GuildId, x.UserId, x.RoleId }); }); + migrationBuilder.CreateIndex( + name: "IX_UserBetStats_MaxWin", + table: "UserBetStats", + column: "MaxWin"); + migrationBuilder.CreateIndex( name: "IX_TempRole_ExpiresAt", table: "TempRole", @@ -73,6 +79,10 @@ namespace NadekoBot.Migrations migrationBuilder.DropTable( name: "TempRole"); + migrationBuilder.DropIndex( + name: "IX_UserBetStats_MaxWin", + table: "UserBetStats"); + migrationBuilder.AddColumn( name: "AwardedXp", table: "UserXpStats", diff --git a/src/NadekoBot/Migrations/Sqlite/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/Sqlite/NadekoSqliteContextModelSnapshot.cs index 8e9d53939..c85d644c6 100644 --- a/src/NadekoBot/Migrations/Sqlite/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/Sqlite/NadekoSqliteContextModelSnapshot.cs @@ -2590,6 +2590,8 @@ namespace NadekoBot.Migrations b.HasKey("Id"); + b.HasIndex("MaxWin"); + b.HasIndex("UserId", "Game") .IsUnique(); diff --git a/src/NadekoBot/Modules/Administration/Notify/Models/AddRoleRewardNotifyModel.cs b/src/NadekoBot/Modules/Administration/Notify/Models/AddRoleRewardNotifyModel.cs new file mode 100644 index 000000000..a83a00804 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Notify/Models/AddRoleRewardNotifyModel.cs @@ -0,0 +1,36 @@ +using NadekoBot.Db.Models; +using NadekoBot.Modules.Administration; + +namespace NadekoBot.Modules.Xp.Services; + +public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel +{ + public static string KeyName + => "notify.reward.addrole"; + + public static NotifyType NotifyType + => NotifyType.AddRoleReward; + + public IReadOnlyDictionary> GetReplacements() + { + var model = this; + return new Dictionary>() + { + { "%event.user%", g => g.GetUser(model.UserId)?.ToString() ?? model.UserId.ToString() }, + { "%event.role%", g => g.GetRole(model.RoleId)?.ToString() ?? model.RoleId.ToString() }, + { "%event.level%", g => model.Level.ToString() } + }; + } + + public bool TryGetUserId(out ulong userId) + { + userId = UserId; + return true; + } + + public bool TryGetGuildId(out ulong guildId) + { + guildId = GuildId; + return true; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Notify/Models/LevelUpNotifyModel.cs b/src/NadekoBot/Modules/Administration/Notify/Models/LevelUpNotifyModel.cs index 7affec529..15d50b019 100644 --- a/src/NadekoBot/Modules/Administration/Notify/Models/LevelUpNotifyModel.cs +++ b/src/NadekoBot/Modules/Administration/Notify/Models/LevelUpNotifyModel.cs @@ -20,6 +20,7 @@ public record struct LevelUpNotifyModel( return new Dictionary>() { { "%event.level%", g => data.Level.ToString() }, + { "%event.user%", g => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString() }, }; } @@ -34,11 +35,4 @@ public record struct LevelUpNotifyModel( userId = UserId; return true; } -} - -public static class INotifyModelExtensions -{ - public static TypedKey GetTypedKey(this T model) - where T : struct, INotifyModel - => new(T.KeyName); } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Notify/Models/ProtectionNotifyModel.cs b/src/NadekoBot/Modules/Administration/Notify/Models/ProtectionNotifyModel.cs new file mode 100644 index 000000000..7a8dd3fdf --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Notify/Models/ProtectionNotifyModel.cs @@ -0,0 +1,34 @@ +#nullable disable +using NadekoBot.Db.Models; + +namespace NadekoBot.Modules.Administration.Services; + +public record struct ProtectionNotifyModel(ulong GuildId, ProtectionType ProtType, ulong UserId) : INotifyModel +{ + public static string KeyName + => "notify.protection"; + + public static NotifyType NotifyType + => NotifyType.Protection; + + public IReadOnlyDictionary> GetReplacements() + { + var data = this; + return new Dictionary>() + { + { "%event.type%", g => data.ProtType.ToString() }, + }; + } + + public bool TryGetUserId(out ulong userId) + { + userId = UserId; + return true; + } + + public bool TryGetGuildId(out ulong guildId) + { + guildId = GuildId; + return true; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Notify/Models/RemoveRoleRewardNotifyModel.cs b/src/NadekoBot/Modules/Administration/Notify/Models/RemoveRoleRewardNotifyModel.cs new file mode 100644 index 000000000..91bb60fef --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Notify/Models/RemoveRoleRewardNotifyModel.cs @@ -0,0 +1,36 @@ +using NadekoBot.Db.Models; +using NadekoBot.Modules.Administration; + +namespace NadekoBot.Modules.Xp.Services; + +public record struct RemoveRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel +{ + public static string KeyName + => "notify.reward.removerole"; + + public static NotifyType NotifyType + => NotifyType.RemoveRoleReward; + + public IReadOnlyDictionary> GetReplacements() + { + var model = this; + return new Dictionary>() + { + { "%event.user%", g => g.GetUser(model.UserId)?.ToString() ?? model.UserId.ToString() }, + { "%event.role%", g => g.GetRole(model.RoleId)?.ToString() ?? model.RoleId.ToString() }, + { "%event.level%", g => model.Level.ToString() }, + }; + } + + public bool TryGetUserId(out ulong userId) + { + userId = UserId; + return true; + } + + public bool TryGetGuildId(out ulong guildId) + { + guildId = GuildId; + return true; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Notify/NotifyCommands.cs b/src/NadekoBot/Modules/Administration/Notify/NotifyCommands.cs index 4d9ae3e74..06acd24ff 100644 --- a/src/NadekoBot/Modules/Administration/Notify/NotifyCommands.cs +++ b/src/NadekoBot/Modules/Administration/Notify/NotifyCommands.cs @@ -1,4 +1,5 @@ using NadekoBot.Db.Models; +using System.Text; namespace NadekoBot.Modules.Administration; @@ -6,19 +7,108 @@ public partial class Administration { public class NotifyCommands : NadekoModule { + [Cmd] + [OwnerOnly] + public async Task Notify() + { + await Response() + .Paginated() + .Items(Enum.GetValues()) + .PageSize(5) + .Page((items, page) => + { + var eb = CreateEmbed() + .WithOkColor() + .WithTitle(GetText(strs.notify_available)); + + foreach (var item in items) + { + eb.AddField(item.ToString(), GetText(GetDescription(item)), false); + } + + return eb; + }) + .SendAsync(); + } + + private LocStr GetDescription(NotifyType item) + => item switch + { + NotifyType.LevelUp => strs.notify_desc_levelup, + NotifyType.Protection => strs.notify_desc_protection, + NotifyType.AddRoleReward => strs.notify_desc_addrolerew, + NotifyType.RemoveRoleReward => strs.notify_desc_removerolerew, + _ => strs.notify_desc_not_found + }; + [Cmd] [OwnerOnly] public async Task Notify(NotifyType nType, [Leftover] string? message = null) { if (string.IsNullOrWhiteSpace(message)) { - await _service.DisableAsync(ctx.Guild.Id, nType); - await Response().Confirm(strs.notify_off(nType)).SendAsync(); + // show msg + var conf = await _service.GetNotifyAsync(ctx.Guild.Id, nType); + if (conf is null) + { + await Response().Confirm(strs.notify_msg_not_set).SendAsync(); + return; + } + + var eb = CreateEmbed() + .WithOkColor() + .WithTitle(GetText(strs.notify_msg)) + .WithDescription(conf.Message.TrimTo(2048)) + .AddField(GetText(strs.notify_type), conf.Type.ToString(), true) + .AddField(GetText(strs.channel), + $""" + <#{conf.ChannelId}> + `{conf.ChannelId}` + """, + true); + + await Response().Embed(eb).SendAsync(); return; } await _service.EnableAsync(ctx.Guild.Id, ctx.Channel.Id, nType, message); - await Response().Confirm(strs.notify_on(nType.ToString())).SendAsync(); + await Response().Confirm(strs.notify_on($"<#{ctx.Channel.Id}>", Format.Bold(nType.ToString()))).SendAsync(); + } + + [Cmd] + [OwnerOnly] + public async Task NotifyList(int page = 1) + { + if (--page < 0) + return; + + var notifs = await _service.GetForGuildAsync(ctx.Guild.Id); + + var sb = new StringBuilder(); + + foreach (var notif in notifs) + { + sb.AppendLine($""" + - **{notif.Type}** + <#{notif.ChannelId}> `{notif.ChannelId}` + + """); + } + + if (notifs.Count == 0) + sb.AppendLine(GetText(strs.notify_none)); + + await Response() + .Confirm(GetText(strs.notify_list), text: sb.ToString()) + .SendAsync(); + } + + [Cmd] + [OwnerOnly] + public async Task NotifyClear(NotifyType nType) + { + await _service.DisableAsync(ctx.Guild.Id, nType); + await Response().Confirm(strs.notify_off(nType)).SendAsync(); } } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Notify/NotifyModelExtensions.cs b/src/NadekoBot/Modules/Administration/Notify/NotifyModelExtensions.cs new file mode 100644 index 000000000..ff129f935 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Notify/NotifyModelExtensions.cs @@ -0,0 +1,8 @@ +namespace NadekoBot.Modules.Administration; + +public static class NotifyModelExtensions +{ + public static TypedKey GetTypedKey(this T model) + where T : struct, INotifyModel + => new(T.KeyName); +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Notify/NotifyService.cs b/src/NadekoBot/Modules/Administration/Notify/NotifyService.cs index 06e031466..1ded32058 100644 --- a/src/NadekoBot/Modules/Administration/Notify/NotifyService.cs +++ b/src/NadekoBot/Modules/Administration/Notify/NotifyService.cs @@ -2,6 +2,7 @@ using LinqToDB.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db.Models; +using NadekoBot.Generators; namespace NadekoBot.Modules.Administration; @@ -199,4 +200,27 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, INService guildsDict.TryRemove(guildId, out _); } + + public async Task> GetForGuildAsync(ulong guildId, int page = 0) + { + ArgumentOutOfRangeException.ThrowIfNegative(page); + + await using var ctx = _db.GetDbContext(); + var list = await ctx.GetTable() + .Where(x => x.GuildId == guildId) + .OrderBy(x => x.Type) + .Skip(page * 10) + .Take(10) + .ToListAsyncLinqToDB(); + + return list; + } + + public async Task GetNotifyAsync(ulong guildId, NotifyType nType) + { + await using var ctx = _db.GetDbContext(); + return await ctx.GetTable() + .Where(x => x.GuildId == guildId && x.Type == nType) + .FirstOrDefaultAsyncLinqToDB(); + } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Protection/ProtectionService.cs b/src/NadekoBot/Modules/Administration/Protection/ProtectionService.cs index 9f5141c55..efc7e3358 100644 --- a/src/NadekoBot/Modules/Administration/Protection/ProtectionService.cs +++ b/src/NadekoBot/Modules/Administration/Protection/ProtectionService.cs @@ -5,36 +5,6 @@ using System.Threading.Channels; namespace NadekoBot.Modules.Administration.Services; -public record struct ProtectionNotifyModel(ulong GuildId, ProtectionType ProtType, ulong UserId) : INotifyModel -{ - public static string KeyName - => "notify.protection"; - - public static NotifyType NotifyType - => NotifyType.Protection; - - public IReadOnlyDictionary> GetReplacements() - { - var data = this; - return new Dictionary>() - { - { "%event.type%", g => data.ProtType.ToString() }, - }; - } - - public bool TryGetUserId(out ulong userId) - { - userId = UserId; - return true; - } - - public bool TryGetGuildId(out ulong guildId) - { - guildId = GuildId; - return true; - } -} - public class ProtectionService : INService { public event Func OnAntiProtectionTriggered = delegate diff --git a/src/NadekoBot/Modules/Gambling/BetStatsCommands.cs b/src/NadekoBot/Modules/Gambling/BetStatsCommands.cs index acc0f3320..36494e621 100644 --- a/src/NadekoBot/Modules/Gambling/BetStatsCommands.cs +++ b/src/NadekoBot/Modules/Gambling/BetStatsCommands.cs @@ -1,6 +1,7 @@ #nullable disable using NadekoBot.Modules.Gambling.Common; using NadekoBot.Modules.Gambling.Services; +using NadekoBot.Modules.Xp.Services; namespace NadekoBot.Modules.Gambling; @@ -10,13 +11,19 @@ public partial class Gambling public sealed class BetStatsCommands : GamblingModule { private readonly GamblingTxTracker _gamblingTxTracker; + private readonly IBotCache _cache; + private readonly IUserService _userService; public BetStatsCommands( GamblingTxTracker gamblingTxTracker, - GamblingConfigService gcs) + GamblingConfigService gcs, + IBotCache cache, + IUserService userService) : base(gcs) { _gamblingTxTracker = gamblingTxTracker; + _cache = cache; + _userService = userService; } [Cmd] @@ -25,12 +32,12 @@ public partial class Gambling var price = await _service.GetResetStatsPriceAsync(ctx.User.Id, game); var result = await PromptUserConfirmAsync(CreateEmbed() - .WithDescription( - $""" - Are you sure you want to reset your bet stats for **{GetGameName(game)}**? + .WithDescription( + $""" + Are you sure you want to reset your bet stats for **{GetGameName(game)}**? - It will cost you {N(price)} - """)); + It will cost you {N(price)} + """)); if (!result) return; @@ -88,15 +95,15 @@ public partial class Gambling }; var eb = CreateEmbed() - .WithOkColor() - .WithAuthor(user) - .AddField("Total Won", N(stats.Sum(x => x.PaidOut)), true) - .AddField("Biggest Win", N(stats.Max(x => x.MaxWin)), true) - .AddField("Biggest Bet", N(stats.Max(x => x.MaxBet)), true) - .AddField("# Bets", stats.Sum(x => x.WinCount + x.LoseCount), true) - .AddField("Payout", - (stats.Sum(x => x.PaidOut) / stats.Sum(x => x.TotalBet)).ToString("P2", Culture), - true); + .WithOkColor() + .WithAuthor(user) + .AddField("Total Won", N(stats.Sum(x => x.PaidOut)), true) + .AddField("Biggest Win", N(stats.Max(x => x.MaxWin)), true) + .AddField("Biggest Bet", N(stats.Max(x => x.MaxBet)), true) + .AddField("# Bets", stats.Sum(x => x.WinCount + x.LoseCount), true) + .AddField("Payout", + (stats.Sum(x => x.PaidOut) / stats.Sum(x => x.TotalBet)).ToString("P2", Culture), + true); if (game == null) { var favGame = stats.MaxBy(x => x.WinCount + x.LoseCount); @@ -115,13 +122,75 @@ public partial class Gambling .SendAsync(); } + private readonly record struct WinLbStat( + int Rank, + string User, + GamblingGame Game, + long MaxWin); + + private TypedKey> GetWinLbKey(int page) + => new($"winlb:{page}"); + + private async Task> GetCachedWinLbAsync(int page) + { + return await _cache.GetOrAddAsync(GetWinLbKey(page), + async () => + { + var items = await _service.GetWinLbAsync(page); + + if (items.Count == 0) + return []; + + var outputItems = new List(items.Count); + for (var i = 0; i < items.Count; i++) + { + var x = items[i]; + var user = (await ctx.Client.GetUserAsync(x.UserId, CacheMode.CacheOnly))?.ToString() + ?? (await _userService.GetUserAsync(x.UserId))?.Username + ?? x.UserId.ToString(); + + outputItems.Add(new WinLbStat(i + 1 + (page * 10), user, x.Game, x.MaxWin)); + } + + return outputItems; + }, + expiry: TimeSpan.FromMinutes(5)); + } + + [Cmd] + public async Task WinLb(int page = 1) + { + if (--page < 0) + return; + + await Response() + .Paginated() + .PageItems(p => GetCachedWinLbAsync(p)) + .PageSize(10) + .Page((items, curPage) => + { + var eb = CreateEmbed() + .WithOkColor(); + + for (var i = 0; i < items.Count; i++) + { + var item = items[i]; + eb.AddField($"#{item.Rank} {item.User}", + $"[{item.Game}]{N(item.MaxWin)}"); + } + + return eb; + }) + .SendAsync(); + } + [Cmd] public async Task GambleStats() { var stats = await _gamblingTxTracker.GetAllAsync(); var eb = CreateEmbed() - .WithOkColor(); + .WithOkColor(); var str = "` Feature `|`   Bet  `|`Paid Out`|`  RoI  `\n"; str += "――――――――――――――――――――\n"; @@ -157,13 +226,13 @@ public partial class Gambling public async Task GambleStatsReset() { if (!await PromptUserConfirmAsync(CreateEmbed() - .WithDescription( - """ - Are you sure? - This will completely reset Gambling Stats. + .WithDescription( + """ + Are you sure? + This will completely reset Gambling Stats. - This action is irreversible. - """))) + This action is irreversible. + """))) return; await GambleStats(); diff --git a/src/NadekoBot/Modules/Gambling/UserBetStatsService.cs b/src/NadekoBot/Modules/Gambling/UserBetStatsService.cs index 50b01379c..b55e65249 100644 --- a/src/NadekoBot/Modules/Gambling/UserBetStatsService.cs +++ b/src/NadekoBot/Modules/Gambling/UserBetStatsService.cs @@ -42,7 +42,7 @@ public sealed class UserBetStatsService : INService await using var ctx = _db.GetDbContext(); await ctx.GetTable() .DeleteAsync(x => x.UserId == userId && (game == null || x.Game == game)); - + return true; } @@ -52,4 +52,16 @@ public sealed class UserBetStatsService : INService await ctx.GetTable() .DeleteAsync(); } + + public async Task> GetWinLbAsync(int page) + { + ArgumentOutOfRangeException.ThrowIfNegative(page); + + await using var ctx = _db.GetDbContext(); + return await ctx.GetTable() + .OrderByDescending(x => x.MaxWin) + .Skip(page * 10) + .Take(10) + .ToArrayAsyncLinqToDB(); + } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index d15a2e6f4..93a1202bf 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -103,11 +103,11 @@ public partial class Searches : NadekoModule } var eb = CreateEmbed() - .WithOkColor() - .WithTitle(GetText(strs.time_new)) - .WithDescription(Format.Code(data.Time.ToString(Culture))) - .AddField(GetText(strs.location), string.Join('\n', data.Address.Split(", ")), true) - .AddField(GetText(strs.timezone), data.TimeZoneName, true); + .WithOkColor() + .WithTitle(GetText(strs.time_new)) + .WithDescription(Format.Code(data.Time.ToString(Culture))) + .AddField(GetText(strs.location), string.Join('\n', data.Address.Split(", ")), true) + .AddField(GetText(strs.timezone), data.TimeZoneName, true); await Response().Embed(eb).SendAsync(); } @@ -129,16 +129,16 @@ public partial class Searches : NadekoModule await Response() .Embed(CreateEmbed() - .WithOkColor() - .WithTitle(movie.Title) - .WithUrl($"https://www.imdb.com/title/{movie.ImdbId}/") - .WithDescription(movie.Plot.TrimTo(1000)) - .AddField("Rating", movie.ImdbRating, true) - .AddField("Genre", movie.Genre, true) - .AddField("Year", movie.Year, true) - .WithImageUrl(Uri.IsWellFormedUriString(movie.Poster, UriKind.Absolute) - ? movie.Poster - : null)) + .WithOkColor() + .WithTitle(movie.Title) + .WithUrl($"https://www.imdb.com/title/{movie.ImdbId}/") + .WithDescription(movie.Plot.TrimTo(1000)) + .AddField("Rating", movie.ImdbRating, true) + .AddField("Genre", movie.Genre, true) + .AddField("Year", movie.Year, true) + .WithImageUrl(Uri.IsWellFormedUriString(movie.Poster, UriKind.Absolute) + ? movie.Poster + : null)) .SendAsync(); } @@ -191,9 +191,9 @@ public partial class Searches : NadekoModule await Response() .Embed(CreateEmbed() - .WithOkColor() - .AddField(GetText(strs.original_url), $"<{query}>") - .AddField(GetText(strs.short_url), $"<{shortLink}>")) + .WithOkColor() + .AddField(GetText(strs.original_url), $"<{query}>") + .AddField(GetText(strs.short_url), $"<{shortLink}>")) .SendAsync(); } @@ -214,13 +214,13 @@ public partial class Searches : NadekoModule } var embed = CreateEmbed() - .WithOkColor() - .WithTitle(card.Name) - .WithDescription(card.Description) - .WithImageUrl(card.ImageUrl) - .AddField(GetText(strs.store_url), card.StoreUrl, true) - .AddField(GetText(strs.cost), card.ManaCost, true) - .AddField(GetText(strs.types), card.Types, true); + .WithOkColor() + .WithTitle(card.Name) + .WithDescription(card.Description) + .WithImageUrl(card.ImageUrl) + .AddField(GetText(strs.store_url), card.StoreUrl, true) + .AddField(GetText(strs.cost), card.ManaCost, true) + .AddField(GetText(strs.types), card.Types, true); await Response().Embed(embed).SendAsync(); } @@ -281,10 +281,10 @@ public partial class Searches : NadekoModule { var item = items[0]; return CreateEmbed() - .WithOkColor() - .WithUrl(item.Permalink) - .WithTitle(item.Word) - .WithDescription(item.Definition); + .WithOkColor() + .WithUrl(item.Permalink) + .WithTitle(item.Word) + .WithDescription(item.Definition); }) .SendAsync(); } @@ -312,11 +312,11 @@ public partial class Searches : NadekoModule { var model = items.First(); var embed = CreateEmbed() - .WithDescription(ctx.User.Mention) - .AddField(GetText(strs.word), model.Word, true) - .AddField(GetText(strs._class), model.WordType, true) - .AddField(GetText(strs.definition), model.Definition) - .WithOkColor(); + .WithDescription(ctx.User.Mention) + .AddField(GetText(strs.word), model.Word, true) + .AddField(GetText(strs._class), model.WordType, true) + .AddField(GetText(strs.definition), model.Definition) + .WithOkColor(); if (!string.IsNullOrWhiteSpace(model.Example)) embed.AddField(GetText(strs.example), model.Example); @@ -404,10 +404,28 @@ public partial class Searches : NadekoModule await Response() .Embed( CreateEmbed() - .WithOkColor() - .AddField("Username", usr.ToString()) - .AddField("Avatar Url", avatarUrl) - .WithThumbnailUrl(avatarUrl.ToString())) + .WithOkColor() + .AddField("Username", usr.ToString()) + .AddField("Avatar Url", avatarUrl) + .WithThumbnailUrl(avatarUrl.ToString())) + .SendAsync(); + } + + [Cmd] + [RequireContext(ContextType.Guild)] + public async Task Banner([Leftover] IGuildUser? usr = null) + { + usr ??= (IGuildUser)ctx.User; + + var bannerUrl = usr.GetGuildBannerUrl(); + + await Response() + .Embed( + CreateEmbed() + .WithOkColor() + .AddField("Username", usr.ToString()) + .AddField("Banner Url", bannerUrl) + .WithThumbnailUrl(bannerUrl)) .SendAsync(); } diff --git a/src/NadekoBot/Modules/Xp/XpService.cs b/src/NadekoBot/Modules/Xp/XpService.cs index 348461a04..ac4d57e00 100644 --- a/src/NadekoBot/Modules/Xp/XpService.cs +++ b/src/NadekoBot/Modules/Xp/XpService.cs @@ -344,9 +344,45 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand if (role is not null && user is not null) { if (rrew.Remove) - _ = user.RemoveRoleAsync(role); + { + try + { + await user.RemoveRoleAsync(role); + await _notifySub.NotifyAsync(new RemoveRoleRewardNotifyModel(guild.Id, + role.Id, + user.Id, + newLevel), + isShardLocal: true); + } + catch (Exception ex) + { + Log.Warning(ex, + "Unable to remove role {RoleId} from user {UserId}: {Message}", + role.Id, + user.Id, + ex.Message); + } + } else - _ = user.AddRoleAsync(role); + { + try + { + await user.AddRoleAsync(role); + await _notifySub.NotifyAsync(new AddRoleRewardNotifyModel(guild.Id, + role.Id, + user.Id, + newLevel), + isShardLocal: true); + } + catch (Exception ex) + { + Log.Warning(ex, + "Unable to add role {RoleId} to user {UserId}: {Message}", + role.Id, + user.Id, + ex.Message); + } + } } } diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index 2252bec79..f35b91ba5 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -29,7 +29,7 @@ - + diff --git a/src/NadekoBot/_common/DoAsUserMessage.cs b/src/NadekoBot/_common/DoAsUserMessage.cs index 9dc574807..a797d9d06 100644 --- a/src/NadekoBot/_common/DoAsUserMessage.cs +++ b/src/NadekoBot/_common/DoAsUserMessage.cs @@ -157,6 +157,9 @@ public sealed class DoAsUserMessage : IUserMessage public MessageCallData? CallData => _msg.CallData; + public IReadOnlyCollection ForwardedMessages + => _msg.ForwardedMessages; + public Task ModifyAsync(Action func, RequestOptions? options = null) { return _msg.ModifyAsync(func, options); diff --git a/src/NadekoBot/_common/Services/UserService.cs b/src/NadekoBot/_common/Services/UserService.cs index a293a7858..37ace810e 100644 --- a/src/NadekoBot/_common/Services/UserService.cs +++ b/src/NadekoBot/_common/Services/UserService.cs @@ -12,7 +12,7 @@ public sealed class UserService : IUserService, INService _db = db; } - public async Task GetUserAsync(ulong userId) + public async Task GetUserAsync(ulong userId) { await using var uow = _db.GetDbContext(); var user = await uow diff --git a/src/NadekoBot/data/aliases.yml b/src/NadekoBot/data/aliases.yml index c8df7b6fb..3f51385c1 100644 --- a/src/NadekoBot/data/aliases.yml +++ b/src/NadekoBot/data/aliases.yml @@ -715,6 +715,8 @@ color: avatar: - avatar - av +banner: + - banner translate: - translate - trans @@ -1549,4 +1551,15 @@ temprole: - temprole notify: - notify - - nfy \ No newline at end of file + - nfy +notifylist: + - notifylist + - notifyl +notifyclear: + - notifyclear + - notifyremove + - notifyrm + - notifclr +winlb: + - winlb + - wins \ No newline at end of file diff --git a/src/NadekoBot/data/strings/commands/commands.en-US.yml b/src/NadekoBot/data/strings/commands/commands.en-US.yml index a31e37794..06b9f3aa2 100644 --- a/src/NadekoBot/data/strings/commands/commands.en-US.yml +++ b/src/NadekoBot/data/strings/commands/commands.en-US.yml @@ -2170,6 +2170,13 @@ avatar: params: - usr: desc: "The user whose avatar is being displayed." +banner: + desc: Shows a mentioned person's banner. + ex: + - '@Someone' + params: + - usr: + desc: "The user whose banner is being displayed." translate: desc: Translates text from the given language to the destination language. ex: @@ -4857,10 +4864,38 @@ minesweeper: notify: desc: |- Sends a message to the current channel once the specified event occurs. + Provide no parameters to see all available events. ex: - 'levelup Congratulations to user %user.name% for reaching level %event.level%' params: + - { } - event: desc: "The event to notify on." - - message: - desc: "The message to send." \ No newline at end of file + - event: + desc: "The event to notify on." + message: + desc: "The message to send." +notifylist: + desc: |- + Lists all active notifications in this server. + ex: + - '' + params: + - { } +notifyclear: + desc: |- + Removes the specified notify event. + ex: + - 'levelup' + params: + - event: + desc: "The notify event to clear." +winlb: + desc: |- + Shows the biggest wins leaderboard + ex: + - '' + - '5' + params: + - page: + desc: "The optional page to display." \ No newline at end of file diff --git a/src/NadekoBot/data/strings/responses/responses.en-US.json b/src/NadekoBot/data/strings/responses/responses.en-US.json index 84c6f4f74..f1a85dec3 100644 --- a/src/NadekoBot/data/strings/responses/responses.en-US.json +++ b/src/NadekoBot/data/strings/responses/responses.en-US.json @@ -1087,7 +1087,7 @@ "giveaway_starting": "Starting giveaway...", "winner": "Winner", "giveaway_list": "List of active giveways", - "todo_list_empty": "Your todo list is empty." , + "todo_list_empty": "Your todo list is empty.", "todo_list": "Todo List", "todo_stats": "{0} items | {1} completed | {2} remaining", "todo_add_max_limit": "You'reached the maximum amount of todos you can have.", @@ -1102,7 +1102,7 @@ "search_results": "Search results", "queue_search_results": "Type the number of the search result to queue up that track.", "overloads": "Overloads", - "honeypot_on": "Honeypot enabled on this channel." , + "honeypot_on": "Honeypot enabled on this channel.", "honeypot_off": "Honeypot disabled.", "afk_set": "AFK message set. Type a message in any channel to clear.", "rero_message_not_found": "The specified message wasn't found. Make sure you've specified the message from this channel.", @@ -1115,7 +1115,7 @@ "nc_hint": "Use `{0}nczoom x y` command to zoom in. Image is {1}x{2} pixels.", "nc_insuff_payment": "Invalid payment.", "invalid_img_size": "Image must to be {0}x{1} pixels.", - "no_attach_found": "No attachment found. Please send the image along with this command." , + "no_attach_found": "No attachment found. Please send the image along with this command.", "trfl_enabled": "Flag translation enabled on this channel. Reacting to a message with a flag will translate it to that language.", "trfl_disabled": "Flag translation disabled.", "rakeback_claimed": "You've claimed {0} as rakeback!", @@ -1125,10 +1125,10 @@ "self_assign_group_role_req": "Users can now self-assign a role from group {0} only if they have {1} role.", "sar_group_not_found": "Group with that number doesn't exist.", "sar_group_deleted": "Group {0} deleted.", - "choose_one": "Choose one" , + "choose_one": "Choose one", "requires_role": "Requires role: {0}", "invalid_message_id": "Invalid Message Id.", - "invalid_message_link": "The message link must be from this server.", + "invalid_message_link": "The message link must be this Bot's message. The bot can't add buttons to other users' messages.", "btnrole_message_max": "Limit reached. You may have up to 25 button roles per message.", "btnrole_not_found": "No button role found on that message.", "btnrole_none": "There are no button roles on this page.", @@ -1145,6 +1145,17 @@ "level_set": "Level of user {0} set to {1} on this server.", "temp_role_added": "User {0} has been given {1} role temporarily. The role expires {2}", "user_afk": "User {0} is AFK.", - "notify_on":"Notification message will be sent on this channel when {0} event triggers.", - "notify_off":"Notification message will no longer be sent when {0} event triggers." + "notify_on": "Notification message will be sent in {0} channel when {1} event triggers.", + "notify_off": "Notification message will no longer be sent when {0} event triggers.", + "notify_none": "No notifications on this page.", + "notify_msg_not_set": "Notification message is not set for this event.", + "notify_list": "Notify List", + "notify_type": "Type", + "notify_msg": "Notify Message", + "notify_available": "List of available notify events", + "notify_desc_levelup": "Triggers when a user levels up on this server.", + "notify_desc_protection": "Triggers when antialt, antispam or antiraid is triggered.", + "notify_desc_addrolerew": "Triggers when a user gets a role as a reward for reaching a level (xprew).", + "notify_desc_removerolerew": "Triggers when a user loses a role as a reward for reaching a level (xprew).", + "notify_desc_not_found": "No description found for this notify event. Please report this." }