mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Compare commits
67 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
273816b8a4 | ||
|
3094c3248b | ||
|
ae6018f0e1 | ||
|
54adbedf9f | ||
|
eb29e34f17 | ||
|
4a402ee673 | ||
|
764babdf06 | ||
|
dd49d202b7 | ||
|
a396c2d9dd | ||
|
15fe8b5daf | ||
|
f8da25a6f3 | ||
|
383acba443 | ||
|
690c03b396 | ||
|
f4ed907134 | ||
|
1b0badd8d8 | ||
|
2c3ada4710 | ||
|
0df3c1a4a1 | ||
|
ac589e0461 | ||
|
8f181eed85 | ||
|
6fefce4c4d | ||
|
d9e080f4b9 | ||
|
762a2eca1f | ||
|
2fba771681 | ||
|
b5e2b6f483 | ||
|
17e5ff8b89 | ||
|
3d287b2afa | ||
|
3f33274cec | ||
|
ee9d8a51bf | ||
|
80a7678a82 | ||
|
de8a0e2207 | ||
|
122b3ae0d9 | ||
|
b4307f9123 | ||
|
5ae18ba1bf | ||
|
44c8c9f459 | ||
|
4e177ff198 | ||
|
ad679a996d | ||
|
7d86a5e3eb | ||
|
214c9a383c | ||
|
f77e1c6b8c | ||
|
7e784b9507 | ||
|
7a14991ed6 | ||
|
4c5c2d7f6e | ||
|
87b90b47ce | ||
|
9f060243f0 | ||
|
d3ab32a7ac | ||
|
7bd081b7cf | ||
|
3a5b482884 | ||
|
db66264bc6 | ||
|
ae1ddd82d0 | ||
|
8523abd6f1 | ||
|
e1892c4ff4 | ||
|
a50a7b3b0e | ||
|
9d2268a925 | ||
|
d77a86c08b | ||
|
d605f685cf | ||
|
bbc1fd28c2 | ||
|
cff8a258d0 | ||
|
1d760a548e | ||
|
25fa8a3852 | ||
|
ca13684c0d | ||
|
0ad6b741e7 | ||
|
4ce756d760 | ||
|
5f2813d3af | ||
|
1b7458529c | ||
|
9c9c8d7490 | ||
|
2700bfdce8 | ||
|
5498c5ce3f |
93
CHANGELOG.md
93
CHANGELOG.md
@@ -2,6 +2,99 @@
|
||||
|
||||
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||
|
||||
## [5.1.6] - 07.08.2024
|
||||
|
||||
### Added
|
||||
|
||||
- `.serverlist` is now paginated
|
||||
|
||||
### Changed
|
||||
|
||||
- `.listservers` renamed to `.serverlist`
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.afk` messages can no longer ping, and the response is moved to DMs to avoid
|
||||
- Possible fix for `.remind` timestamp
|
||||
|
||||
### Removed
|
||||
- Removed old bloat / semi broken / dumb commands
|
||||
- `.memelist` / `.memegen` (too inconvenient to use)
|
||||
- `.activity` (useless owner-only command)
|
||||
- `.rafflecur` (Just use raffle and then award manually instead)
|
||||
- `.rollduel` (we had this command?)
|
||||
- You can no longer bet on `.connect4`
|
||||
- `.economy` Removed.
|
||||
- Was buggy and didn't really show the real state of the economy.
|
||||
- It might come back improved in the future
|
||||
- `.mal` Removed. Useless information / semi broken
|
||||
|
||||
## [5.1.5] - 01.08.2024
|
||||
|
||||
### Added
|
||||
|
||||
- Added: Added a `.afk <msg>?` command which sets an afk message which will trigger whenever someone pings you
|
||||
- Message will when you type a message in any channel that the bot sees, or after 8 hours, whichever comes first
|
||||
- The specified message will be prefixed with "The user is afk: "
|
||||
- The afk message will disappear 30 seconds after being triggered
|
||||
|
||||
### Changed
|
||||
|
||||
- Bot now shows a message when .prune fails due to already running error
|
||||
- Updated some bet descriptions to include 'all' 'half' usage instructions
|
||||
- Updated some command strings
|
||||
- dev: Vastly simplified medusa creation using dotnet templates, docs updated
|
||||
- Slight refactor of .wiki, time, .catfact, .wikia, .define, .bible and .quran commands, no significant change in functionality
|
||||
|
||||
### Fixed
|
||||
|
||||
- .coins will no longer show double minus sign for negative changes
|
||||
- You can once again disable cleverbot responses using fake 'cleverbot:response' module name in permission commands
|
||||
|
||||
### Removed
|
||||
- Removed .rip command
|
||||
|
||||
## [5.1.4] - 13.07.2024
|
||||
|
||||
### Added
|
||||
|
||||
- Added `.coins` command which lists top 10 cryptos ordered by marketcap
|
||||
- Added Clubs rank in the leaderboard to `.clubinfo`
|
||||
- Bot owners can now check other people's bank balance (Not server owners, only bot owner, the person who is hosting the bot)
|
||||
- You can now send multiple waifu gifts at once to waifus. For example `.waifugift 3xRose @user` will give that user 3 roses
|
||||
- The format is `<NUMBER>x<ITEM>`, no spaces
|
||||
- Added `.boosttest` command
|
||||
- Added support for any openai compatible api for the chatterbot feature change:
|
||||
- Changed games.yml to allow input of the apiUrl (needs to be openai compatible) and modelName as a string.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated command strings to clarify `.say` and `.send` usages
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.waifugift` help string
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed selfhost button from `.donate` command, no idea why it was there in the first place
|
||||
|
||||
## [5.1.3] - 06.07.2024
|
||||
|
||||
### Added
|
||||
|
||||
- Added `.quran` command, which will show the provided ayah in english and arabic, including recitation by Alafasy
|
||||
|
||||
### Changed
|
||||
|
||||
- Replying to the bot's message in the channel where chatterbot is enabled will also trigger the ai response, as if you pinged the bot. This only works for chatterbot, but not for nadeko ai command prompts
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.stickeradd` it now properly supports 300x300 image uploads.
|
||||
- Bot should now trim the invalid characters from chatterbot usernames to avoid openai errors
|
||||
- Fixed prompt triggering chatterbot responses twice
|
||||
|
||||
## [5.1.2] - 29.06.2024
|
||||
|
||||
### Fixed
|
||||
|
@@ -8,7 +8,6 @@ COPY src/NadekoBot/*.csproj src/NadekoBot/
|
||||
COPY src/NadekoBot.Coordinator/*.csproj src/NadekoBot.Coordinator/
|
||||
COPY src/NadekoBot.Generators/*.csproj src/NadekoBot.Generators/
|
||||
COPY src/NadekoBot.Voice/*.csproj src/NadekoBot.Voice/
|
||||
COPY NuGet.Config ./
|
||||
|
||||
# Restore the dependencies for the NadekoBot project
|
||||
RUN dotnet restore src/NadekoBot/
|
||||
|
@@ -12,7 +12,6 @@ ProjectSection(SolutionItems) = preProject
|
||||
README.md = README.md
|
||||
.gitlab-ci.yml = .gitlab-ci.yml
|
||||
Dockerfile = Dockerfile
|
||||
NuGet.Config = NuGet.Config
|
||||
migrate.ps1 = migrate.ps1
|
||||
remove-migration.ps1 = remove-migration.ps1
|
||||
EndProjectSection
|
||||
|
@@ -1,6 +0,0 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
<add key="nadeko.bot" value="https://www.myget.org/F/nadeko/api/v3/index.json" protocolVersion="3" />
|
||||
</packageSources>
|
||||
</configuration>
|
@@ -1,8 +1,55 @@
|
||||
# Creating A Medusa
|
||||
|
||||
## Theory
|
||||
## Getting started
|
||||
|
||||
### Introduction
|
||||
This section will guide you through how to create a simple custom medusa. You can find the entirety of this code hosted [here](https://gitlab.com/nadeko/example_medusa)
|
||||
|
||||
#### Prerequisite
|
||||
- [.net8 sdk](https://dotnet.microsoft.com/en-us/download) installed
|
||||
- Optional: use [vscode](https://code.visualstudio.com/download) to write code
|
||||
|
||||
#### Guide
|
||||
|
||||
- Open your favorite terminal and navigate to a folder where you will keep your project .
|
||||
|
||||
- Create a new folder and move into it
|
||||
- `mkdir example_medusa `
|
||||
- `cd example_medusa`
|
||||
|
||||
- Install nadeko-medusa template
|
||||
- `dotnet new install nadeko-medusa`
|
||||
|
||||
- Make a new Nadeko Medusa project
|
||||
- `dotnet new nadeko-medusa`
|
||||
|
||||
### Build it
|
||||
|
||||
- Build your Medusa into a dll that Nadeko can load. In your terminal, type:
|
||||
- `dotnet publish -o bin/medusae/example_medusa /p:DebugType=embedded`
|
||||
|
||||
- Done. You can now try it out in action.
|
||||
|
||||
### Try it out
|
||||
|
||||
- Copy the `bin/medusae/example_medusa` folder into your NadekoBot's `data/medusae/` folder. (Nadeko version 4.1.0+)
|
||||
|
||||
- Load it with `.meload example_medusa`
|
||||
|
||||
- In the channel your bot can see, run the following commands to try it out
|
||||
- `.hello` and
|
||||
- `.hello @<someone>`
|
||||
|
||||
- Check its information with
|
||||
- `.meinfo example_medusa`
|
||||
|
||||
- Unload it
|
||||
- `.meunload example_medusa`
|
||||
|
||||
- :tada: Congrats! You've just made your first medusa! :tada:
|
||||
|
||||
|
||||
|
||||
## Theory
|
||||
|
||||
Medusa system allows you to write independent medusae (known as "modules", "cogs" or "plugins" in other software) which you can then load, unload and update at will without restarting the bot.
|
||||
|
||||
@@ -99,9 +146,9 @@ If you don't want any auxiliary files, and you don't want to bother making new .
|
||||
|
||||
If you update your response strings .yml file(s) while the medusa is loaded and running, running `.stringsreload` will reload the responses without the need to reload the medusa or restart the bot.
|
||||
|
||||
#### Config
|
||||
#### Bot medusa config file
|
||||
|
||||
- Medusa config is kept in `medusae/medusa.yml` file
|
||||
- Medusa config is kept in `data/medusae/medusa.yml` file in NadekoBot installation folder
|
||||
- At the moment this config only keeps track of which medusae are currently loaded (they will also be always loaded at startup)
|
||||
- If a medusa is causing issues and you're unable to unload it, you can remove it from the `loaded:` list in this config file and restart the bot. It won't be loaded next time the bot is started up
|
||||
|
||||
@@ -115,138 +162,4 @@ To make sure your medusa can be properly unloaded/reloaded you must:
|
||||
|
||||
- If you are still having issues, you can always run `.meunload` followed by a bot restart, or if you want to find what is causing the medusa unloadability issues, you can check the [microsoft's assembly unloadability debugging guide](https://docs.microsoft.com/en-us/dotnet/standard/assembly/unloadability)
|
||||
|
||||
## Practice
|
||||
|
||||
This section will guide you through how to create a simple custom medusa. You can find the entirety of this code hosted [here](https://gitlab.com/nadeko/example_medusa)
|
||||
|
||||
#### Prerequisite
|
||||
- [.net6 sdk](https://dotnet.microsoft.com/en-us/download) installed
|
||||
- Optional: use [vscode](https://code.visualstudio.com/download) to write code
|
||||
|
||||
#### Guide
|
||||
|
||||
|
||||
- Open your favorite terminal and navigate to a folder where you will keep your project .
|
||||
|
||||
- Create a new folder
|
||||
- `mkdir example_medusa`
|
||||
- Create a new .net class library
|
||||
- `dotnet new classlib`
|
||||
- Open the current folder with your favorite editor/IDE. In this case we'll use VsCode
|
||||
- `code .`
|
||||
- Remove the `Class1.cs` file
|
||||
- Replace the contents of the `.csproj` file with the following contents
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
|
||||
<!-- Reduces some boilerplate in your .cs files -->
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<!-- Use latest .net features -->
|
||||
<LangVersion>preview</LangVersion>
|
||||
<EnablePreviewFeatures>true</EnablePreviewFeatures>
|
||||
<GenerateRequiresPreviewFeaturesAttribute>true</GenerateRequiresPreviewFeaturesAttribute>
|
||||
|
||||
<!-- tell .net that this library will be used as a plugin -->
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Base medusa package. You MUST reference this in order to have a working medusa -->
|
||||
<!-- Also, this package comes from MyGet, which requires you to have a NuGet.Config file next to your .csproj -->
|
||||
<PackageReference Include="Nadeko.Medusa" Version="4.3.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<!-- Note: If you want to use NadekoBot services etc... You will have to manually clone
|
||||
the https://gitlab.com/kwoth/nadekobot repo locally and reference the NadekoBot.csproj because there is no NadekoBot package atm.
|
||||
It is strongly recommended that you checkout a specific tag which matches your version of nadeko,
|
||||
as there could be breaking changes even between minor versions of NadekoBot.
|
||||
For example if you're running NadekoBot 4.1.0 locally for which you want to create a medusa for,
|
||||
you should do "git checkout 4.1.0" in your NadekoBot solution and then reference the NadekoBot.csproj
|
||||
-->
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy shortcut and full strings to output (if they exist) -->
|
||||
<ItemGroup>
|
||||
<None Update="res.yml;cmds.yml;strings/**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
```
|
||||
- Create a `MySnek.cs` file and add the following contents
|
||||
```cs
|
||||
using Nadeko.Snake;
|
||||
using NadekoBot;
|
||||
using Discord;
|
||||
|
||||
public sealed class MySnek : Snek
|
||||
{
|
||||
[cmd]
|
||||
public async Task Hello(AnyContext ctx)
|
||||
{
|
||||
await ctx.Channel.SendMessageAsync($"Hello everyone!");
|
||||
}
|
||||
|
||||
[cmd]
|
||||
public async Task Hello(AnyContext ctx, IUser target)
|
||||
{
|
||||
await ctx.ConfirmLocalizedAsync("hello", target);
|
||||
}
|
||||
}
|
||||
```
|
||||
- Create `res.yml` and `cmds.yml` files with the following contents
|
||||
`res.yml`
|
||||
```yml
|
||||
medusa.description: "This is my medusa's description"
|
||||
hello: "Hello {0}, from res.yml!"
|
||||
```
|
||||
|
||||
`cmds.yml`
|
||||
```yml
|
||||
hello:
|
||||
desc: "This is a basic hello command"
|
||||
args:
|
||||
- ""
|
||||
- "@Someone"
|
||||
```
|
||||
|
||||
- Add `NuGet.Config` file which will let you use the base Nadeko.Medusa package. This file should always look like this and you shouldn't change it
|
||||
|
||||
```xml
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
<add key="nadeko.bot" value="https://www.myget.org/F/nadeko/api/v3/index.json" protocolVersion="3" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
### Build it
|
||||
|
||||
- Build your Medusa into a dll that Nadeko can load. In your terminal, type:
|
||||
- `dotnet publish -o bin/medusae/example_medusa /p:DebugType=embedded`
|
||||
|
||||
- Done. You can now try it out in action.
|
||||
|
||||
### Try it out
|
||||
|
||||
- Copy the `bin/medusae/example_medusa` folder into your NadekoBot's `data/medusae/` folder. (Nadeko version 4.1.0+)
|
||||
|
||||
- Load it with `.meload example_medusa`
|
||||
|
||||
- In the channel your bot can see, run the following commands to try it out
|
||||
- `.hello` and
|
||||
- `.hello @<someone>`
|
||||
|
||||
- Check its information with
|
||||
- `.meinfo example_medusa`
|
||||
|
||||
- Unload it
|
||||
- `.meunload example_medusa`
|
||||
|
||||
- Congrats! You've just made your first medusa!
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using Discord;
|
||||
using NadekoBot;
|
||||
|
||||
namespace NadekoBot.Medusa;
|
||||
|
||||
|
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net.Core" Version="3.204.0" />
|
||||
<PackageReference Include="Discord.Net.Core" Version="3.15.3" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
||||
</ItemGroup>
|
||||
|
@@ -77,7 +77,6 @@ csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_accessors = true:suggestion
|
||||
csharp_style_expression_bodied_constructors = when_on_single_line:suggestion
|
||||
csharp_style_expression_bodied_indexers = true:suggestion
|
||||
csharp_style_expression_bodied_lambdas = true:suggestion
|
||||
csharp_style_expression_bodied_local_functions = true:suggestion
|
||||
@@ -181,9 +180,9 @@ dotnet_naming_rule.private_readonly_field.symbols = private_readonly_field
|
||||
dotnet_naming_rule.private_readonly_field.style = begins_with_underscore
|
||||
dotnet_naming_rule.private_readonly_field.severity = warning
|
||||
|
||||
dotnet_naming_rule.private_field.symbols = private_field
|
||||
dotnet_naming_rule.private_field.style = camel_case
|
||||
dotnet_naming_rule.private_field.severity = warning
|
||||
# dotnet_naming_rule.private_field.symbols = private_field
|
||||
# dotnet_naming_rule.private_field.style = camel_case
|
||||
# dotnet_naming_rule.private_field.severity = warning
|
||||
|
||||
dotnet_naming_rule.const_fields.symbols = const_fields
|
||||
dotnet_naming_rule.const_fields.style = all_upper
|
||||
|
@@ -88,7 +88,7 @@ public sealed class Bot : IBot
|
||||
|
||||
|
||||
public IReadOnlyList<ulong> GetCurrentGuildIds()
|
||||
=> Client.Guilds.Select(x => x.Id).ToList();
|
||||
=> Client.Guilds.Select(x => x.Id).ToList().ToList();
|
||||
|
||||
private void AddServices()
|
||||
{
|
||||
@@ -114,7 +114,7 @@ public sealed class Bot : IBot
|
||||
// svcs.Components.Remove<IPlanner, Planner>();
|
||||
// svcs.Components.Add<IPlanner, RemovablePlanner>();
|
||||
|
||||
svcs.AddSingleton<IBotCredentials, IBotCredentials>(_ => _credsProvider.GetCreds());
|
||||
svcs.AddSingleton<IBotCredentials>(_ => _credsProvider.GetCreds());
|
||||
svcs.AddSingleton<DbService, DbService>(_db);
|
||||
svcs.AddSingleton<IBotCredsProvider>(_credsProvider);
|
||||
svcs.AddSingleton<DiscordSocketClient>(Client);
|
||||
|
@@ -182,15 +182,6 @@ public static class GuildConfigExtensions
|
||||
.SelectMany(gc => gc.FollowedStreams)
|
||||
.ToList();
|
||||
|
||||
public static void SetCleverbotEnabled(this DbSet<GuildConfig> configs, ulong id, bool cleverbotEnabled)
|
||||
{
|
||||
var conf = configs.FirstOrDefault(gc => gc.GuildId == id);
|
||||
|
||||
if (conf is null)
|
||||
return;
|
||||
|
||||
conf.CleverbotEnabled = cleverbotEnabled;
|
||||
}
|
||||
|
||||
public static XpSettings XpSettingsFor(this DbContext ctx, ulong guildId)
|
||||
{
|
||||
|
@@ -1,6 +1,4 @@
|
||||
#nullable disable
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace NadekoBot.Db.Models;
|
||||
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Migrations;
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Administration._common.results;
|
||||
|
||||
|
@@ -4,7 +4,6 @@ using System.Net;
|
||||
using System.Threading.Channels;
|
||||
using LinqToDB;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
|
@@ -27,5 +27,15 @@ public partial class Administration
|
||||
.Confirm($"{result.GuildCount} guilds' data remain in the database.")
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task Keep()
|
||||
{
|
||||
var result = await _svc.KeepGuild(Context.Guild.Id);
|
||||
|
||||
await Response().Text("This guild's bot data will be saved.").SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
using LinqToDB;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using LinqToDB.Mapping;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
@@ -66,67 +67,88 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
|
||||
// delete guild xp
|
||||
await ctx.GetTable<UserXpStats>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
|
||||
// delete expressions
|
||||
await ctx.GetTable<NadekoExpression>()
|
||||
.Where(x => x.GuildId != null && !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId.Value))
|
||||
.Where(x => x.GuildId != null
|
||||
&& !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId.Value))
|
||||
.DeleteAsync();
|
||||
|
||||
|
||||
// delete quotes
|
||||
await ctx.GetTable<Quote>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
|
||||
// delete planted currencies
|
||||
await ctx.GetTable<PlantedCurrency>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
|
||||
// delete image only channels
|
||||
await ctx.GetTable<ImageOnlyChannel>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
|
||||
// delete reaction roles
|
||||
await ctx.GetTable<ReactionRoleV2>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
|
||||
// delete ignored users
|
||||
await ctx.GetTable<DiscordPermOverride>()
|
||||
.Where(x => x.GuildId != null && !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId.Value))
|
||||
.Where(x => x.GuildId != null
|
||||
&& !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId.Value))
|
||||
.DeleteAsync();
|
||||
|
||||
|
||||
// delete perm overrides
|
||||
await ctx.GetTable<DiscordPermOverride>()
|
||||
.Where(x => x.GuildId != null && !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId.Value))
|
||||
.Where(x => x.GuildId != null
|
||||
&& !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId.Value))
|
||||
.DeleteAsync();
|
||||
|
||||
|
||||
// delete repeaters
|
||||
await ctx.GetTable<Repeater>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
|
||||
return new()
|
||||
{
|
||||
GuildCount = guildIds.Keys.Count,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> KeepGuild(ulong guildId)
|
||||
{
|
||||
await using var db = _db.GetDbContext();
|
||||
await using var ctx = db.CreateLinqToDBContext();
|
||||
|
||||
var table = ctx.CreateTable<KeptGuilds>(tableOptions: TableOptions.CheckExistence);
|
||||
|
||||
if (await table.AnyAsyncLinqToDB(x => x.GuildId == guildId))
|
||||
return false;
|
||||
|
||||
await table.InsertAsync(() => new()
|
||||
{
|
||||
GuildId = guildId
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ValueTask OnKeepReport(KeepReport report)
|
||||
{
|
||||
guildIds[report.ShardId] = report.GuildIds;
|
||||
@@ -136,11 +158,18 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
await _pubSub.Sub(_keepTriggerKey, OnKeepTrigger);
|
||||
|
||||
_client.JoinedGuild += ClientOnJoinedGuild;
|
||||
|
||||
if (_client.ShardId == 0)
|
||||
await _pubSub.Sub(_keepReportKey, OnKeepReport);
|
||||
}
|
||||
|
||||
private async Task ClientOnJoinedGuild(SocketGuild arg)
|
||||
{
|
||||
await KeepGuild(arg.Id);
|
||||
}
|
||||
|
||||
private ValueTask OnKeepTrigger(bool arg)
|
||||
{
|
||||
_pubSub.Pub(_keepReportKey,
|
||||
@@ -152,4 +181,10 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public class KeptGuilds
|
||||
{
|
||||
[PrimaryKey]
|
||||
public ulong GuildId { get; set; }
|
||||
}
|
@@ -3,4 +3,5 @@
|
||||
public interface ICleanupService
|
||||
{
|
||||
Task<KeepResult?> DeleteMissingGuildDataAsync();
|
||||
Task<bool> KeepGuild(ulong guildId);
|
||||
}
|
@@ -1,6 +1,4 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Db;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public class GameVoiceChannelService : INService
|
||||
|
@@ -225,5 +225,19 @@ public partial class Administration
|
||||
if (!enabled)
|
||||
await Response().Pending(strs.greetdmmsg_enable($"`{prefix}greetdm`")).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
[Ratelimit(5)]
|
||||
public async Task BoostTest([Leftover] IGuildUser? user = null)
|
||||
{
|
||||
user ??= (IGuildUser)ctx.User;
|
||||
|
||||
await _service.BoostTest((ITextChannel)ctx.Channel, user);
|
||||
var enabled = _service.GetBoostEnabled(ctx.Guild.Id);
|
||||
if (!enabled)
|
||||
await Response().Pending(strs.boostmsg_enable($"`{prefix}boost`")).SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using System.Threading.Channels;
|
||||
|
||||
@@ -242,7 +241,7 @@ public class GreetService : INService, IReadyExecutor
|
||||
guild: channel.Guild,
|
||||
channel: channel,
|
||||
users: users.ToArray());
|
||||
|
||||
|
||||
var text = SmartText.CreateFrom(conf.ChannelGreetMessageText);
|
||||
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||
try
|
||||
@@ -630,6 +629,13 @@ public class GreetService : INService, IReadyExecutor
|
||||
return conf.SendChannelByeMessage;
|
||||
}
|
||||
|
||||
public bool GetBoostEnabled(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
return conf.SendBoostMessage;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Messages
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using System.Threading.Channels;
|
||||
|
||||
|
@@ -65,23 +65,6 @@ public partial class Administration
|
||||
await progressMsg.DeleteAsync();
|
||||
}
|
||||
|
||||
private async Task SendResult(PruneResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case PruneResult.Success:
|
||||
break;
|
||||
case PruneResult.AlreadyRunning:
|
||||
break;
|
||||
case PruneResult.FeatureLimit:
|
||||
await Response().Pending(strs.feature_limit_reached_owner).SendAsync();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(result), result, null);
|
||||
}
|
||||
}
|
||||
|
||||
// prune x
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
@@ -218,5 +201,27 @@ public partial class Administration
|
||||
|
||||
await Response().Confirm(strs.prune_cancelled).SendAsync();
|
||||
}
|
||||
|
||||
|
||||
private async Task SendResult(PruneResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case PruneResult.Success:
|
||||
break;
|
||||
case PruneResult.AlreadyRunning:
|
||||
var msg = await Response().Pending(strs.prune_already_running).SendAsync();
|
||||
msg.DeleteAfter(5);
|
||||
break;
|
||||
case PruneResult.FeatureLimit:
|
||||
var msg2 = await Response().Pending(strs.feature_limit_reached_owner).SendAsync();
|
||||
msg2.DeleteAfter(10);
|
||||
break;
|
||||
default:
|
||||
Log.Error("Unhandled result received in prune: {Result}", result);
|
||||
await Response().Error(strs.error_occured).SendAsync();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -26,21 +26,21 @@ public class PruneService : INService
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel, nameof(channel));
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||
|
||||
var originalAmount = amount;
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||
|
||||
using var cancelSource = new CancellationTokenSource();
|
||||
if (!_pruningGuilds.TryAdd(channel.GuildId, cancelSource))
|
||||
return PruneResult.AlreadyRunning;
|
||||
|
||||
if (!await _ps.LimitHitAsync(LimitedFeatureName.Prune, channel.Guild.OwnerId))
|
||||
{
|
||||
return PruneResult.FeatureLimit;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!await _ps.LimitHitAsync(LimitedFeatureName.Prune, channel.Guild.OwnerId))
|
||||
{
|
||||
return PruneResult.FeatureLimit;
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
IMessage[] msgs;
|
||||
IMessage lastMessage = null;
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Patronage;
|
||||
using NadekoBot.Db.Models;
|
||||
using OneOf;
|
||||
using OneOf.Types;
|
||||
|
@@ -33,7 +33,7 @@ public partial class Administration
|
||||
var msg = await ctx.Channel.GetMessageAsync(messageId);
|
||||
if (msg is null)
|
||||
{
|
||||
await Response().Error(strs.not_found).SendAsync();
|
||||
await Response().Error(strs.rero_message_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,6 @@
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Patronage;
|
||||
using NadekoBot.Db.Models;
|
||||
using OneOf.Types;
|
||||
|
@@ -3,7 +3,6 @@ using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
|
@@ -546,7 +546,7 @@ public partial class Administration
|
||||
text = await repSvc.ReplaceAsync(text, repCtx);
|
||||
await Response().Channel(ch).Text(text).SendAsync();
|
||||
|
||||
await ctx.OkAsync();;
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
|
||||
|
@@ -273,6 +273,31 @@ public partial class Administration
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public Task WarnDelete(IGuildUser user, int index)
|
||||
=> WarnDelete(user.Id, index);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task WarnDelete(ulong userId, int index)
|
||||
{
|
||||
if (--index < 0)
|
||||
return;
|
||||
|
||||
var warn = await _service.WarnDelete(userId, index);
|
||||
|
||||
if (warn is null)
|
||||
{
|
||||
await Response().Error(strs.warning_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await Response().Confirm(strs.warning_deleted(Format.Bold(index.ToString()))).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
@@ -286,6 +311,7 @@ public partial class Administration
|
||||
{
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
var success = await _service.WarnClearAsync(ctx.Guild.Id, userId, index, ctx.User.ToString());
|
||||
var userStr = Format.Bold((ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString());
|
||||
if (index == 0)
|
||||
|
@@ -4,7 +4,6 @@ using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Common.TypeReaders.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Permissions.Services;
|
||||
using NadekoBot.Db.Models;
|
||||
using Newtonsoft.Json;
|
||||
@@ -89,9 +88,10 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
{
|
||||
ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
|
||||
|
||||
previousCount = uow.Set<Warning>().ForId(guildId, userId)
|
||||
.Where(w => !w.Forgiven && w.UserId == userId)
|
||||
.Sum(x => x.Weight);
|
||||
previousCount = uow.Set<Warning>()
|
||||
.ForId(guildId, userId)
|
||||
.Where(w => !w.Forgiven && w.UserId == userId)
|
||||
.Sum(x => x.Weight);
|
||||
|
||||
uow.Set<Warning>().Add(warn);
|
||||
|
||||
@@ -103,7 +103,7 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
var totalCount = previousCount + weight;
|
||||
|
||||
var p = ps.Where(x => x.Count > previousCount && x.Count <= totalCount)
|
||||
.MaxBy(x => x.Count);
|
||||
.MaxBy(x => x.Count);
|
||||
|
||||
if (p is not null)
|
||||
{
|
||||
@@ -244,33 +244,33 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var cleared = await uow.Set<Warning>()
|
||||
.Where(x => uow.Set<GuildConfig>()
|
||||
.Any(y => y.GuildId == x.GuildId
|
||||
&& y.WarnExpireHours > 0
|
||||
&& y.WarnExpireAction == WarnExpireAction.Clear)
|
||||
&& x.Forgiven == false
|
||||
&& x.DateAdded
|
||||
< DateTime.UtcNow.AddHours(-uow.Set<GuildConfig>()
|
||||
.Where(y => x.GuildId == y.GuildId)
|
||||
.Select(y => y.WarnExpireHours)
|
||||
.First()))
|
||||
.UpdateAsync(_ => new()
|
||||
{
|
||||
Forgiven = true,
|
||||
ForgivenBy = "expiry"
|
||||
});
|
||||
.Where(x => uow.Set<GuildConfig>()
|
||||
.Any(y => y.GuildId == x.GuildId
|
||||
&& y.WarnExpireHours > 0
|
||||
&& y.WarnExpireAction == WarnExpireAction.Clear)
|
||||
&& x.Forgiven == false
|
||||
&& x.DateAdded
|
||||
< DateTime.UtcNow.AddHours(-uow.Set<GuildConfig>()
|
||||
.Where(y => x.GuildId == y.GuildId)
|
||||
.Select(y => y.WarnExpireHours)
|
||||
.First()))
|
||||
.UpdateAsync(_ => new()
|
||||
{
|
||||
Forgiven = true,
|
||||
ForgivenBy = "expiry"
|
||||
});
|
||||
|
||||
var deleted = await uow.Set<Warning>()
|
||||
.Where(x => uow.Set<GuildConfig>()
|
||||
.Any(y => y.GuildId == x.GuildId
|
||||
&& y.WarnExpireHours > 0
|
||||
&& y.WarnExpireAction == WarnExpireAction.Delete)
|
||||
&& x.DateAdded
|
||||
< DateTime.UtcNow.AddHours(-uow.Set<GuildConfig>()
|
||||
.Where(y => x.GuildId == y.GuildId)
|
||||
.Select(y => y.WarnExpireHours)
|
||||
.First()))
|
||||
.DeleteAsync();
|
||||
.Where(x => uow.Set<GuildConfig>()
|
||||
.Any(y => y.GuildId == x.GuildId
|
||||
&& y.WarnExpireHours > 0
|
||||
&& y.WarnExpireAction == WarnExpireAction.Delete)
|
||||
&& x.DateAdded
|
||||
< DateTime.UtcNow.AddHours(-uow.Set<GuildConfig>()
|
||||
.Where(y => x.GuildId == y.GuildId)
|
||||
.Select(y => y.WarnExpireHours)
|
||||
.First()))
|
||||
.DeleteAsync();
|
||||
|
||||
if (cleared > 0 || deleted > 0)
|
||||
{
|
||||
@@ -293,21 +293,21 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
if (config.WarnExpireAction == WarnExpireAction.Clear)
|
||||
{
|
||||
await uow.Set<Warning>()
|
||||
.Where(x => x.GuildId == guildId
|
||||
&& x.Forgiven == false
|
||||
&& x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours))
|
||||
.UpdateAsync(_ => new()
|
||||
{
|
||||
Forgiven = true,
|
||||
ForgivenBy = "expiry"
|
||||
});
|
||||
.Where(x => x.GuildId == guildId
|
||||
&& x.Forgiven == false
|
||||
&& x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours))
|
||||
.UpdateAsync(_ => new()
|
||||
{
|
||||
Forgiven = true,
|
||||
ForgivenBy = "expiry"
|
||||
});
|
||||
}
|
||||
else if (config.WarnExpireAction == WarnExpireAction.Delete)
|
||||
{
|
||||
await uow.Set<Warning>()
|
||||
.Where(x => x.GuildId == guildId
|
||||
&& x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours))
|
||||
.DeleteAsync();
|
||||
.Where(x => x.GuildId == guildId
|
||||
&& x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours))
|
||||
.DeleteAsync();
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
@@ -425,8 +425,8 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.GuildConfigsForId(guildId, gc => gc.Include(x => x.WarnPunishments))
|
||||
.WarnPunishments.OrderBy(x => x.Count)
|
||||
.ToArray();
|
||||
.WarnPunishments.OrderBy(x => x.Count)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public (IReadOnlyCollection<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill(
|
||||
@@ -436,20 +436,20 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
var gusers = guild.Users;
|
||||
//get user objects and reasons
|
||||
var bans = people.Split("\n")
|
||||
.Select(x =>
|
||||
{
|
||||
var split = x.Trim().Split(" ");
|
||||
.Select(x =>
|
||||
{
|
||||
var split = x.Trim().Split(" ");
|
||||
|
||||
var reason = string.Join(" ", split.Skip(1));
|
||||
var reason = string.Join(" ", split.Skip(1));
|
||||
|
||||
if (ulong.TryParse(split[0], out var id))
|
||||
return (Original: split[0], Id: id, Reason: reason);
|
||||
if (ulong.TryParse(split[0], out var id))
|
||||
return (Original: split[0], Id: id, Reason: reason);
|
||||
|
||||
return (Original: split[0],
|
||||
gusers.FirstOrDefault(u => u.ToString().ToLowerInvariant() == x)?.Id,
|
||||
Reason: reason);
|
||||
})
|
||||
.ToArray();
|
||||
return (Original: split[0],
|
||||
gusers.FirstOrDefault(u => u.ToString().ToLowerInvariant() == x)?.Id,
|
||||
Reason: reason);
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
//if user is null, means that person couldn't be found
|
||||
var missing = bans.Count(x => !x.Id.HasValue);
|
||||
@@ -483,11 +483,12 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
}
|
||||
else if (template is null)
|
||||
{
|
||||
uow.Set<BanTemplate>().Add(new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Text = text
|
||||
});
|
||||
uow.Set<BanTemplate>()
|
||||
.Add(new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Text = text
|
||||
});
|
||||
}
|
||||
else
|
||||
template.Text = text;
|
||||
@@ -499,31 +500,31 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await ctx.Set<BanTemplate>()
|
||||
.ToLinqToDBTable()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Text = null,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
PruneDays = pruneDays
|
||||
},
|
||||
old => new()
|
||||
{
|
||||
PruneDays = pruneDays
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId
|
||||
});
|
||||
.ToLinqToDBTable()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Text = null,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
PruneDays = pruneDays
|
||||
},
|
||||
old => new()
|
||||
{
|
||||
PruneDays = pruneDays
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<int?> GetBanPruneAsync(ulong guildId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.Set<BanTemplate>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.Select(x => x.PruneDays)
|
||||
.FirstOrDefaultAsyncLinqToDB();
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.Select(x => x.PruneDays)
|
||||
.FirstOrDefaultAsyncLinqToDB();
|
||||
}
|
||||
|
||||
public Task<SmartText> GetBanUserDmEmbed(
|
||||
@@ -554,18 +555,18 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
banReason = string.IsNullOrWhiteSpace(banReason) ? "-" : banReason;
|
||||
|
||||
var repCtx = new ReplacementContext(client, guild)
|
||||
.WithOverride("%ban.mod%", () => moderator.ToString())
|
||||
.WithOverride("%ban.mod.fullname%", () => moderator.ToString())
|
||||
.WithOverride("%ban.mod.name%", () => moderator.Username)
|
||||
.WithOverride("%ban.mod.discrim%", () => moderator.Discriminator)
|
||||
.WithOverride("%ban.user%", () => target.ToString())
|
||||
.WithOverride("%ban.user.fullname%", () => target.ToString())
|
||||
.WithOverride("%ban.user.name%", () => target.Username)
|
||||
.WithOverride("%ban.user.discrim%", () => target.Discriminator)
|
||||
.WithOverride("%reason%", () => banReason)
|
||||
.WithOverride("%ban.reason%", () => banReason)
|
||||
.WithOverride("%ban.duration%",
|
||||
() => duration?.ToString(@"d\.hh\:mm") ?? "perma");
|
||||
.WithOverride("%ban.mod%", () => moderator.ToString())
|
||||
.WithOverride("%ban.mod.fullname%", () => moderator.ToString())
|
||||
.WithOverride("%ban.mod.name%", () => moderator.Username)
|
||||
.WithOverride("%ban.mod.discrim%", () => moderator.Discriminator)
|
||||
.WithOverride("%ban.user%", () => target.ToString())
|
||||
.WithOverride("%ban.user.fullname%", () => target.ToString())
|
||||
.WithOverride("%ban.user.name%", () => target.Username)
|
||||
.WithOverride("%ban.user.discrim%", () => target.Discriminator)
|
||||
.WithOverride("%reason%", () => banReason)
|
||||
.WithOverride("%ban.reason%", () => banReason)
|
||||
.WithOverride("%ban.duration%",
|
||||
() => duration?.ToString(@"d\.hh\:mm") ?? "perma");
|
||||
|
||||
|
||||
// if template isn't set, use the old message style
|
||||
@@ -594,4 +595,24 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
var output = SmartText.CreateFrom(template);
|
||||
return await _repSvc.ReplaceAsync(output, repCtx);
|
||||
}
|
||||
|
||||
public async Task<Warning> WarnDelete(ulong userId, int index)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
var warn = await uow.GetTable<Warning>()
|
||||
.Where(x => x.UserId == userId)
|
||||
.OrderByDescending(x => x.DateAdded)
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsyncLinqToDB();
|
||||
|
||||
if (warn is not null)
|
||||
{
|
||||
await uow.GetTable<Warning>()
|
||||
.Where(x => x.Id == warn.Id)
|
||||
.DeleteAsync();
|
||||
}
|
||||
|
||||
return warn;
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
@@ -398,15 +398,14 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
|
||||
|
||||
var serialized = _service.ExportExpressions(ctx.Guild?.Id);
|
||||
await using var stream = await serialized.ToStream();
|
||||
await ctx.Channel.SendFileAsync(stream, "exprs-export.yml");
|
||||
await ctx.User.SendFileAsync(stream, $"exprs-export_{DateTime.UtcNow:yyyy-MM-dd-HH-mm-ss}_{(ctx.Guild?.Id.ToString() ?? "global")}.yml");
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
#if GLOBAL_NADEKO
|
||||
[OwnerOnly]
|
||||
#endif
|
||||
public async Task ExprsImport([Leftover] string input = null)
|
||||
{
|
||||
// todo cooldown on public bot for 1 day, limit 100
|
||||
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
{
|
||||
await Response().Error(strs.expr_insuff_perms).SendAsync();
|
||||
|
@@ -2,7 +2,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Common.Yml;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
|
@@ -73,6 +73,27 @@ public partial class Gambling
|
||||
await Response().Error(strs.cant_dm).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task BankBalance([Leftover] IUser user)
|
||||
{
|
||||
var bal = await _bank.GetBalanceAsync(user.Id);
|
||||
|
||||
var eb = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(strs.bank_balance_other(user.ToString(), N(bal))));
|
||||
|
||||
try
|
||||
{
|
||||
await Response().User(ctx.User).Embed(eb).SendAsync();
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Response().Error(strs.cant_dm).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BankTakeInternalAsync(long amount, ulong userId)
|
||||
{
|
||||
|
@@ -3,7 +3,6 @@ using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Blackjack;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Utility;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
|
@@ -38,11 +38,11 @@ public sealed class Connect4Game : IDisposable
|
||||
|
||||
public Phase CurrentPhase { get; private set; } = Phase.Joining;
|
||||
|
||||
public ImmutableArray<Field> GameState
|
||||
=> _gameState.ToImmutableArray();
|
||||
public IReadOnlyList<Field> GameState
|
||||
=> _gameState.AsReadOnly();
|
||||
|
||||
public ImmutableArray<(ulong UserId, string Username)?> Players
|
||||
=> _players.ToImmutableArray();
|
||||
public IReadOnlyCollection<(ulong UserId, string Username)?> Players
|
||||
=> _players.AsReadOnly();
|
||||
|
||||
public (ulong UserId, string Username) CurrentPlayer
|
||||
=> CurrentPhase == Phase.P1Move ? _players[0].Value : _players[1].Value;
|
||||
@@ -56,7 +56,6 @@ public sealed class Connect4Game : IDisposable
|
||||
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
private readonly Options _options;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly NadekoRandom _rng;
|
||||
|
||||
private Timer playerTimeoutTimer;
|
||||
@@ -73,12 +72,11 @@ public sealed class Connect4Game : IDisposable
|
||||
public Connect4Game(
|
||||
ulong userId,
|
||||
string userName,
|
||||
Options options,
|
||||
ICurrencyService cs)
|
||||
Options options
|
||||
)
|
||||
{
|
||||
_players[0] = (userId, userName);
|
||||
_options = options;
|
||||
_cs = cs;
|
||||
|
||||
_rng = new();
|
||||
for (var i = 0; i < NUMBER_OF_COLUMNS * NUMBER_OF_ROWS; i++)
|
||||
@@ -99,14 +97,13 @@ public sealed class Connect4Game : IDisposable
|
||||
{
|
||||
_ = OnGameFailedToStart?.Invoke(this);
|
||||
CurrentPhase = Phase.Ended;
|
||||
await _cs.AddAsync(_players[0].Value.UserId, _options.Bet, new("connect4", "refund"));
|
||||
}
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> Join(ulong userId, string userName, int bet)
|
||||
public async Task<bool> Join(ulong userId, string userName)
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
@@ -117,11 +114,6 @@ public sealed class Connect4Game : IDisposable
|
||||
if (_players[0].Value.UserId == userId) // same user can't join own game
|
||||
return false;
|
||||
|
||||
if (bet != _options.Bet) // can't join if bet amount is not the same
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(userId, bet, new("connect4", "bet"))) // user doesn't have enough money to gamble
|
||||
return false;
|
||||
|
||||
if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player
|
||||
{
|
||||
@@ -351,13 +343,8 @@ public sealed class Connect4Game : IDisposable
|
||||
|
||||
if (result == Result.Draw)
|
||||
{
|
||||
_cs.AddAsync(CurrentPlayer.UserId, _options.Bet, new("connect4", "draw"));
|
||||
_cs.AddAsync(OtherPlayer.UserId, _options.Bet, new("connect4", "draw"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (winId is not null)
|
||||
_cs.AddAsync(winId.Value, (long)(_options.Bet * 1.98), new("connect4", "win"));
|
||||
}
|
||||
|
||||
private Field GetPlayerPiece(ulong userId)
|
||||
@@ -394,16 +381,10 @@ public sealed class Connect4Game : IDisposable
|
||||
HelpText = "Turn time in seconds. It has to be between 5 and 60. Default 15.")]
|
||||
public int TurnTimer { get; set; } = 15;
|
||||
|
||||
[Option('b', "bet", Required = false, Default = 0, HelpText = "Amount you bet. Default 0.")]
|
||||
public int Bet { get; set; }
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (TurnTimer is < 5 or > 60)
|
||||
TurnTimer = 15;
|
||||
|
||||
if (Bet < 0)
|
||||
Bet = 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -29,17 +29,15 @@ public partial class Gambling
|
||||
}
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
private IUserMessage msg;
|
||||
|
||||
private int repostCounter;
|
||||
|
||||
public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb)
|
||||
public Connect4Commands(DiscordSocketClient client, GamblingConfigService gamb)
|
||||
: base(gamb)
|
||||
{
|
||||
_client = client;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
@@ -48,10 +46,8 @@ public partial class Gambling
|
||||
public async Task Connect4(params string[] args)
|
||||
{
|
||||
var (options, _) = OptionsParser.ParseFrom(new Connect4Game.Options(), args);
|
||||
if (!await CheckBetOptional(options.Bet))
|
||||
return;
|
||||
|
||||
var newGame = new Connect4Game(ctx.User.Id, ctx.User.ToString(), options, _cs);
|
||||
var newGame = new Connect4Game(ctx.User.Id, ctx.User.ToString(), options);
|
||||
Connect4Game game;
|
||||
if ((game = _service.Connect4Games.GetOrAdd(ctx.Channel.Id, newGame)) != newGame)
|
||||
{
|
||||
@@ -60,31 +56,17 @@ public partial class Gambling
|
||||
|
||||
newGame.Dispose();
|
||||
//means game already exists, try to join
|
||||
await game.Join(ctx.User.Id, ctx.User.ToString(), options.Bet);
|
||||
await game.Join(ctx.User.Id, ctx.User.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.Bet > 0)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id, options.Bet, new("connect4", "bet")))
|
||||
{
|
||||
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
||||
_service.Connect4Games.TryRemove(ctx.Channel.Id, out _);
|
||||
game.Dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
game.OnGameStateUpdated += Game_OnGameStateUpdated;
|
||||
game.OnGameFailedToStart += GameOnGameFailedToStart;
|
||||
game.OnGameEnded += GameOnGameEnded;
|
||||
_client.MessageReceived += ClientMessageReceived;
|
||||
|
||||
game.Initialize();
|
||||
if (options.Bet == 0)
|
||||
await Response().Confirm(strs.connect4_created).SendAsync();
|
||||
else
|
||||
await Response().Error(strs.connect4_created_bet(N(options.Bet))).SendAsync();
|
||||
await Response().Confirm(strs.connect4_created).SendAsync();
|
||||
|
||||
Task ClientMessageReceived(SocketMessage arg)
|
||||
{
|
||||
@@ -151,19 +133,19 @@ public partial class Gambling
|
||||
title = GetText(strs.connect4_draw);
|
||||
|
||||
return msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||
.WithTitle(title)
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
.WithTitle(title)
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Game_OnGameStateUpdated(Connect4Game game)
|
||||
{
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor();
|
||||
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor();
|
||||
|
||||
|
||||
if (msg is null)
|
||||
@@ -198,7 +180,7 @@ public partial class Gambling
|
||||
|
||||
for (var i = 0; i < Connect4Game.NUMBER_OF_COLUMNS; i++)
|
||||
sb.Append(_numbers[i]);
|
||||
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,6 @@ using System.Text;
|
||||
using NadekoBot.Modules.Gambling.Rps;
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Modules.Patronage;
|
||||
using NadekoBot.Modules.Utility;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
@@ -31,8 +30,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||
private readonly IPatronageService _ps;
|
||||
|
||||
private IUserMessage rdMsg;
|
||||
|
||||
public Gambling(
|
||||
IGamblingService gs,
|
||||
DbService db,
|
||||
@@ -105,34 +102,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await Response().Embed(eb).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task Economy()
|
||||
{
|
||||
var ec = await _service.GetEconomyAsync();
|
||||
decimal onePercent = 0;
|
||||
|
||||
// This stops the top 1% from owning more than 100% of the money
|
||||
if (ec.Cash > 0)
|
||||
{
|
||||
onePercent = ec.OnePercent / (ec.Cash - ec.Bot);
|
||||
}
|
||||
|
||||
// [21:03] Bob Page: Kinda remids me of US economy
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithTitle(GetText(strs.economy_state))
|
||||
.AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
|
||||
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
|
||||
.AddField(GetText(strs.currency_planted), N(ec.Planted))
|
||||
.AddField(GetText(strs.owned_waifus_total), N(ec.Waifus))
|
||||
.AddField(GetText(strs.bot_currency), N(ec.Bot))
|
||||
.AddField(GetText(strs.bank_accounts), N(ec.Bank))
|
||||
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank))
|
||||
.WithOkColor();
|
||||
|
||||
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
|
||||
await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
|
||||
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
|
||||
{
|
||||
var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative);
|
||||
@@ -602,117 +571,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RollDuel(IUser u)
|
||||
{
|
||||
if (ctx.User.Id == u.Id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//since the challenge is created by another user, we need to reverse the ids
|
||||
//if it gets removed, means challenge is accepted
|
||||
if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game))
|
||||
{
|
||||
await game.StartGame();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RollDuel([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, IUser u)
|
||||
{
|
||||
if (ctx.User.Id == u.Id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.roll_duel));
|
||||
|
||||
var description = string.Empty;
|
||||
|
||||
var game = new RollDuelGame(_cs, _client.CurrentUser.Id, ctx.User.Id, u.Id, amount);
|
||||
//means challenge is just created
|
||||
if (_service.Duels.TryGetValue((ctx.User.Id, u.Id), out var other))
|
||||
{
|
||||
if (other.Amount != amount)
|
||||
{
|
||||
await Response().Error(strs.roll_duel_already_challenged).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await RollDuel(u);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_service.Duels.TryAdd((u.Id, ctx.User.Id), game))
|
||||
{
|
||||
game.OnGameTick += GameOnGameTick;
|
||||
game.OnEnded += GameOnEnded;
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.roll_duel_challenge(Format.Bold(ctx.User.ToString()),
|
||||
Format.Bold(u.ToString()),
|
||||
Format.Bold(N(amount))))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
async Task GameOnGameTick(RollDuelGame arg)
|
||||
{
|
||||
var rolls = arg.Rolls.Last();
|
||||
description += $@"{Format.Bold(ctx.User.ToString())} rolled **{rolls.Item1}**
|
||||
{Format.Bold(u.ToString())} rolled **{rolls.Item2}**
|
||||
--
|
||||
";
|
||||
embed = embed.WithDescription(description);
|
||||
|
||||
if (rdMsg is null)
|
||||
{
|
||||
rdMsg = await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await rdMsg.ModifyAsync(x => { x.Embed = embed.Build(); });
|
||||
}
|
||||
}
|
||||
|
||||
async Task GameOnEnded(RollDuelGame rdGame, RollDuelGame.Reason reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (reason == RollDuelGame.Reason.Normal)
|
||||
{
|
||||
var winner = rdGame.Winner == rdGame.P1 ? ctx.User : u;
|
||||
description += $"\n**{winner}** Won {N((long)(rdGame.Amount * 2 * 0.98))}";
|
||||
|
||||
embed = embed.WithDescription(description);
|
||||
|
||||
await rdMsg.ModifyAsync(x => x.Embed = embed.Build());
|
||||
}
|
||||
else if (reason == RollDuelGame.Reason.Timeout)
|
||||
{
|
||||
await Response().Error(strs.roll_duel_timeout).SendAsync();
|
||||
}
|
||||
else if (reason == RollDuelGame.Reason.NoFunds)
|
||||
{
|
||||
await Response().Error(strs.roll_duel_no_funds).SendAsync();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_service.Duels.TryRemove((u.Id, ctx.User.Id), out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task BetRoll([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
||||
{
|
||||
|
@@ -2,7 +2,6 @@
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Connect4;
|
||||
@@ -129,38 +128,6 @@ public class GamblingService : INService, IReadyExecutor
|
||||
|
||||
private static readonly TypedKey<EconomyResult> _ecoKey = new("nadeko:economy");
|
||||
|
||||
public async Task<EconomyResult> GetEconomyAsync()
|
||||
{
|
||||
var data = await _cache.GetOrAddAsync(_ecoKey,
|
||||
async () =>
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var cash = uow.Set<DiscordUser>().GetTotalCurrency();
|
||||
var onePercent = uow.Set<DiscordUser>().GetTopOnePercentCurrency(_client.CurrentUser.Id);
|
||||
decimal planted = uow.Set<PlantedCurrency>().AsQueryable().Sum(x => x.Amount);
|
||||
var waifus = uow.Set<WaifuInfo>().GetTotalValue();
|
||||
var bot = await uow.Set<DiscordUser>().GetUserCurrencyAsync(_client.CurrentUser.Id);
|
||||
decimal bank = await uow.GetTable<BankUser>()
|
||||
.SumAsyncLinqToDB(x => x.Balance);
|
||||
|
||||
var result = new EconomyResult
|
||||
{
|
||||
Cash = cash,
|
||||
Planted = planted,
|
||||
Bot = bot,
|
||||
Waifus = waifus,
|
||||
OnePercent = onePercent,
|
||||
Bank = bank
|
||||
};
|
||||
|
||||
return result;
|
||||
},
|
||||
TimeSpan.FromMinutes(3));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
private static readonly SemaphoreSlim _timelyLock = new(1, 1);
|
||||
|
||||
private static TypedKey<Dictionary<ulong, long>> _timelyKey
|
||||
|
@@ -1,7 +1,6 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.ImageSharp;
|
||||
@@ -32,7 +31,6 @@ public class PlantPickService : INService, IExecNoCommand
|
||||
|
||||
public PlantPickService(
|
||||
DbService db,
|
||||
CommandHandler cmd,
|
||||
IBotStrings strings,
|
||||
IImageCache images,
|
||||
FontProvider fonts,
|
||||
@@ -108,7 +106,6 @@ public class PlantPickService : INService, IExecNoCommand
|
||||
/// Get a random currency image stream, with an optional password sticked onto it.
|
||||
/// </summary>
|
||||
/// <param name="pass">Optional password to add to top left corner.</param>
|
||||
/// <param name="extension">Extension of the file, defaults to png</param>
|
||||
/// <returns>Stream of the currency image</returns>
|
||||
public async Task<(Stream, string)> GetRandomCurrencyImageAsync(string pass)
|
||||
{
|
||||
|
@@ -1,61 +0,0 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class CurrencyRaffleCommands : GamblingSubmodule<CurrencyRaffleService>
|
||||
{
|
||||
public enum Mixed { Mixed }
|
||||
|
||||
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService)
|
||||
: base(gamblingConfService)
|
||||
{
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task RaffleCur(Mixed _, [OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
||||
=> RaffleCur(amount, true);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task RaffleCur([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, bool mixed = false)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount))
|
||||
return;
|
||||
|
||||
async Task OnEnded(IUser arg, long won)
|
||||
{
|
||||
await Response()
|
||||
.Confirm(GetText(strs.rafflecur_ended(CurrencyName,
|
||||
Format.Bold(arg.ToString()),
|
||||
won + CurrencySign)))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
var res = await _service.JoinOrCreateGame(ctx.Channel.Id, ctx.User, amount, mixed, OnEnded);
|
||||
|
||||
if (res.Item1 is not null)
|
||||
{
|
||||
await Response()
|
||||
.Confirm(GetText(strs.rafflecur(res.Item1.GameType.ToString())),
|
||||
string.Join("\n", res.Item1.Users.Select(x => $"{x.DiscordUser} ({N(x.Amount)})")),
|
||||
footer: GetText(strs.rafflecur_joined(ctx.User.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (res.Item2 == CurrencyRaffleService.JoinErrorType.AlreadyJoinedOrInvalidAmount)
|
||||
await Response().Error(strs.rafflecur_already_joined).SendAsync();
|
||||
else if (res.Item2 == CurrencyRaffleService.JoinErrorType.NotEnoughCurrency)
|
||||
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class CurrencyRaffleGame
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
Mixed,
|
||||
Normal
|
||||
}
|
||||
|
||||
public IEnumerable<User> Users
|
||||
=> _users;
|
||||
|
||||
public Type GameType { get; }
|
||||
|
||||
private readonly HashSet<User> _users = new();
|
||||
|
||||
public CurrencyRaffleGame(Type type)
|
||||
=> GameType = type;
|
||||
|
||||
public bool AddUser(IUser usr, long amount)
|
||||
{
|
||||
// if game type is normal, and someone already joined the game
|
||||
// (that's the user who created it)
|
||||
if (GameType == Type.Normal && _users.Count > 0 && _users.First().Amount != amount)
|
||||
return false;
|
||||
|
||||
if (!_users.Add(new()
|
||||
{
|
||||
DiscordUser = usr,
|
||||
Amount = amount
|
||||
}))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public User GetWinner()
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
if (GameType == Type.Mixed)
|
||||
{
|
||||
var num = rng.NextLong(0L, Users.Sum(x => x.Amount));
|
||||
var sum = 0L;
|
||||
foreach (var u in Users)
|
||||
{
|
||||
sum += u.Amount;
|
||||
if (sum > num)
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
var usrs = _users.ToArray();
|
||||
return usrs[rng.Next(0, usrs.Length)];
|
||||
}
|
||||
|
||||
public class User
|
||||
{
|
||||
public IUser DiscordUser { get; set; }
|
||||
public long Amount { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
=> DiscordUser.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is User u ? u.DiscordUser == DiscordUser : false;
|
||||
}
|
||||
}
|
@@ -1,81 +0,0 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class CurrencyRaffleService : INService
|
||||
{
|
||||
public enum JoinErrorType
|
||||
{
|
||||
NotEnoughCurrency,
|
||||
AlreadyJoinedOrInvalidAmount
|
||||
}
|
||||
|
||||
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new();
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
public CurrencyRaffleService(ICurrencyService cs)
|
||||
=> _cs = cs;
|
||||
|
||||
public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame(
|
||||
ulong channelId,
|
||||
IUser user,
|
||||
long amount,
|
||||
bool mixed,
|
||||
Func<IUser, long, Task> onEnded)
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
var newGame = false;
|
||||
if (!Games.TryGetValue(channelId, out var crg))
|
||||
{
|
||||
newGame = true;
|
||||
crg = new(mixed ? CurrencyRaffleGame.Type.Mixed : CurrencyRaffleGame.Type.Normal);
|
||||
Games.Add(channelId, crg);
|
||||
}
|
||||
|
||||
//remove money, and stop the game if this
|
||||
// user created it and doesn't have the money
|
||||
if (!await _cs.RemoveAsync(user.Id, amount, new("raffle", "join")))
|
||||
{
|
||||
if (newGame)
|
||||
Games.Remove(channelId);
|
||||
return (null, JoinErrorType.NotEnoughCurrency);
|
||||
}
|
||||
|
||||
if (!crg.AddUser(user, amount))
|
||||
{
|
||||
await _cs.AddAsync(user.Id, amount, new("raffle", "refund"));
|
||||
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
|
||||
}
|
||||
|
||||
if (newGame)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(60000);
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
var winner = crg.GetWinner();
|
||||
var won = crg.Users.Sum(x => x.Amount);
|
||||
|
||||
await _cs.AddAsync(winner.DiscordUser.Id, won, new("raffle", "win"));
|
||||
Games.Remove(channelId, out _);
|
||||
_ = onEnded(winner.DiscordUser, won);
|
||||
}
|
||||
catch { }
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}
|
||||
|
||||
return (crg, null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Db.Models;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
@@ -7,9 +7,7 @@ using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using NadekoBot.Modules.Gambling;
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Modules.Utility;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
@@ -25,9 +23,6 @@ public partial class Gambling
|
||||
[Group]
|
||||
public partial class SlotCommands : GamblingSubmodule<IGamblingService>
|
||||
{
|
||||
private static decimal totalBet;
|
||||
private static decimal totalPaidOut;
|
||||
|
||||
private readonly IImageCache _images;
|
||||
private readonly FontProvider _fonts;
|
||||
private readonly DbService _db;
|
||||
@@ -71,17 +66,19 @@ public partial class Gambling
|
||||
|
||||
|
||||
var eb = _sender.CreateEmbed()
|
||||
.WithAuthor(ctx.User)
|
||||
.WithDescription(Format.Bold(text))
|
||||
.WithImageUrl($"attachment://result.png")
|
||||
.WithOkColor();
|
||||
.WithAuthor(ctx.User)
|
||||
.WithDescription(Format.Bold(text))
|
||||
.WithImageUrl($"attachment://result.png")
|
||||
.WithOkColor();
|
||||
|
||||
var bb = new ButtonBuilder(emote: Emoji.Parse("🔁"), customId: "slot:again", label: "Pull Again");
|
||||
var inter = _inter.Create(ctx.User.Id, bb, smc =>
|
||||
{
|
||||
smc.DeferAsync();
|
||||
return Slot(amount);
|
||||
});
|
||||
var inter = _inter.Create(ctx.User.Id,
|
||||
bb,
|
||||
smc =>
|
||||
{
|
||||
smc.DeferAsync();
|
||||
return Slot(amount);
|
||||
});
|
||||
|
||||
var msg = await ctx.Channel.SendFileAsync(imgStream,
|
||||
"result.png",
|
||||
@@ -161,12 +158,6 @@ public partial class Gambling
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
lock (_slotStatsLock)
|
||||
{
|
||||
totalBet += amount;
|
||||
totalPaidOut += result.Won;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -213,7 +204,7 @@ public partial class Gambling
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Origin = new(393, 480)
|
||||
Origin = new(393, 480)
|
||||
},
|
||||
ownedAmount.ToString(),
|
||||
fontColor));
|
||||
|
@@ -3,6 +3,7 @@ using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Waifu;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Db.Models;
|
||||
using System.Globalization;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
@@ -21,8 +22,8 @@ public partial class Gambling
|
||||
{
|
||||
var price = _service.GetResetPrice(ctx.User);
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithTitle(GetText(strs.waifu_reset_confirm))
|
||||
.WithDescription(GetText(strs.waifu_reset_price(Format.Bold(N(price)))));
|
||||
.WithTitle(GetText(strs.waifu_reset_confirm))
|
||||
.WithDescription(GetText(strs.waifu_reset_price(Format.Bold(N(price)))));
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed))
|
||||
return;
|
||||
@@ -152,12 +153,12 @@ public partial class Gambling
|
||||
await Response().Confirm(strs.waifu_divorced_notlike(N(amount))).SendAsync();
|
||||
else if (result == DivorceResult.NotYourWife)
|
||||
await Response().Error(strs.waifu_not_yours).SendAsync();
|
||||
else
|
||||
else if (remaining is { } rem)
|
||||
{
|
||||
await Response()
|
||||
.Error(strs.waifu_recent_divorce(
|
||||
Format.Bold(((int)remaining?.TotalHours).ToString()),
|
||||
Format.Bold(remaining?.Minutes.ToString())))
|
||||
Format.Bold(((int)rem.TotalHours).ToString()),
|
||||
Format.Bold(rem.Minutes.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
}
|
||||
@@ -237,7 +238,7 @@ public partial class Gambling
|
||||
private string GetLbString(WaifuLbResult w)
|
||||
{
|
||||
var claimer = "no one";
|
||||
var status = string.Empty;
|
||||
string status;
|
||||
|
||||
var waifuUsername = w.Username.TrimTo(20);
|
||||
var claimerUsername = w.Claimer?.TrimTo(20);
|
||||
@@ -307,24 +308,26 @@ public partial class Gambling
|
||||
fansStr = "-";
|
||||
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.waifu)
|
||||
+ " "
|
||||
+ (wi.FullName ?? name ?? targetId.ToString())
|
||||
+ " - \"the "
|
||||
+ _service.GetClaimTitle(wi.ClaimCount)
|
||||
+ "\"")
|
||||
.AddField(GetText(strs.price), N(wi.Price), true)
|
||||
.AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true)
|
||||
.AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true)
|
||||
.AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true)
|
||||
.AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
|
||||
.AddField("\u200B", "\u200B", true)
|
||||
.AddField(GetText(strs.fans(fansList.Count)), fansStr, true)
|
||||
.AddField($"Waifus ({wi.ClaimCount})",
|
||||
wi.ClaimCount == 0 ? nobody : claimsStr,
|
||||
true)
|
||||
.AddField(GetText(strs.gifts), itemsStr, true);
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.waifu)
|
||||
+ " "
|
||||
+ (wi.FullName ?? name ?? targetId.ToString())
|
||||
+ " - \"the "
|
||||
+ _service.GetClaimTitle(wi.ClaimCount)
|
||||
+ "\"")
|
||||
.AddField(GetText(strs.price), N(wi.Price), true)
|
||||
.AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true)
|
||||
.AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true)
|
||||
.AddField(GetText(strs.changes_of_heart),
|
||||
$"{wi.AffinityCount} - \"the {affInfo}\"",
|
||||
true)
|
||||
.AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
|
||||
.AddField("\u200B", "\u200B", true)
|
||||
.AddField(GetText(strs.fans(fansList.Count)), fansStr, true)
|
||||
.AddField($"Waifus ({wi.ClaimCount})",
|
||||
wi.ClaimCount == 0 ? nobody : claimsStr,
|
||||
true)
|
||||
.AddField(GetText(strs.gifts), itemsStr, true);
|
||||
|
||||
await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
@@ -348,7 +351,7 @@ public partial class Gambling
|
||||
.Page((items, _) =>
|
||||
{
|
||||
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.waifu_gift_shop)).WithOkColor();
|
||||
|
||||
|
||||
items
|
||||
.ToList()
|
||||
.ForEach(x => embed.AddField(
|
||||
@@ -364,30 +367,28 @@ public partial class Gambling
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task WaifuGift(string itemName, [Leftover] IUser waifu)
|
||||
public async Task WaifuGift(MultipleWaifuItems items, [Leftover] IUser waifu)
|
||||
{
|
||||
if (waifu.Id == ctx.User.Id)
|
||||
return;
|
||||
|
||||
var allItems = _service.GetWaifuItems();
|
||||
var item = allItems.FirstOrDefault(x => x.Name.ToLowerInvariant() == itemName.ToLowerInvariant());
|
||||
if (item is null)
|
||||
{
|
||||
await Response().Error(strs.waifu_gift_not_exist).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, item);
|
||||
var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, items.Item, items.Count);
|
||||
|
||||
if (sucess)
|
||||
{
|
||||
await Response()
|
||||
.Confirm(strs.waifu_gift(Format.Bold(item + " " + item.ItemEmoji),
|
||||
.Confirm(strs.waifu_gift(
|
||||
Format.Bold($"{GetCountString(items)}{items.Item} {items.Item.ItemEmoji}"),
|
||||
Format.Bold(waifu.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
||||
}
|
||||
|
||||
private static string GetCountString(MultipleWaifuItems items)
|
||||
=> items.Count > 1
|
||||
? $"{items.Count}x "
|
||||
: string.Empty;
|
||||
}
|
||||
}
|
@@ -3,7 +3,6 @@ using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Waifu;
|
||||
@@ -89,9 +88,14 @@ public class WaifuService : INService, IReadyExecutor
|
||||
if (waifu is null)
|
||||
return settings.Waifu.MinPrice;
|
||||
|
||||
var divorces = uow.Set<WaifuUpdate>().Count(x
|
||||
=> x.Old != null && x.Old.UserId == user.Id && x.UpdateType == WaifuUpdateType.Claimed && x.New == null);
|
||||
var affs = uow.Set<WaifuUpdate>().AsQueryable()
|
||||
var divorces = uow.Set<WaifuUpdate>()
|
||||
.Count(x
|
||||
=> x.Old != null
|
||||
&& x.Old.UserId == user.Id
|
||||
&& x.UpdateType == WaifuUpdateType.Claimed
|
||||
&& x.New == null);
|
||||
var affs = uow.Set<WaifuUpdate>()
|
||||
.AsQueryable()
|
||||
.Where(w => w.User.UserId == user.Id
|
||||
&& w.UpdateType == WaifuUpdateType.AffinityChanged
|
||||
&& w.New != null)
|
||||
@@ -110,12 +114,14 @@ public class WaifuService : INService, IReadyExecutor
|
||||
if (!await _cs.RemoveAsync(user.Id, price, new("waifu", "reset")))
|
||||
return false;
|
||||
|
||||
var affs = uow.Set<WaifuUpdate>().AsQueryable()
|
||||
var affs = uow.Set<WaifuUpdate>()
|
||||
.AsQueryable()
|
||||
.Where(w => w.User.UserId == user.Id
|
||||
&& w.UpdateType == WaifuUpdateType.AffinityChanged
|
||||
&& w.New != null);
|
||||
|
||||
var divorces = uow.Set<WaifuUpdate>().AsQueryable()
|
||||
var divorces = uow.Set<WaifuUpdate>()
|
||||
.AsQueryable()
|
||||
.Where(x => x.Old != null
|
||||
&& x.Old.UserId == user.Id
|
||||
&& x.UpdateType == WaifuUpdateType.Claimed
|
||||
@@ -158,20 +164,22 @@ public class WaifuService : INService, IReadyExecutor
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
else
|
||||
{
|
||||
uow.Set<WaifuInfo>().Add(w = new()
|
||||
{
|
||||
Waifu = waifu,
|
||||
Claimer = claimer,
|
||||
Affinity = null,
|
||||
Price = amount
|
||||
});
|
||||
uow.Set<WaifuUpdate>().Add(new()
|
||||
{
|
||||
User = waifu,
|
||||
Old = null,
|
||||
New = claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
uow.Set<WaifuInfo>()
|
||||
.Add(w = new()
|
||||
{
|
||||
Waifu = waifu,
|
||||
Claimer = claimer,
|
||||
Affinity = null,
|
||||
Price = amount
|
||||
});
|
||||
uow.Set<WaifuUpdate>()
|
||||
.Add(new()
|
||||
{
|
||||
User = waifu,
|
||||
Old = null,
|
||||
New = claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
result = WaifuClaimResult.Success;
|
||||
}
|
||||
}
|
||||
@@ -186,13 +194,14 @@ public class WaifuService : INService, IReadyExecutor
|
||||
w.Price = amount + (amount / 4);
|
||||
result = WaifuClaimResult.Success;
|
||||
|
||||
uow.Set<WaifuUpdate>().Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
uow.Set<WaifuUpdate>()
|
||||
.Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity
|
||||
@@ -206,13 +215,14 @@ public class WaifuService : INService, IReadyExecutor
|
||||
w.Price = amount;
|
||||
result = WaifuClaimResult.Success;
|
||||
|
||||
uow.Set<WaifuUpdate>().Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
uow.Set<WaifuUpdate>()
|
||||
.Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -241,29 +251,31 @@ public class WaifuService : INService, IReadyExecutor
|
||||
|
||||
remaining = await _cache.GetRatelimitAsync(GetAffinityKey(user.Id),
|
||||
30.Minutes());
|
||||
|
||||
|
||||
if (remaining is not null)
|
||||
{
|
||||
}
|
||||
else if (w is null)
|
||||
{
|
||||
var thisUser = uow.GetOrCreateUser(user);
|
||||
uow.Set<WaifuInfo>().Add(new()
|
||||
{
|
||||
Affinity = newAff,
|
||||
Waifu = thisUser,
|
||||
Price = 1,
|
||||
Claimer = null
|
||||
});
|
||||
uow.Set<WaifuInfo>()
|
||||
.Add(new()
|
||||
{
|
||||
Affinity = newAff,
|
||||
Waifu = thisUser,
|
||||
Price = 1,
|
||||
Claimer = null
|
||||
});
|
||||
success = true;
|
||||
|
||||
uow.Set<WaifuUpdate>().Add(new()
|
||||
{
|
||||
User = thisUser,
|
||||
Old = null,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
uow.Set<WaifuUpdate>()
|
||||
.Add(new()
|
||||
{
|
||||
User = thisUser,
|
||||
Old = null,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -272,13 +284,14 @@ public class WaifuService : INService, IReadyExecutor
|
||||
w.Affinity = newAff;
|
||||
success = true;
|
||||
|
||||
uow.Set<WaifuUpdate>().Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldAff,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
uow.Set<WaifuUpdate>()
|
||||
.Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldAff,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
@@ -301,10 +314,10 @@ public class WaifuService : INService, IReadyExecutor
|
||||
|
||||
private static TypedKey<long> GetDivorceKey(ulong userId)
|
||||
=> new($"waifu:divorce_cd:{userId}");
|
||||
|
||||
|
||||
private static TypedKey<long> GetAffinityKey(ulong userId)
|
||||
=> new($"waifu:affinity:{userId}");
|
||||
|
||||
|
||||
public async Task<(WaifuInfo, DivorceResult, long, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId)
|
||||
{
|
||||
DivorceResult result;
|
||||
@@ -343,13 +356,14 @@ public class WaifuService : INService, IReadyExecutor
|
||||
var oldClaimer = w.Claimer;
|
||||
w.Claimer = null;
|
||||
|
||||
uow.Set<WaifuUpdate>().Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = null,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
uow.Set<WaifuUpdate>()
|
||||
.Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = null,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
@@ -358,40 +372,54 @@ public class WaifuService : INService, IReadyExecutor
|
||||
return (w, result, amount, remaining);
|
||||
}
|
||||
|
||||
public async Task<bool> GiftWaifuAsync(IUser from, IUser giftedWaifu, WaifuItemModel itemObj)
|
||||
public async Task<bool> GiftWaifuAsync(
|
||||
IUser from,
|
||||
IUser giftedWaifu,
|
||||
WaifuItemModel itemObj,
|
||||
int count)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(from, itemObj.Price, new("waifu", "item")))
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(count, 1, nameof(count));
|
||||
|
||||
if (!await _cs.RemoveAsync(from, itemObj.Price * count, new("waifu", "item")))
|
||||
return false;
|
||||
|
||||
var totalValue = itemObj.Price * count;
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var w = uow.Set<WaifuInfo>().ByWaifuUserId(giftedWaifu.Id, set => set.Include(x => x.Items).Include(x => x.Claimer));
|
||||
var w = uow.Set<WaifuInfo>()
|
||||
.ByWaifuUserId(giftedWaifu.Id,
|
||||
set => set
|
||||
.Include(x => x.Items)
|
||||
.Include(x => x.Claimer));
|
||||
if (w is null)
|
||||
{
|
||||
uow.Set<WaifuInfo>().Add(w = new()
|
||||
{
|
||||
Affinity = null,
|
||||
Claimer = null,
|
||||
Price = 1,
|
||||
Waifu = uow.GetOrCreateUser(giftedWaifu)
|
||||
});
|
||||
uow.Set<WaifuInfo>()
|
||||
.Add(w = new()
|
||||
{
|
||||
Affinity = null,
|
||||
Claimer = null,
|
||||
Price = 1,
|
||||
Waifu = uow.GetOrCreateUser(giftedWaifu)
|
||||
});
|
||||
}
|
||||
|
||||
if (!itemObj.Negative)
|
||||
{
|
||||
w.Items.Add(new()
|
||||
{
|
||||
Name = itemObj.Name.ToLowerInvariant(),
|
||||
ItemEmoji = itemObj.ItemEmoji
|
||||
});
|
||||
w.Items.AddRange(Enumerable.Range(0, count)
|
||||
.Select((_) => new WaifuItem()
|
||||
{
|
||||
Name = itemObj.Name.ToLowerInvariant(),
|
||||
ItemEmoji = itemObj.ItemEmoji
|
||||
}));
|
||||
|
||||
if (w.Claimer?.UserId == from.Id)
|
||||
w.Price += (long)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
|
||||
w.Price += (long)(totalValue * _gss.Data.Waifu.Multipliers.GiftEffect);
|
||||
else
|
||||
w.Price += itemObj.Price / 2;
|
||||
w.Price += totalValue / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
w.Price -= (long)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
|
||||
w.Price -= (long)(totalValue * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
|
||||
if (w.Price < 1)
|
||||
w.Price = 1;
|
||||
}
|
||||
@@ -492,6 +520,7 @@ public class WaifuService : INService, IReadyExecutor
|
||||
}
|
||||
|
||||
private static readonly TypedKey<long> _waifuDecayKey = $"waifu:last_decay";
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
// only decay waifu values from shard 0
|
||||
@@ -513,7 +542,7 @@ public class WaifuService : INService, IReadyExecutor
|
||||
var nowB = now.ToBinary();
|
||||
|
||||
var result = await _cache.GetAsync(_waifuDecayKey);
|
||||
|
||||
|
||||
if (result.TryGetValue(out var val))
|
||||
{
|
||||
var lastDecay = DateTime.FromBinary(val);
|
||||
@@ -533,7 +562,6 @@ public class WaifuService : INService, IReadyExecutor
|
||||
{
|
||||
Price = (long)(old.Price * multi)
|
||||
});
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -550,33 +578,35 @@ public class WaifuService : INService, IReadyExecutor
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<DiscordUser>()
|
||||
.Where(x => ctx.GetTable<WaifuInfo>()
|
||||
.Where(wi => wi.ClaimerId == waifuId)
|
||||
.Select(wi => wi.WaifuId)
|
||||
.Contains(x.Id))
|
||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
||||
.ToListAsyncEF();
|
||||
.Where(x => ctx.GetTable<WaifuInfo>()
|
||||
.Where(wi => wi.ClaimerId == waifuId)
|
||||
.Select(wi => wi.WaifuId)
|
||||
.Contains(x.Id))
|
||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
||||
.ToListAsyncEF();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<string>> GetFansNames(int waifuId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<DiscordUser>()
|
||||
.Where(x => ctx.GetTable<WaifuInfo>()
|
||||
.Where(wi => wi.AffinityId == waifuId)
|
||||
.Select(wi => wi.WaifuId)
|
||||
.Contains(x.Id))
|
||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
||||
.ToListAsyncEF();
|
||||
.Where(x => ctx.GetTable<WaifuInfo>()
|
||||
.Where(wi => wi.AffinityId == waifuId)
|
||||
.Select(wi => wi.WaifuId)
|
||||
.Contains(x.Id))
|
||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
||||
.ToListAsyncEF();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<WaifuItem>> GetItems(int waifuId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<WaifuItem>()
|
||||
.Where(x => x.WaifuInfoId == ctx.GetTable<WaifuInfo>()
|
||||
.Where(x => x.WaifuId == waifuId)
|
||||
.Select(x => x.Id)
|
||||
.FirstOrDefault())
|
||||
.ToListAsyncEF();
|
||||
.Where(x => x.WaifuInfoId
|
||||
== ctx.GetTable<WaifuInfo>()
|
||||
.Where(x => x.WaifuId == waifuId)
|
||||
.Select(x => x.Id)
|
||||
.FirstOrDefault())
|
||||
.ToListAsyncEF();
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public record class MultipleWaifuItems(int Count, WaifuItemModel Item);
|
@@ -0,0 +1,47 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class MultipleWaifuItemsTypeReader : NadekoTypeReader<MultipleWaifuItems>
|
||||
{
|
||||
private readonly WaifuService _service;
|
||||
|
||||
[GeneratedRegex(@"(?:(?<count>\d+)[x*])?(?<item>.+)")]
|
||||
private static partial Regex ItemRegex();
|
||||
|
||||
public MultipleWaifuItemsTypeReader(WaifuService service)
|
||||
{
|
||||
_service = service;
|
||||
}
|
||||
public override ValueTask<TypeReaderResult<MultipleWaifuItems>> ReadAsync(ICommandContext ctx, string input)
|
||||
{
|
||||
input = input.ToLowerInvariant();
|
||||
var match = ItemRegex().Match(input);
|
||||
if (!match.Success)
|
||||
{
|
||||
return new(Discord.Commands.TypeReaderResult.FromError(CommandError.ParseFailed, "Invalid input."));
|
||||
}
|
||||
|
||||
var count = 1;
|
||||
if (match.Groups["count"].Success)
|
||||
{
|
||||
if (!int.TryParse(match.Groups["count"].Value, out count) || count < 1)
|
||||
{
|
||||
return new(Discord.Commands.TypeReaderResult.FromError(CommandError.ParseFailed, "Invalid count."));
|
||||
}
|
||||
}
|
||||
|
||||
var itemName = match.Groups["item"].Value?.ToLowerInvariant();
|
||||
var allItems = _service.GetWaifuItems();
|
||||
var item = allItems.FirstOrDefault(x => x.Name.ToLowerInvariant() == itemName);
|
||||
if (item is null)
|
||||
{
|
||||
return new(Discord.Commands.TypeReaderResult.FromError(CommandError.ParseFailed, "Waifu gift does not exist."));
|
||||
}
|
||||
|
||||
return new(Discord.Commands.TypeReaderResult.FromSuccess(new MultipleWaifuItems(count, item)));
|
||||
}
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling;
|
||||
using NadekoBot.Modules.Gambling.Betdraw;
|
||||
using NadekoBot.Modules.Gambling.Rps;
|
||||
using OneOf;
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling;
|
||||
using NadekoBot.Modules.Gambling.Betdraw;
|
||||
using NadekoBot.Modules.Gambling.Rps;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NCalc;
|
||||
|
@@ -1,7 +1,5 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Games.Services;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Games;
|
||||
|
||||
@@ -18,32 +16,20 @@ public partial class Games
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[NoPublicBot]
|
||||
public async Task CleverBot()
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
if (_service.ChatterBotGuilds.TryRemove(channel.Guild.Id, out _))
|
||||
{
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
uow.Set<GuildConfig>().SetCleverbotEnabled(ctx.Guild.Id, false);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
var newState = await _service.ToggleChatterBotAsync(ctx.Guild.Id);
|
||||
|
||||
if (!newState)
|
||||
{
|
||||
await Response().Confirm(strs.chatbot_disabled).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
_service.ChatterBotGuilds.TryAdd(channel.Guild.Id, new(() => _service.CreateSession(), true));
|
||||
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
uow.Set<GuildConfig>().SetCleverbotEnabled(ctx.Guild.Id, true);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await Response().Confirm(strs.chatbot_enabled).SendAsync();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Games.Common;
|
||||
@@ -10,7 +12,7 @@ namespace NadekoBot.Modules.Games.Services;
|
||||
|
||||
public class ChatterBotService : IExecOnMessage
|
||||
{
|
||||
public ConcurrentDictionary<ulong, Lazy<IChatterBotSession>> ChatterBotGuilds { get; }
|
||||
private ConcurrentDictionary<ulong, Lazy<IChatterBotSession>> ChatterBotGuilds { get; }
|
||||
|
||||
public int Priority
|
||||
=> 1;
|
||||
@@ -21,6 +23,7 @@ public class ChatterBotService : IExecOnMessage
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly GamesConfigService _gcs;
|
||||
private readonly IMessageSenderService _sender;
|
||||
private readonly DbService _db;
|
||||
public readonly IPatronageService _ps;
|
||||
|
||||
public ChatterBotService(
|
||||
@@ -31,12 +34,14 @@ public class ChatterBotService : IExecOnMessage
|
||||
IHttpClientFactory factory,
|
||||
IBotCredentials creds,
|
||||
GamesConfigService gcs,
|
||||
IMessageSenderService sender)
|
||||
IMessageSenderService sender,
|
||||
DbService db)
|
||||
{
|
||||
_client = client;
|
||||
_perms = perms;
|
||||
_creds = creds;
|
||||
_sender = sender;
|
||||
_db = db;
|
||||
_httpFactory = factory;
|
||||
_perms = perms;
|
||||
_gcs = gcs;
|
||||
@@ -58,18 +63,21 @@ public class ChatterBotService : IExecOnMessage
|
||||
|
||||
Log.Information("Cleverbot will not work as the api key is missing");
|
||||
return null;
|
||||
case ChatBotImplementation.Gpt:
|
||||
case ChatBotImplementation.OpenAi:
|
||||
var data = _gcs.Data;
|
||||
if (!string.IsNullOrWhiteSpace(_creds.Gpt3ApiKey))
|
||||
return new OfficialGptSession(_creds.Gpt3ApiKey,
|
||||
_gcs.Data.ChatGpt.ModelName,
|
||||
_gcs.Data.ChatGpt.ChatHistory,
|
||||
_gcs.Data.ChatGpt.MaxTokens,
|
||||
_gcs.Data.ChatGpt.MinTokens,
|
||||
_gcs.Data.ChatGpt.PersonalityPrompt,
|
||||
return new OpenAiApiSession(
|
||||
data.ChatGpt.ApiUrl,
|
||||
_creds.Gpt3ApiKey,
|
||||
data.ChatGpt.ModelName,
|
||||
data.ChatGpt.ChatHistory,
|
||||
data.ChatGpt.MaxTokens,
|
||||
data.ChatGpt.MinTokens,
|
||||
data.ChatGpt.PersonalityPrompt,
|
||||
_client.CurrentUser.Username,
|
||||
_httpFactory);
|
||||
|
||||
Log.Information("Gpt3 will not work as the api key is missing");
|
||||
Log.Information("Openai Api will likely not work as the api key is missing");
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
@@ -96,6 +104,8 @@ public class ChatterBotService : IExecOnMessage
|
||||
message = msg.Content[normalMention.Length..].Trim();
|
||||
else if (msg.Content.StartsWith(nickMention, StringComparison.InvariantCulture))
|
||||
message = msg.Content[nickMention.Length..].Trim();
|
||||
else if (msg.ReferencedMessage?.Author.Id == nadekoId)
|
||||
message = msg.Content;
|
||||
else
|
||||
return null;
|
||||
|
||||
@@ -192,4 +202,38 @@ public class ChatterBotService : IExecOnMessage
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleChatterBotAsync(ulong guildId)
|
||||
{
|
||||
if (ChatterBotGuilds.TryRemove(guildId, out _))
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.Set<GuildConfig>()
|
||||
.ToLinqToDBTable()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.UpdateAsync((gc) => new GuildConfig()
|
||||
{
|
||||
CleverbotEnabled = false
|
||||
});
|
||||
await uow.SaveChangesAsync();
|
||||
return false;
|
||||
}
|
||||
|
||||
ChatterBotGuilds.TryAdd(guildId, new(() => CreateSession(), true));
|
||||
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
await uow.Set<GuildConfig>()
|
||||
.ToLinqToDBTable()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.UpdateAsync((gc) => new GuildConfig()
|
||||
{
|
||||
CleverbotEnabled = true
|
||||
});
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
9
src/NadekoBot/Modules/Games/ChatterBot/_common/Choice.cs
Normal file
9
src/NadekoBot/Modules/Games/ChatterBot/_common/Choice.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Common.ChatterBot;
|
||||
|
||||
public class Choice
|
||||
{
|
||||
[JsonPropertyName("message")]
|
||||
public required Message Message { get; init; }
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
#nullable disable
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Common.ChatterBot;
|
||||
|
||||
public class OpenAiCompletionResponse
|
||||
{
|
||||
[JsonPropertyName("choices")]
|
||||
public Choice[] Choices { get; set; }
|
||||
|
||||
[JsonPropertyName("usage")]
|
||||
public OpenAiUsageData Usage { get; set; }
|
||||
}
|
||||
|
||||
public class OpenAiUsageData
|
||||
{
|
||||
[JsonPropertyName("prompt_tokens")]
|
||||
public int PromptTokens { get; set; }
|
||||
|
||||
[JsonPropertyName("completion_tokens")]
|
||||
public int CompletionTokens { get; set; }
|
||||
|
||||
[JsonPropertyName("total_tokens")]
|
||||
public int TotalTokens { get; set; }
|
||||
}
|
||||
|
||||
public class Choice
|
||||
{
|
||||
[JsonPropertyName("message")]
|
||||
public Message Message { get; init; }
|
||||
}
|
||||
|
||||
public class Message {
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; init; }
|
||||
}
|
||||
|
||||
public class Gpt3ApiRequest
|
||||
{
|
||||
[JsonPropertyName("model")]
|
||||
public string Model { get; init; }
|
||||
|
||||
[JsonPropertyName("messages")]
|
||||
public List<GPTMessage> Messages { get; init; }
|
||||
|
||||
[JsonPropertyName("temperature")]
|
||||
public int Temperature { get; init; }
|
||||
|
||||
[JsonPropertyName("max_tokens")]
|
||||
public int MaxTokens { get; init; }
|
||||
}
|
||||
|
||||
public class GPTMessage
|
||||
{
|
||||
[JsonPropertyName("role")]
|
||||
public string Role {get; init;}
|
||||
[JsonPropertyName("content")]
|
||||
public string Content {get; init;}
|
||||
[JsonPropertyName("name")]
|
||||
public string Name {get; init;}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user