Compare commits

...

39 Commits

Author SHA1 Message Date
Kwoth
e49e3eec69 Version upped to 4.3.10, CHANGELOG.md updated 2022-11-10 22:42:34 +01:00
Kwoth
3992ae392b Fixed nullref in xploop 2022-11-10 22:32:27 +01:00
Kwoth
8f0c5fab47 Fixed nullref in cmdcd 2022-11-08 07:51:29 +01:00
Kwoth
780a260b88 If cleverbot is disabled through permissions, let other things execute 2022-11-08 07:51:20 +01:00
Kwoth
25692b9585 Fixed .feedadd for real 2022-11-04 17:21:05 +01:00
Kwoth
ed3ce52865 Fixed .feedadd, closes #388 2022-11-04 05:08:22 +01:00
Kwoth
f5f0f1e250 Fixed .prune @Target not working bug 2022-10-25 02:07:05 +02:00
Kwoth
9d9e61fdfb Fixed command cooldown calculation. Closes #387 2022-10-25 01:57:58 +02:00
Kwoth
e68e948a80 Aliases now support %target% placeholder. For example 'alias .bft .bf %target% t' 2022-10-22 23:33:42 +02:00
Kwoth
cb98f4aa15 Added .exprtoggleglobal / .extg which can be used to toggle usage of global expressions on the server 2022-10-21 21:31:59 +02:00
Kwoth
bfec0cbcbf OwnerId will be autofilled in creds.yml if it's missing at startup 2022-10-19 23:43:44 +02:00
Kwoth
3e1268f3bb You can now specify time+date (time is optional) in remind instead of relative time, in the format HH:mm dd.MM.YYYY 2022-10-18 23:31:34 +02:00
Kwoth
c28f458972 Updated help text for .antispam and .antiraid 2022-10-18 01:45:00 +02:00
Kwoth
27ac948463 Added .forwardtochannel which will forward messages to the current channel. It has lower priority than fwtoall 2022-10-17 22:20:48 +02:00
Kwoth
3f9a3c4c18 Merge branch 'hokutochen-v4-patch-12507' into 'v4'
upped medusa version and updated brew command

See merge request Kwoth/nadekobot!271
2022-10-17 11:11:37 +00:00
Hokuto Chen
9a5545a951 docs: upped medusa version and updated brew command 2022-10-17 11:11:37 +00:00
Kwoth
584193db18 Added .filterlist command 2022-10-16 22:57:36 +02:00
Kwoth
1a132fd234 Added a missing return 2022-10-15 13:17:30 +02:00
Kwoth
fd6a51ac82 Added appropriate error messages for every club related command. Removed obsolete error messages. 2022-10-15 13:13:31 +02:00
Kwoth
eb1fabb2b7 Prepared several more enums to clarify club related action results 2022-10-14 23:02:43 +02:00
Kwoth
d079e684bd Clubleave errors clarified. Moved enum results to their own files 2022-10-13 22:27:12 +02:00
Kwoth
bf817a1436 Medusa modules (sneks) should now inherit medusa description when listed in .mdls command 2022-10-12 23:29:48 +02:00
Kwoth
78f1624aaf .meload and .meunload are now case sensitive. Previously loaded medusae may need to be reloaded or data/medusae/medusa.yml may need to be edited manually 2022-10-12 20:59:04 +02:00
Kwoth
793a49fc64 Updated version to 4.3.9 2022-10-12 20:20:05 +02:00
Kwoth
8b6be656b3 Updated CHANGELOG.md 2022-10-12 20:19:33 +02:00
Kwoth
89a88304dc If version is not specified, Nadeko.Medusa will default to 5.0.0 to allow for full compatibility for from-source-selfhosters 2022-10-12 20:10:55 +02:00
Kwoth
a7fe9ae08f .betstats now looks uwu 2022-10-11 19:59:58 +02:00
Kwoth
0469705037 Clarified .feedadd errors 2022-10-11 19:14:16 +02:00
Kwoth
dc568fe0e2 Removed a duplicate string key 2022-10-11 18:29:53 +02:00
Kwoth
eb01bb6c08 Removed ambiguity in clubban and clubunban error messages 2022-10-11 18:21:45 +02:00
Kwoth
71a3539d0e Possible fix for Remind .timely button 2022-10-10 18:39:49 +02:00
Kwoth
c896a0cdb8 Added better error messages to .clubapply 2022-10-09 16:25:28 +02:00
Kwoth
8effe817ad Fixed a bug with postgresql and mysql migration folders as well as compilation warning related to it 2022-10-08 09:05:31 +02:00
Kwoth
eedf6998b6 Fixed a bug in cmdcd not modifying the database. Added total stats to .betstats 2022-10-07 11:01:26 +02:00
Kwoth
e6b7c31a72 Gambling Tracker will only track successful removes 2022-10-06 10:55:12 +02:00
Kwoth
2f77fd57b0 Improved .betstats formatting 2022-10-04 16:27:19 +02:00
Kwoth
fda3d92134 Added gambling stats migrations for postgres and mysql 2022-10-03 19:58:08 +02:00
Kwoth
fe6f28143b Updated changelog 2022-10-03 13:52:17 +02:00
Kwoth
df3909fc55 Added .betstats command, shows statistics for multiple gambling commands, .slotstats removed as it is obsolete 2022-10-03 13:49:52 +02:00
117 changed files with 14754 additions and 486 deletions

View File

@@ -104,7 +104,7 @@ publish-medusa-package:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
script: script:
- LAST_TAG=$(git describe --tags --abbrev=0) - LAST_TAG=$(git describe --tags --abbrev=0)
- if [ $CI_COMMIT_TAG ];then MEDUSA_VERSION="$CI_COMMIT_TAG"; else MEDUSA_VERSION="$LAST_TAG-$CI_COMMIT_SHA"; fi - if [ $CI_COMMIT_TAG ];then MEDUSA_VERSION="$CI_COMMIT_TAG"; else MEDUSA_VERSION="$LAST_TAG-$CI_COMMIT_SHORT_SHA"; fi
- cd src/Nadeko.Medusa/ - cd src/Nadeko.Medusa/
- dotnet pack -c Release /p:Version=$MEDUSA_VERSION -o bin/Release/packed - dotnet pack -c Release /p:Version=$MEDUSA_VERSION -o bin/Release/packed
- dotnet nuget push bin/Release/packed/ --source https://www.myget.org/F/nadeko/api/v2/package --api-key "$MYGET_API_KEY" - dotnet nuget push bin/Release/packed/ --source https://www.myget.org/F/nadeko/api/v2/package --api-key "$MYGET_API_KEY"

View File

@@ -2,7 +2,56 @@
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
## Unreleased ## [4.3.10] - 10.11.2022
### Added
- `.filterlist` / `.fl` command which lists link and invite filtering channels and status
- Added support for `%target%` placeholder in `.alias` command
- Added .forwardtochannel which will forward messages to the current channel. It has lower priority than fwtoall
- Added .exprtoggleglobal / .extg which can be used to toggle usage of global expressions on the server
### Changed
- .meload and .meunload are now case sensitive. Previously loaded medusae may need to be reloaded or data/medusae/medusa.yml may need to be edited manually
- Several club related command have their error messages improved
- Updated help text for .antispam and .antiraid
- You can now specify time and date (time is optional) in `.remind` command instead of relative time, in the format `HH:mm dd.MM.YYYY`
- OwnerId will be automatically added to `creds.yml` at bot startup if it's missing
### Fixed
- Fixed `.cmdcd` console error
- Fixed an error when currency is add per xp
- Fixed an issue preventing execution of expressions starting with @Bot when cleverbot is enabled on the server
- Fixed `.feedadd`
- Fixed `.prune @target` not working
- Medusa modules (sneks) should now inherit medusa description when listed in .mdls command
- Fixed command cooldown calculation
## [4.3.9] - 12.10.2022
### Added
- `.betstats` shows sum of all bets, payouts and the payout rate in %. Updates once an hour
### Changed
- `.betstats` looks way better (except on Mac)
- `.feedadd` errors clarified and separated in individual error messages for each issue.
- `.clubban` and `.clubunban` errors clarified and separated in individual error messages for each issue.
- `.clubapply` better error messages
### Fixed
- `.timely` 'Remind' button fixed in DMs
- `.cmdcd` database bugs fixed
- Fixed bugged mysql and postgresql migrations
- Fixed issues with lodaing medusae due to strict versioning
### Removed
- `.slotstats` Superseded by `.betstats`
## [4.3.8] - 02.10.2022 ## [4.3.8] - 02.10.2022

View File

@@ -7,7 +7,7 @@ Open Terminal (if you don't know how to, click on the magnifying glass on the to
###### Homebrew/wget ###### Homebrew/wget
*Skip this step if you already have homebrew installed* *Skip this step if you already have homebrew installed*
- Copy and paste this command, then press Enter: - Copy and paste this command, then press Enter:
- `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"` - `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
- Install wget - Install wget
- `brew install wget` - `brew install wget`

View File

@@ -155,7 +155,7 @@ This section will guide you through how to create a simple custom medusa. You ca
<ItemGroup> <ItemGroup>
<!-- Base medusa package. You MUST reference this in order to have a working medusa --> <!-- 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 --> <!-- Also, this package comes from MyGet, which requires you to have a NuGet.Config file next to your .csproj -->
<PackageReference Include="Nadeko.Medusa" Version="1.0.1"> <PackageReference Include="Nadeko.Medusa" Version="4.3.9">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View File

@@ -17,4 +17,7 @@
<PackageReference Include="YamlDotNet" Version="11.2.1" /> <PackageReference Include="YamlDotNet" Version="11.2.1" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Version)' == '' ">
<Version>5.0.0</Version>
</PropertyGroup>
</Project> </Project>

View File

@@ -313,10 +313,29 @@ public sealed class Bot
await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services); await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
// await _interactionService.AddModulesAsync(typeof(Bot).Assembly, Services); // await _interactionService.AddModulesAsync(typeof(Bot).Assembly, Services);
IsReady = true; IsReady = true;
await EnsureBotOwnershipAsync();
_ = Task.Run(ExecuteReadySubscriptions); _ = Task.Run(ExecuteReadySubscriptions);
Log.Information("Shard {ShardId} ready", Client.ShardId); Log.Information("Shard {ShardId} ready", Client.ShardId);
} }
private async ValueTask EnsureBotOwnershipAsync()
{
try
{
if (_creds.OwnerIds.Count != 0)
return;
Log.Information("Initializing Owner Id...");
var info = await Client.GetApplicationInfoAsync();
_credsProvider.ModifyCredsFile(x => x.OwnerIds = new[] { info.Owner.Id });
}
catch (Exception ex)
{
Log.Warning("Getting application info failed: {ErrorMessage}", ex.Message);
}
}
private Task ExecuteReadySubscriptions() private Task ExecuteReadySubscriptions()
{ {
var readyExecutors = Services.GetServices<IReadyExecutor>(); var readyExecutors = Services.GetServices<IReadyExecutor>();

View File

@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Configs;
public sealed partial class BotConfig : ICloneable<BotConfig> public sealed partial class BotConfig : ICloneable<BotConfig>
{ {
[Comment(@"DO NOT CHANGE")] [Comment(@"DO NOT CHANGE")]
public int Version { get; set; } = 4; public int Version { get; set; } = 5;
[Comment(@"Most commands, when executed, have a small colored line [Comment(@"Most commands, when executed, have a small colored line
next to the response. The color depends whether the command next to the response. The color depends whether the command
@@ -40,6 +40,10 @@ Allowed values: Simple, Normal, None")]
or all owners? (this might cause the bot to lag if there's a lot of owners specified)")] or all owners? (this might cause the bot to lag if there's a lot of owners specified)")]
public bool ForwardToAllOwners { get; set; } public bool ForwardToAllOwners { get; set; }
[Comment(@"Any messages sent by users in Bot's DM to be forwarded to the specified channel.
This option will only work when ForwardToAllOwners is set to false")]
public ulong? ForwardToChannel { get; set; }
[Comment(@"When a user DMs the bot with a message which is not a command [Comment(@"When a user DMs the bot with a message which is not a command
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot. they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.
Supports embeds. How it looks: https://puu.sh/B0BLV.png")] Supports embeds. How it looks: https://puu.sh/B0BLV.png")]

View File

@@ -186,7 +186,6 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
return MedusaLoadResult.AlreadyLoaded; return MedusaLoadResult.AlreadyLoaded;
var safeName = Uri.EscapeDataString(name); var safeName = Uri.EscapeDataString(name);
name = name.ToLowerInvariant();
await _lock.WaitAsync(); await _lock.WaitAsync();
try try
@@ -525,7 +524,6 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private async Task<MedusaUnloadResult> InternalUnloadAsync(string name) private async Task<MedusaUnloadResult> InternalUnloadAsync(string name)
{ {
name = name.ToLowerInvariant();
if (!_resolved.Remove(name, out var lsi)) if (!_resolved.Remove(name, out var lsi))
return MedusaUnloadResult.NotLoaded; return MedusaUnloadResult.NotLoaded;

View File

@@ -1,15 +1,14 @@
#nullable disable using CommandLine;
using CommandLine;
namespace NadekoBot.Common; namespace NadekoBot.Common;
public static class OptionsParser public static class OptionsParser
{ {
public static T ParseFrom<T>(string[] args) public static T ParseFrom<T>(string[]? args)
where T : INadekoCommandOptions, new() where T : INadekoCommandOptions, new()
=> ParseFrom(new T(), args).Item1; => ParseFrom(new T(), args).Item1;
public static (T, bool) ParseFrom<T>(T options, string[] args) public static (T, bool) ParseFrom<T>(T options, string[]? args)
where T : INadekoCommandOptions where T : INadekoCommandOptions
{ {
using var p = new Parser(x => using var p = new Parser(x =>

View File

@@ -0,0 +1,9 @@
#nullable disable
namespace NadekoBot.Services.Database.Models;
public class GamblingStats : DbEntity
{
public string Feature { get; set; }
public decimal Bet { get; set; }
public decimal PaidOut { get; set; }
}

View File

@@ -95,6 +95,8 @@ public class GuildConfig : DbEntity
public int WarnExpireHours { get; set; } public int WarnExpireHours { get; set; }
public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear; public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear;
public bool DisableGlobalExpressions { get; set; } = false;
#region Boost Message #region Boost Message
public bool SendBoostMessage { get; set; } public bool SendBoostMessage { get; set; }

View File

@@ -472,6 +472,14 @@ public abstract class NadekoContext : DbContext
.IsUnique()); .IsUnique());
#endregion #endregion
#region GamblingStats
modelBuilder.Entity<GamblingStats>(gs => gs
.HasIndex(x => x.Feature)
.IsUnique());
#endregion
} }
#if DEBUG #if DEBUG

View File

@@ -2,6 +2,7 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
@@ -10,15 +11,45 @@ using NadekoBot.Services.Database;
namespace NadekoBot.Migrations.Mysql namespace NadekoBot.Migrations.Mysql
{ {
[DbContext(typeof(MysqlContext))] [DbContext(typeof(MysqlContext))]
partial class MysqlContextModelSnapshot : ModelSnapshot [Migration("20221003175743_gambling-stats")]
partial class gamblingstats
{ {
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "6.0.7") .HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("NadekoBot.Db.Models.AutoPublishChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<ulong>("ChannelId")
.HasColumnType("bigint unsigned")
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("datetime(6)")
.HasColumnName("dateadded");
b.Property<ulong>("GuildId")
.HasColumnType("bigint unsigned")
.HasColumnName("guildid");
b.HasKey("Id")
.HasName("pk_autopublishchannel");
b.HasIndex("GuildId")
.IsUnique()
.HasDatabaseName("ix_autopublishchannel_guildid");
b.ToTable("autopublishchannel", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b => modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -1072,6 +1103,39 @@ namespace NadekoBot.Migrations.Mysql
b.ToTable("filterwordschannelid", (string)null); b.ToTable("filterwordschannelid", (string)null);
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<decimal>("Bet")
.HasColumnType("decimal(65,30)")
.HasColumnName("bet");
b.Property<DateTime?>("DateAdded")
.HasColumnType("datetime(6)")
.HasColumnName("dateadded");
b.Property<string>("Feature")
.HasColumnType("varchar(255)")
.HasColumnName("feature");
b.Property<decimal>("PaidOut")
.HasColumnType("decimal(65,30)")
.HasColumnName("paidout");
b.HasKey("Id")
.HasName("pk_gamblingstats");
b.HasIndex("Feature")
.IsUnique()
.HasDatabaseName("ix_gamblingstats_feature");
b.ToTable("gamblingstats", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -2236,6 +2300,10 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("rolename"); .HasColumnName("rolename");
b.Property<ulong?>("RoleRequirement")
.HasColumnType("bigint unsigned")
.HasColumnName("rolerequirement");
b.Property<int>("Type") b.Property<int>("Type")
.HasColumnType("int") .HasColumnType("int")
.HasColumnName("type"); .HasColumnName("type");

View File

@@ -0,0 +1,44 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class gamblingstats : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "gamblingstats",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
feature = table.Column<string>(type: "varchar(255)", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
bet = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
paidout = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_gamblingstats", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_gamblingstats_feature",
table: "gamblingstats",
column: "feature",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "gamblingstats");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class toggleglobalexpressions : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "disableglobalexpressions",
table: "guildconfigs",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "disableglobalexpressions",
table: "guildconfigs");
}
}
}

View File

@@ -1101,6 +1101,39 @@ namespace NadekoBot.Migrations.Mysql
b.ToTable("filterwordschannelid", (string)null); b.ToTable("filterwordschannelid", (string)null);
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<decimal>("Bet")
.HasColumnType("decimal(65,30)")
.HasColumnName("bet");
b.Property<DateTime?>("DateAdded")
.HasColumnType("datetime(6)")
.HasColumnName("dateadded");
b.Property<string>("Feature")
.HasColumnType("varchar(255)")
.HasColumnName("feature");
b.Property<decimal>("PaidOut")
.HasColumnType("decimal(65,30)")
.HasColumnName("paidout");
b.HasKey("Id")
.HasName("pk_gamblingstats");
b.HasIndex("Feature")
.IsUnique()
.HasDatabaseName("ix_gamblingstats_feature");
b.ToTable("gamblingstats", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -1225,6 +1258,10 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("tinyint(1)") .HasColumnType("tinyint(1)")
.HasColumnName("deletestreamonlinemessage"); .HasColumnName("deletestreamonlinemessage");
b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("tinyint(1)")
.HasColumnName("disableglobalexpressions");
b.Property<string>("DmGreetMessageText") b.Property<string>("DmGreetMessageText")
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("dmgreetmessagetext"); .HasColumnName("dmgreetmessagetext");

View File

@@ -2,6 +2,7 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
@@ -11,9 +12,10 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace NadekoBot.Migrations.PostgreSql namespace NadekoBot.Migrations.PostgreSql
{ {
[DbContext(typeof(PostgreSqlContext))] [DbContext(typeof(PostgreSqlContext))]
partial class PostgreSqlContextModelSnapshot : ModelSnapshot [Migration("20221003175752_gambling-stats")]
partial class gamblingstats
{ {
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
@@ -22,6 +24,37 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("NadekoBot.Db.Models.AutoPublishChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("ChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.HasKey("Id")
.HasName("pk_autopublishchannel");
b.HasIndex("GuildId")
.IsUnique()
.HasDatabaseName("ix_autopublishchannel_guildid");
b.ToTable("autopublishchannel", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b => modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -1126,6 +1159,41 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("filterwordschannelid", (string)null); b.ToTable("filterwordschannelid", (string)null);
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Bet")
.HasColumnType("numeric")
.HasColumnName("bet");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Feature")
.HasColumnType("text")
.HasColumnName("feature");
b.Property<decimal>("PaidOut")
.HasColumnType("numeric")
.HasColumnName("paidout");
b.HasKey("Id")
.HasName("pk_gamblingstats");
b.HasIndex("Feature")
.IsUnique()
.HasDatabaseName("ix_gamblingstats_feature");
b.ToTable("gamblingstats", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -2342,6 +2410,10 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("rolename"); .HasColumnName("rolename");
b.Property<decimal?>("RoleRequirement")
.HasColumnType("numeric(20,0)")
.HasColumnName("rolerequirement");
b.Property<int>("Type") b.Property<int>("Type")
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("type"); .HasColumnName("type");

View File

@@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class gamblingstats : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "gamblingstats",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
feature = table.Column<string>(type: "text", nullable: true),
bet = table.Column<decimal>(type: "numeric", nullable: false),
paidout = table.Column<decimal>(type: "numeric", nullable: false),
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_gamblingstats", x => x.id);
});
migrationBuilder.CreateIndex(
name: "ix_gamblingstats_feature",
table: "gamblingstats",
column: "feature",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "gamblingstats");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class toggleglobalexpressions : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "disableglobalexpressions",
table: "guildconfigs",
type: "boolean",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "disableglobalexpressions",
table: "guildconfigs");
}
}
}

View File

@@ -1157,6 +1157,41 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("filterwordschannelid", (string)null); b.ToTable("filterwordschannelid", (string)null);
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Bet")
.HasColumnType("numeric")
.HasColumnName("bet");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Feature")
.HasColumnType("text")
.HasColumnName("feature");
b.Property<decimal>("PaidOut")
.HasColumnType("numeric")
.HasColumnName("paidout");
b.HasKey("Id")
.HasName("pk_gamblingstats");
b.HasIndex("Feature")
.IsUnique()
.HasDatabaseName("ix_gamblingstats_feature");
b.ToTable("gamblingstats", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -1287,6 +1322,10 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("deletestreamonlinemessage"); .HasColumnName("deletestreamonlinemessage");
b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("boolean")
.HasColumnName("disableglobalexpressions");
b.Property<string>("DmGreetMessageText") b.Property<string>("DmGreetMessageText")
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("dmgreetmessagetext"); .HasColumnName("dmgreetmessagetext");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class gamblingstats : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "GamblingStats",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Feature = table.Column<string>(type: "TEXT", nullable: true),
Bet = table.Column<decimal>(type: "TEXT", nullable: false),
PaidOut = table.Column<decimal>(type: "TEXT", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_GamblingStats", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_GamblingStats_Feature",
table: "GamblingStats",
column: "Feature",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GamblingStats");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class toggleglobalexpressions : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "DisableGlobalExpressions",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DisableGlobalExpressions",
table: "GuildConfigs");
}
}
}

View File

@@ -864,6 +864,32 @@ namespace NadekoBot.Migrations
b.ToTable("FilterWordsChannelId"); b.ToTable("FilterWordsChannelId");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<decimal>("Bet")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<string>("Feature")
.HasColumnType("TEXT");
b.Property<decimal>("PaidOut")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Feature")
.IsUnique();
b.ToTable("GamblingStats");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -960,6 +986,9 @@ namespace NadekoBot.Migrations
b.Property<bool>("DeleteStreamOnlineMessage") b.Property<bool>("DeleteStreamOnlineMessage")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("INTEGER");
b.Property<string>("DmGreetMessageText") b.Property<string>("DmGreetMessageText")
.HasColumnType("TEXT"); .HasColumnType("TEXT");

View File

@@ -72,7 +72,7 @@ public partial class Administration
[BotPerm(ChannelPerm.ManageMessages)] [BotPerm(ChannelPerm.ManageMessages)]
[NadekoOptions(typeof(PruneOptions))] [NadekoOptions(typeof(PruneOptions))]
[Priority(0)] [Priority(0)]
public Task Prune(IGuildUser user, int count = 100, string args = null) public Task Prune(IGuildUser user, int count = 100, params string[] args)
=> Prune(user.Id, count, args); => Prune(user.Id, count, args);
//prune userid [x] //prune userid [x]

View File

@@ -230,6 +230,19 @@ public partial class Administration
await ReplyPendingLocalizedAsync(strs.fwall_stop); await ReplyPendingLocalizedAsync(strs.fwall_stop);
} }
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task ForwardToChannel()
{
var enabled = _service.ForwardToChannel(ctx.Channel.Id);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.fwch_start);
else
await ReplyPendingLocalizedAsync(strs.fwch_stop);
}
[Cmd] [Cmd]
public async Task ShardStats(int page = 1) public async Task ShardStats(int page = 1)
{ {

View File

@@ -85,12 +85,12 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
autoCommands = uow.AutoCommands.AsNoTracking() autoCommands = uow.AutoCommands.AsNoTracking()
.Where(x => x.Interval >= 5) .Where(x => x.Interval >= 5)
.AsEnumerable() .AsEnumerable()
.GroupBy(x => x.GuildId) .GroupBy(x => x.GuildId)
.ToDictionary(x => x.Key, .ToDictionary(x => x.Key,
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent()) y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
.ToConcurrent(); .ToConcurrent();
var startupCommands = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0); var startupCommands = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
foreach (var cmd in startupCommands) foreach (var cmd in startupCommands)
@@ -169,18 +169,18 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
private async Task LoadOwnerChannels() private async Task LoadOwnerChannels()
{ {
var channels = await _creds.OwnerIds.Select(id => var channels = await _creds.OwnerIds.Select(id =>
{ {
var user = _client.GetUser(id); var user = _client.GetUser(id);
if (user is null) if (user is null)
return Task.FromResult<IDMChannel>(null); return Task.FromResult<IDMChannel>(null);
return user.CreateDMChannelAsync(); return user.CreateDMChannelAsync();
}) })
.WhenAll(); .WhenAll();
ownerChannels = channels.Where(x => x is not null) ownerChannels = channels.Where(x => x is not null)
.ToDictionary(x => x.Recipient.Id, x => x) .ToDictionary(x => x.Recipient.Id, x => x)
.ToImmutableDictionary(); .ToImmutableDictionary();
if (!ownerChannels.Any()) if (!ownerChannels.Any())
{ {
@@ -202,7 +202,7 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg) public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
{ {
var bs = _bss.Data; var bs = _bss.Data;
if (msg.Channel is IDMChannel && bs.ForwardMessages && ownerChannels.Any()) if (msg.Channel is IDMChannel && bs.ForwardMessages && (ownerChannels.Any() || bs.ForwardToChannel is not null))
{ {
var title = _strings.GetText(strs.dm_from) + $" [{msg.Author}]({msg.Author.Id})"; var title = _strings.GetText(strs.dm_from) + $" [{msg.Author}]({msg.Author.Id})";
@@ -232,6 +232,18 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
} }
} }
} }
else if (bs.ForwardToChannel is ulong cid)
{
try
{
if (_client.GetChannel(cid) is ITextChannel ch)
await ch.SendConfirmAsync(_eb, title, toSend);
}
catch
{
Log.Warning("Error forwarding message to the channel");
}
}
else else
{ {
var firstOwnerChannel = ownerChannels.Values.First(); var firstOwnerChannel = ownerChannels.Values.First();
@@ -333,6 +345,20 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
return isToAll; return isToAll;
} }
public bool ForwardToChannel(ulong? channelId)
{
using var uow = _db.GetDbContext();
_bss.ModifyConfig(config =>
{
config.ForwardToChannel = channelId == config.ForwardToChannel
? null
: channelId;
});
return channelId is not null;
}
private void HandleStatusChanges() private void HandleStatusChanges()
=> _pubSub.Sub(_activitySetKey, => _pubSub.Sub(_activitySetKey,
async data => async data =>

View File

@@ -41,6 +41,17 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
message.Length > 1024 ? GetText(strs.redacted_too_long) : message)); message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
} }
[Cmd]
[UserPerm(GuildPerm.Administrator)]
public async Task ExprToggleGlobal()
{
var result = await _service.ToggleGlobalExpressionsAsync(ctx.Guild.Id);
if (result)
await ReplyConfirmLocalizedAsync(strs.expr_global_disabled);
else
await ReplyConfirmLocalizedAsync(strs.expr_global_enabled);
}
[Cmd] [Cmd]
[UserPerm(GuildPerm.Administrator)] [UserPerm(GuildPerm.Administrator)]
public async Task ExprAddServer(string key, [Leftover] string message) public async Task ExprAddServer(string key, [Leftover] string message)

View File

@@ -7,6 +7,7 @@ using NadekoBot.Modules.Permissions.Common;
using NadekoBot.Modules.Permissions.Services; using NadekoBot.Modules.Permissions.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using LinqToDB.EntityFrameworkCore;
using Nadeko.Common; using Nadeko.Common;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NamingConventions;
@@ -56,8 +57,8 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
// 1. expressions are almost never added (compared to how many times they are being looped through) // 1. expressions are almost never added (compared to how many times they are being looped through)
// 2. only need write locks for this as we'll rebuild+replace the array on every edit // 2. only need write locks for this as we'll rebuild+replace the array on every edit
// 3. there's never many of them (at most a thousand, usually < 100) // 3. there's never many of them (at most a thousand, usually < 100)
private NadekoExpression[] globalReactions; private NadekoExpression[] globalExpressions;
private ConcurrentDictionary<ulong, NadekoExpression[]> newGuildReactions; private ConcurrentDictionary<ulong, NadekoExpression[]> newguildExpressions;
private readonly DbService _db; private readonly DbService _db;
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
@@ -72,6 +73,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
private readonly Random _rng; private readonly Random _rng;
private bool ready; private bool ready;
private ConcurrentHashSet<ulong> _disabledGlobalExpressionGuilds;
public NadekoExpressionsService( public NadekoExpressionsService(
PermissionService perms, PermissionService perms,
@@ -113,7 +115,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
.Where(x => allGuildIds.Contains(x.GuildId.Value)) .Where(x => allGuildIds.Contains(x.GuildId.Value))
.ToListAsync(); .ToListAsync();
newGuildReactions = guildItems.GroupBy(k => k.GuildId!.Value) newguildExpressions = guildItems.GroupBy(k => k.GuildId!.Value)
.ToDictionary(g => g.Key, .ToDictionary(g => g.Key,
g => g.Select(x => g => g.Select(x =>
{ {
@@ -123,6 +125,11 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
.ToArray()) .ToArray())
.ToConcurrent(); .ToConcurrent();
_disabledGlobalExpressionGuilds = new (await uow.GuildConfigs
.Where(x => x.DisableGlobalExpressions)
.Select(x => x.GuildId)
.ToListAsyncLinqToDB());
lock (_gexprWriteLock) lock (_gexprWriteLock)
{ {
var globalItems = uow.Expressions.AsNoTracking() var globalItems = uow.Expressions.AsNoTracking()
@@ -135,7 +142,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
}) })
.ToArray(); .ToArray();
globalReactions = globalItems; globalExpressions = globalItems;
} }
ready = true; ready = true;
@@ -151,14 +158,17 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
var content = umsg.Content.Trim().ToLowerInvariant(); var content = umsg.Content.Trim().ToLowerInvariant();
if (newGuildReactions.TryGetValue(channel.Guild.Id, out var reactions) && reactions.Length > 0) if (newguildExpressions.TryGetValue(channel.Guild.Id, out var expressions) && expressions.Length > 0)
{ {
var expr = MatchExpressions(content, reactions); var expr = MatchExpressions(content, expressions);
if (expr is not null) if (expr is not null)
return expr; return expr;
} }
var localGrs = globalReactions; if (_disabledGlobalExpressionGuilds.Contains(channel.Guild.Id))
return null;
var localGrs = globalExpressions;
return MatchExpressions(content, localGrs); return MatchExpressions(content, localGrs);
} }
@@ -345,7 +355,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{ {
if (maybeGuildId is { } guildId) if (maybeGuildId is { } guildId)
{ {
newGuildReactions.AddOrUpdate(guildId, newguildExpressions.AddOrUpdate(guildId,
new[] { expr }, new[] { expr },
(_, old) => (_, old) =>
{ {
@@ -363,7 +373,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{ {
lock (_gexprWriteLock) lock (_gexprWriteLock)
{ {
var exprs = globalReactions; var exprs = globalExpressions;
for (var i = 0; i < exprs.Length; i++) for (var i = 0; i < exprs.Length; i++)
{ {
if (exprs[i].Id == expr.Id) if (exprs[i].Id == expr.Id)
@@ -379,7 +389,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
expr.Trigger = expr.Trigger.Replace(MENTION_PH, _client.CurrentUser.Mention); expr.Trigger = expr.Trigger.Replace(MENTION_PH, _client.CurrentUser.Mention);
if (maybeGuildId is { } guildId) if (maybeGuildId is { } guildId)
newGuildReactions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr)); newguildExpressions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr));
else else
return _pubSub.Pub(_gexprAddedKey, expr); return _pubSub.Pub(_gexprAddedKey, expr);
@@ -390,7 +400,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{ {
if (maybeGuildId is { } guildId) if (maybeGuildId is { } guildId)
{ {
newGuildReactions.AddOrUpdate(guildId, newguildExpressions.AddOrUpdate(guildId,
Array.Empty<NadekoExpression>(), Array.Empty<NadekoExpression>(),
(key, old) => DeleteInternal(old, id, out _)); (key, old) => DeleteInternal(old, id, out _));
@@ -399,7 +409,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
lock (_gexprWriteLock) lock (_gexprWriteLock)
{ {
var expr = Array.Find(globalReactions, item => item.Id == id); var expr = Array.Find(globalExpressions, item => item.Id == id);
if (expr is not null) if (expr is not null)
return _pubSub.Pub(_gexprDeletedkey, expr.Id); return _pubSub.Pub(_gexprDeletedkey, expr.Id);
} }
@@ -492,7 +502,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
var count = uow.Expressions.ClearFromGuild(guildId); var count = uow.Expressions.ClearFromGuild(guildId);
uow.SaveChanges(); uow.SaveChanges();
newGuildReactions.TryRemove(guildId, out _); newguildExpressions.TryRemove(guildId, out _);
return count; return count;
} }
@@ -562,10 +572,10 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{ {
lock (_gexprWriteLock) lock (_gexprWriteLock)
{ {
var newGlobalReactions = new NadekoExpression[globalReactions.Length + 1]; var newGlobalReactions = new NadekoExpression[globalExpressions.Length + 1];
Array.Copy(globalReactions, newGlobalReactions, globalReactions.Length); Array.Copy(globalExpressions, newGlobalReactions, globalExpressions.Length);
newGlobalReactions[globalReactions.Length] = c; newGlobalReactions[globalExpressions.Length] = c;
globalReactions = newGlobalReactions; globalExpressions = newGlobalReactions;
} }
return default; return default;
@@ -575,11 +585,11 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{ {
lock (_gexprWriteLock) lock (_gexprWriteLock)
{ {
for (var i = 0; i < globalReactions.Length; i++) for (var i = 0; i < globalExpressions.Length; i++)
{ {
if (globalReactions[i].Id == c.Id) if (globalExpressions[i].Id == c.Id)
{ {
globalReactions[i] = c; globalExpressions[i] = c;
return default; return default;
} }
} }
@@ -596,8 +606,8 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{ {
lock (_gexprWriteLock) lock (_gexprWriteLock)
{ {
var newGlobalReactions = DeleteInternal(globalReactions, id, out _); var newGlobalReactions = DeleteInternal(globalExpressions, id, out _);
globalReactions = newGlobalReactions; globalExpressions = newGlobalReactions;
} }
return default; return default;
@@ -612,7 +622,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
private Task OnLeftGuild(SocketGuild arg) private Task OnLeftGuild(SocketGuild arg)
{ {
newGuildReactions.TryRemove(arg.Id, out _); newguildExpressions.TryRemove(arg.Id, out _);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -622,7 +632,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var exprs = await uow.Expressions.AsNoTracking().Where(x => x.GuildId == gc.GuildId).ToArrayAsync(); var exprs = await uow.Expressions.AsNoTracking().Where(x => x.GuildId == gc.GuildId).ToArrayAsync();
newGuildReactions[gc.GuildId] = exprs; newguildExpressions[gc.GuildId] = exprs;
} }
#endregion #endregion
@@ -702,10 +712,25 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
public NadekoExpression[] GetExpressionsFor(ulong? maybeGuildId) public NadekoExpression[] GetExpressionsFor(ulong? maybeGuildId)
{ {
if (maybeGuildId is { } guildId) if (maybeGuildId is { } guildId)
return newGuildReactions.TryGetValue(guildId, out var exprs) ? exprs : Array.Empty<NadekoExpression>(); return newguildExpressions.TryGetValue(guildId, out var exprs) ? exprs : Array.Empty<NadekoExpression>();
return globalReactions; return globalExpressions;
} }
#endregion #endregion
public async Task<bool> ToggleGlobalExpressionsAsync(ulong guildId)
{
await using var ctx = _db.GetDbContext();
var gc = ctx.GuildConfigsForId(guildId, set => set);
var toReturn = gc.DisableGlobalExpressions = !gc.DisableGlobalExpressions;
await ctx.SaveChangesAsync();
if (toReturn)
_disabledGlobalExpressionGuilds.Add(guildId);
else
_disabledGlobalExpressionGuilds.TryRemove(guildId);
return toReturn;
}
} }

View File

@@ -29,6 +29,7 @@ public partial class Gambling : GamblingModule<GamblingService>
private readonly IBankService _bank; private readonly IBankService _bank;
private readonly IPatronageService _ps; private readonly IPatronageService _ps;
private readonly RemindService _remind; private readonly RemindService _remind;
private readonly GamblingTxTracker _gamblingTxTracker;
private IUserMessage rdMsg; private IUserMessage rdMsg;
@@ -41,7 +42,8 @@ public partial class Gambling : GamblingModule<GamblingService>
GamblingConfigService configService, GamblingConfigService configService,
IBankService bank, IBankService bank,
IPatronageService ps, IPatronageService ps,
RemindService remind) RemindService remind,
GamblingTxTracker gamblingTxTracker)
: base(configService) : base(configService)
{ {
_gs = gs; _gs = gs;
@@ -51,6 +53,7 @@ public partial class Gambling : GamblingModule<GamblingService>
_bank = bank; _bank = bank;
_ps = ps; _ps = ps;
_remind = remind; _remind = remind;
_gamblingTxTracker = gamblingTxTracker;
_enUsCulture = new CultureInfo("en-US", false).NumberFormat; _enUsCulture = new CultureInfo("en-US", false).NumberFormat;
_enUsCulture.NumberDecimalDigits = 0; _enUsCulture.NumberDecimalDigits = 0;
@@ -65,6 +68,43 @@ public partial class Gambling : GamblingModule<GamblingService>
return N(bal); return N(bal);
} }
[Cmd]
public async Task BetStats()
{
var stats = await _gamblingTxTracker.GetAllAsync();
var eb = _eb.Create(ctx)
.WithOkColor();
var str = "` Feature `` Bet ``Paid Out`` RoI `\n";
str += "――――――――――――――――――――\n";
foreach (var stat in stats)
{
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
str += $"`{stat.Feature.PadBoth(9)}`" +
$"`{stat.Bet.ToString("N0").PadLeft(8, '')}`" +
$"`{stat.PaidOut.ToString("N0").PadLeft(8, '')}`" +
$"`{perc.PadLeft(6, '')}`\n";
}
var bet = stats.Sum(x => x.Bet);
var paidOut = stats.Sum(x => x.PaidOut);
if (bet == 0)
bet = 1;
var tPerc = (paidOut / bet).ToString("P2", Culture);
str += "――――――――――――――――――――\n";
str += $"` {("TOTAL").PadBoth(7)}` " +
$"**{N(bet).PadLeft(8, '')}**" +
$"**{N(paidOut).PadLeft(8, '')}**" +
$"`{tPerc.PadLeft(6, '')}`";
eb.WithDescription(str);
await ctx.Channel.EmbedAsync(eb);
}
[Cmd] [Cmd]
public async Task Economy() public async Task Economy()
{ {
@@ -79,15 +119,15 @@ public partial class Gambling : GamblingModule<GamblingService>
// [21:03] Bob Page: Kinda remids me of US economy // [21:03] Bob Page: Kinda remids me of US economy
var embed = _eb.Create() var embed = _eb.Create()
.WithTitle(GetText(strs.economy_state)) .WithTitle(GetText(strs.economy_state))
.AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot)) .AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%") .AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
.AddField(GetText(strs.currency_planted), N(ec.Planted)) .AddField(GetText(strs.currency_planted), N(ec.Planted))
.AddField(GetText(strs.owned_waifus_total), N(ec.Waifus)) .AddField(GetText(strs.owned_waifus_total), N(ec.Waifus))
.AddField(GetText(strs.bot_currency), N(ec.Bot)) .AddField(GetText(strs.bot_currency), N(ec.Bot))
.AddField(GetText(strs.bank_accounts), N(ec.Bank)) .AddField(GetText(strs.bank_accounts), N(ec.Bank))
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank)) .AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank))
.WithOkColor(); .WithOkColor();
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table // ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
await ctx.Channel.EmbedAsync(embed); await ctx.Channel.EmbedAsync(embed);
@@ -105,7 +145,7 @@ public partial class Gambling : GamblingModule<GamblingService>
await _remind.AddReminderAsync(ctx.User.Id, await _remind.AddReminderAsync(ctx.User.Id,
ctx.User.Id, ctx.User.Id,
ctx.Guild.Id, ctx.Guild?.Id,
true, true,
when, when,
GetText(strs.timely_time)); GetText(strs.timely_time));
@@ -253,9 +293,9 @@ public partial class Gambling : GamblingModule<GamblingService>
} }
var embed = _eb.Create() var embed = _eb.Create()
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString() .WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
?? $"{userId}"))) ?? $"{userId}")))
.WithOkColor(); .WithOkColor();
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var tr in trs) foreach (var tr in trs)
@@ -292,8 +332,8 @@ public partial class Gambling : GamblingModule<GamblingService>
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var tr = await uow.CurrencyTransactions.ToLinqToDBTable() var tr = await uow.CurrencyTransactions.ToLinqToDBTable()
.Where(x => x.Id == intId && x.UserId == ctx.User.Id) .Where(x => x.Id == intId && x.UserId == ctx.User.Id)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (tr is null) if (tr is null)
{ {
@@ -354,9 +394,9 @@ public partial class Gambling : GamblingModule<GamblingService>
var balance = await _bank.GetBalanceAsync(ctx.User.Id); var balance = await _bank.GetBalanceAsync(ctx.User.Id);
await N(balance) await N(balance)
.Pipe(strs.bank_balance) .Pipe(strs.bank_balance)
.Pipe(GetText) .Pipe(GetText)
.Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true)); .Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true));
} }
private NadekoInteraction CreateCashInteraction() private NadekoInteraction CreateCashInteraction()
@@ -799,8 +839,8 @@ public partial class Gambling : GamblingModule<GamblingService>
} }
else if (result.Result == RpsResultType.Win) else if (result.Result == RpsResultType.Win)
{ {
if((long)result.Won > 0) if ((long)result.Won > 0)
embed.AddField(GetText(strs.won), N(amount.Value)); embed.AddField(GetText(strs.won), N(amount.Value));
msg = GetText(strs.rps_win(ctx.User.Mention, msg = GetText(strs.rps_win(ctx.User.Mention,
GetRpsPick(pick), GetRpsPick(pick),
@@ -863,99 +903,98 @@ public partial class Gambling : GamblingModule<GamblingService>
} }
public enum GambleTestTarget public enum GambleTestTarget
{
Slot,
Betroll,
Betflip,
BetflipT,
BetDraw,
BetDrawHL,
BetDrawRB,
Lula,
Rps,
}
[Cmd]
[OwnerOnly]
public async Task BetTest()
{
await SendConfirmAsync(GetText(strs.available_tests),
Enum.GetValues<GambleTestTarget>()
.Select(x => $"`{x}`")
.Join(", "));
}
[Cmd]
[OwnerOnly]
public async Task BetTest(GambleTestTarget target, int tests = 1000)
{
if (tests <= 0)
return;
await ctx.Channel.TriggerTypingAsync();
var streak = 0;
var maxW = 0;
var maxL = 0;
var dict = new Dictionary<decimal, int>();
for (var i = 0; i < tests; i++)
{ {
Slot, var multi = target switch
Betroll,
Betflip,
BetflipT,
BetDraw,
BetDrawHL,
BetDrawRB,
Lula,
Rps,
}
[Cmd]
[OwnerOnly]
public async Task BetTest()
{
await SendConfirmAsync(GetText(strs.available_tests),
Enum.GetValues<GambleTestTarget>()
.Select(x => $"`{x}`")
.Join(", "));
}
[Cmd]
[OwnerOnly]
public async Task BetTest(GambleTestTarget target, int tests = 1000)
{
if (tests <= 0)
return;
await ctx.Channel.TriggerTypingAsync();
var streak = 0;
var maxW = 0;
var maxL = 0;
var dict = new Dictionary<decimal, int>();
for (var i = 0; i < tests; i++)
{ {
var multi = target switch GambleTestTarget.BetDraw => (await _gs.BetDrawAsync(ctx.User.Id, 0, 1, 0)).AsT0.Multiplier,
{ GambleTestTarget.BetDrawRB => (await _gs.BetDrawAsync(ctx.User.Id, 0, null, 1)).AsT0.Multiplier,
GambleTestTarget.BetDraw => (await _gs.BetDrawAsync(ctx.User.Id, 0, 1, 0)).AsT0.Multiplier, GambleTestTarget.BetDrawHL => (await _gs.BetDrawAsync(ctx.User.Id, 0, 0, null)).AsT0.Multiplier,
GambleTestTarget.BetDrawRB => (await _gs.BetDrawAsync(ctx.User.Id, 0, null, 1)).AsT0.Multiplier, GambleTestTarget.Slot => (await _gs.SlotAsync(ctx.User.Id, 0)).AsT0.Multiplier,
GambleTestTarget.BetDrawHL => (await _gs.BetDrawAsync(ctx.User.Id, 0, 0, null)).AsT0.Multiplier, GambleTestTarget.Betflip => (await _gs.BetFlipAsync(ctx.User.Id, 0, 0)).AsT0.Multiplier,
GambleTestTarget.Slot => (await _gs.SlotAsync(ctx.User.Id, 0)).AsT0.Multiplier, GambleTestTarget.BetflipT => (await _gs.BetFlipAsync(ctx.User.Id, 0, 1)).AsT0.Multiplier,
GambleTestTarget.Betflip => (await _gs.BetFlipAsync(ctx.User.Id, 0, 0)).AsT0.Multiplier, GambleTestTarget.Lula => (await _gs.LulaAsync(ctx.User.Id, 0)).AsT0.Multiplier,
GambleTestTarget.BetflipT => (await _gs.BetFlipAsync(ctx.User.Id, 0, 1)).AsT0.Multiplier, GambleTestTarget.Rps => (await _gs.RpsAsync(ctx.User.Id, 0, (byte)(i % 3))).AsT0.Multiplier,
GambleTestTarget.Lula => (await _gs.LulaAsync(ctx.User.Id, 0)).AsT0.Multiplier, GambleTestTarget.Betroll => (await _gs.BetRollAsync(ctx.User.Id, 0)).AsT0.Multiplier,
GambleTestTarget.Rps => (await _gs.RpsAsync(ctx.User.Id, 0, (byte)(i % 3))).AsT0.Multiplier, _ => throw new ArgumentOutOfRangeException(nameof(target))
GambleTestTarget.Betroll => (await _gs.BetRollAsync(ctx.User.Id, 0)).AsT0.Multiplier, };
_ => throw new ArgumentOutOfRangeException(nameof(target))
};
if (dict.ContainsKey(multi)) if (dict.ContainsKey(multi))
dict[multi] += 1; dict[multi] += 1;
else
dict.Add(multi, 1);
if (multi < 1)
{
if (streak <= 0)
--streak;
else else
dict.Add(multi, 1); streak = -1;
if (multi < 1) maxL = Math.Max(maxL, -streak);
{
if (streak <= 0)
--streak;
else
streak = -1;
maxL = Math.Max(maxL, -streak);
}
else if (multi > 1)
{
if (streak >= 0)
++streak;
else
streak = 1;
maxW = Math.Max(maxW, streak);
}
} }
else if (multi > 1)
var sb = new StringBuilder();
decimal payout = 0;
foreach (var key in dict.Keys.OrderByDescending(x => x))
{ {
sb.AppendLine($"x**{key}** occured `{dict[key]}` times. {dict[key] * 1.0f / tests * 100}%"); if (streak >= 0)
payout += key * dict[key]; ++streak;
else
streak = 1;
maxW = Math.Max(maxW, streak);
} }
sb.AppendLine();
sb.AppendLine($"Longest win streak: `{maxW}`");
sb.AppendLine($"Longest lose streak: `{maxL}`");
await SendConfirmAsync(GetText(strs.test_results_for(target)),
sb.ToString(),
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%");
} }
var sb = new StringBuilder();
decimal payout = 0;
foreach (var key in dict.Keys.OrderByDescending(x => x))
{
sb.AppendLine($"x**{key}** occured `{dict[key]}` times. {dict[key] * 1.0f / tests * 100}%");
payout += key * dict[key];
}
sb.AppendLine();
sb.AppendLine($"Longest win streak: `{maxW}`");
sb.AppendLine($"Longest lose streak: `{maxL}`");
await SendConfirmAsync(GetText(strs.test_results_for(target)),
sb.ToString(),
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%");
}
} }

View File

@@ -47,27 +47,6 @@ public partial class Gambling
public Task Test() public Task Test()
=> Task.CompletedTask; => Task.CompletedTask;
[Cmd]
[OwnerOnly]
public async Task SlotStats()
{
//i remembered to not be a moron
var paid = totalPaidOut;
var bet = totalBet;
if (bet <= 0)
bet = 1;
var embed = _eb.Create()
.WithOkColor()
.WithTitle("Slot Stats")
.AddField("Total Bet", N(bet), true)
.AddField("Paid Out", N(paid), true)
.WithFooter($"Payout Rate: {paid * 1.0M / bet * 100:f4}%");
await ctx.Channel.EmbedAsync(embed);
}
[Cmd] [Cmd]
public async Task Slot(ShmartNumber amount) public async Task Slot(ShmartNumber amount)
{ {

View File

@@ -126,7 +126,7 @@ public class ChatterBotService : IExecOnMessage
Log.Information("{PermissionMessage}", returnMsg); Log.Information("{PermissionMessage}", returnMsg);
} }
return true; return false;
} }
if (await _ccs.TryBlock(sg, usrMsg.Author, CleverBotResponseStr.CLEVERBOT_RESPONSE)) if (await _ccs.TryBlock(sg, usrMsg.Author, CleverBotResponseStr.CLEVERBOT_RESPONSE))

View File

@@ -92,7 +92,7 @@ public partial class Help : NadekoModule<HelpService>
localModules.OrderBy(module => module.Name) localModules.OrderBy(module => module.Name)
.ToList() .ToList()
.ForEach(module => embed.AddField($"{GetModuleEmoji(module.Name)} {module.Name}", .ForEach(module => embed.AddField($"{GetModuleEmoji(module.Name)} {module.Name}",
GetText(GetModuleLocStr(module.Name)) GetModuleDescription(module.Name)
+ "\n" + "\n"
+ Format.Code(GetText(strs.module_footer(prefix, module.Name.ToLowerInvariant()))), + Format.Code(GetText(strs.module_footer(prefix, module.Name.ToLowerInvariant()))),
true)); true));
@@ -104,6 +104,25 @@ public partial class Help : NadekoModule<HelpService>
false); false);
} }
private string GetModuleDescription(string moduleName)
{
var key = GetModuleLocStr(moduleName);
if (key.Key == strs.module_description_missing.Key)
{
var desc = _medusae
.GetLoadedMedusae(Culture)
.FirstOrDefault(m => m.Sneks
.Any(x => x.Name.Equals(moduleName, StringComparison.InvariantCultureIgnoreCase)))
?.Description;
if (desc is not null)
return desc;
}
return GetText(key);
}
private LocStr GetModuleLocStr(string moduleName) private LocStr GetModuleLocStr(string moduleName)
{ {
switch (moduleName.ToLowerInvariant()) switch (moduleName.ToLowerInvariant())

View File

@@ -1,10 +1,12 @@
#nullable disable using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db;
namespace NadekoBot.Modules.Permissions.Services; namespace NadekoBot.Modules.Permissions.Services;
public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
{ {
private readonly DbService _db;
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, int>> _settings = new(); private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, int>> _settings = new();
private readonly ConcurrentDictionary<(ulong, string), ConcurrentDictionary<ulong, DateTime>> _activeCooldowns = private readonly ConcurrentDictionary<(ulong, string), ConcurrentDictionary<ulong, DateTime>> _activeCooldowns =
@@ -12,11 +14,13 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
public int Priority => 0; public int Priority => 0;
public CmdCdService(Bot bot) public CmdCdService(Bot bot, DbService db)
{ {
_db = db;
_settings = bot _settings = bot
.AllGuildConfigs .AllGuildConfigs
.ToDictionary(x => x.GuildId, x => x.CommandCooldowns .ToDictionary(x => x.GuildId, x => x.CommandCooldowns
.DistinctBy(x => x.CommandName.ToLowerInvariant())
.ToDictionary(c => c.CommandName, c => c.Seconds) .ToDictionary(c => c.CommandName, c => c.Seconds)
.ToConcurrent()) .ToConcurrent())
.ToConcurrent(); .ToConcurrent();
@@ -25,13 +29,16 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
public Task<bool> ExecPreCommandAsync(ICommandContext context, string moduleName, CommandInfo command) public Task<bool> ExecPreCommandAsync(ICommandContext context, string moduleName, CommandInfo command)
=> TryBlock(context.Guild, context.User, command.Name.ToLowerInvariant()); => TryBlock(context.Guild, context.User, command.Name.ToLowerInvariant());
public async Task<bool> TryBlock(IGuild guild, IUser user, string commandName) public Task<bool> TryBlock(IGuild? guild, IUser user, string commandName)
{ {
if (guild is null)
return Task.FromResult(false);
if (!_settings.TryGetValue(guild.Id, out var cooldownSettings)) if (!_settings.TryGetValue(guild.Id, out var cooldownSettings))
return false; return Task.FromResult(false);
if (!cooldownSettings.TryGetValue(commandName, out var cdSeconds)) if (!cooldownSettings.TryGetValue(commandName, out var cdSeconds))
return false; return Task.FromResult(false);
var cooldowns = _activeCooldowns.GetOrAdd( var cooldowns = _activeCooldowns.GetOrAdd(
(guild.Id, commandName), (guild.Id, commandName),
@@ -40,7 +47,7 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
// if user is not already on cooldown, add // if user is not already on cooldown, add
if (cooldowns.TryAdd(user.Id, DateTime.UtcNow)) if (cooldowns.TryAdd(user.Id, DateTime.UtcNow))
{ {
return false; return Task.FromResult(false);
} }
// if there is an entry, maybe it expired. Try to check if it expired and don't fail if it did // if there is an entry, maybe it expired. Try to check if it expired and don't fail if it did
@@ -48,14 +55,14 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
if (cooldowns.TryGetValue(user.Id, out var oldValue)) if (cooldowns.TryGetValue(user.Id, out var oldValue))
{ {
var diff = DateTime.UtcNow - oldValue; var diff = DateTime.UtcNow - oldValue;
if (diff.Seconds > cdSeconds) if (diff.TotalSeconds > cdSeconds)
{ {
if (cooldowns.TryUpdate(user.Id, DateTime.UtcNow, oldValue)) if (cooldowns.TryUpdate(user.Id, DateTime.UtcNow, oldValue))
return false; return Task.FromResult(false);
} }
} }
return true; return Task.FromResult(true);
} }
public async Task OnReadyAsync() public async Task OnReadyAsync()
@@ -64,7 +71,6 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
while (await timer.WaitForNextTickAsync()) while (await timer.WaitForNextTickAsync())
{ {
var now = DateTime.UtcNow;
// once per hour delete expired entries // once per hour delete expired entries
foreach (var ((guildId, commandName), dict) in _activeCooldowns) foreach (var ((guildId, commandName), dict) in _activeCooldowns)
{ {
@@ -85,7 +91,7 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
private void Cleanup(ConcurrentDictionary<ulong, DateTime> dict, int cdSeconds) private void Cleanup(ConcurrentDictionary<ulong, DateTime> dict, int cdSeconds)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
foreach (var (key, _) in dict.Where(x => (now - x.Value).Seconds > cdSeconds).ToArray()) foreach (var (key, _) in dict.Where(x => (now - x.Value).TotalSeconds > cdSeconds).ToArray())
{ {
dict.TryRemove(key, out _); dict.TryRemove(key, out _);
} }
@@ -97,16 +103,34 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
dict.TryRemove(cmdName, out _); dict.TryRemove(cmdName, out _);
_activeCooldowns.TryRemove((guildId, cmdName), out _); _activeCooldowns.TryRemove((guildId, cmdName), out _);
using var ctx = _db.GetDbContext();
var gc = ctx.GuildConfigsForId(guildId, x => x.Include(x => x.CommandCooldowns));
gc.CommandCooldowns.RemoveWhere(x => x.CommandName == cmdName);
ctx.SaveChanges();
} }
public void AddCooldown(ulong guildId, string name, int secs) public void AddCooldown(ulong guildId, string name, int secs)
{ {
if (secs <= 0)
throw new ArgumentOutOfRangeException(nameof(secs));
var sett = _settings.GetOrAdd(guildId, static _ => new()); var sett = _settings.GetOrAdd(guildId, static _ => new());
sett[name] = secs; sett[name] = secs;
// force cleanup // force cleanup
if (_activeCooldowns.TryGetValue((guildId, name), out var dict)) if (_activeCooldowns.TryGetValue((guildId, name), out var dict))
Cleanup(dict, secs); Cleanup(dict, secs);
using var ctx = _db.GetDbContext();
var gc = ctx.GuildConfigsForId(guildId, x => x.Include(x => x.CommandCooldowns));
gc.CommandCooldowns.RemoveWhere(x => x.CommandName == name);
gc.CommandCooldowns.Add(new()
{
Seconds = secs,
CommandName = name
});
ctx.SaveChanges();
} }
public IReadOnlyCollection<(string CommandName, int Seconds)> GetCommandCooldowns(ulong guildId) public IReadOnlyCollection<(string CommandName, int Seconds)> GetCommandCooldowns(ulong guildId)

View File

@@ -25,6 +25,47 @@ public partial class Permissions
await ReplyConfirmLocalizedAsync(strs.fw_cleared); await ReplyConfirmLocalizedAsync(strs.fw_cleared);
} }
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task FilterList()
{
var embed = _eb.Create(ctx)
.WithOkColor()
.WithTitle("Server filter settings");
var config = await _service.GetFilterSettings(ctx.Guild.Id);
string GetEnabledEmoji(bool value)
=> value ? "\\🟢" : "\\🔴";
async Task<string> GetChannelListAsync(IReadOnlyCollection<ulong> channels)
{
var toReturn = (await channels
.Select(async cid =>
{
var ch = await ctx.Guild.GetChannelAsync(cid);
return ch is null
? $"{cid} *missing*"
: $"<#{cid}>";
})
.WhenAll())
.Join('\n');
if (string.IsNullOrWhiteSpace(toReturn))
return GetText(strs.no_channel_found);
return toReturn;
}
embed.AddField($"{GetEnabledEmoji(config.FilterLinksEnabled)} Filter Links",
await GetChannelListAsync(config.FilterLinksChannels));
embed.AddField($"{GetEnabledEmoji(config.FilterInvitesEnabled)} Filter Invites",
await GetChannelListAsync(config.FilterInvitesChannels));
await ctx.Channel.EmbedAsync(embed);
}
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task SrvrFilterInv() public async Task SrvrFilterInv()

View File

@@ -1,4 +1,5 @@
#nullable disable #nullable disable
using AngleSharp.Dom;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db; using NadekoBot.Db;
@@ -222,4 +223,20 @@ public sealed class FilterService : IExecOnMessage
return false; return false;
} }
public async Task<ServerFilterSettings> GetFilterSettings(ulong guildId)
{
await using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set
.Include(x => x.FilterInvitesChannelIds)
.Include(x => x.FilterLinksChannelIds));
return new()
{
FilterInvitesChannels = gc.FilterInvitesChannelIds.Map(x => x.ChannelId),
FilterLinksChannels = gc.FilterLinksChannelIds.Map(x => x.ChannelId),
FilterInvitesEnabled = gc.FilterInvites,
FilterLinksEnabled = gc.FilterLinks,
};
}
} }

View File

@@ -0,0 +1,10 @@
#nullable disable
namespace NadekoBot.Modules.Permissions.Services;
public readonly struct ServerFilterSettings
{
public bool FilterInvitesEnabled { get; init; }
public bool FilterLinksEnabled { get; init; }
public IReadOnlyCollection<ulong> FilterInvitesChannels { get; init; }
public IReadOnlyCollection<ulong> FilterLinksChannels { get; init; }
}

View File

@@ -32,33 +32,43 @@ public partial class Searches
[UserPerm(GuildPerm.ManageMessages)] [UserPerm(GuildPerm.ManageMessages)]
public async Task Feed(string url, [Leftover] ITextChannel channel = null) public async Task Feed(string url, [Leftover] ITextChannel channel = null)
{ {
var success = Uri.TryCreate(url, UriKind.Absolute, out var uri) if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps); || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
if (success)
{ {
channel ??= (ITextChannel)ctx.Channel; await ReplyErrorLocalizedAsync(strs.feed_invalid_url);
try return;
{
await FeedReader.ReadAsync(url);
}
catch (Exception ex)
{
Log.Information(ex, "Unable to get feeds from that url");
success = false;
}
} }
if (success) channel ??= (ITextChannel)ctx.Channel;
try
{ {
success = _service.AddFeed(ctx.Guild.Id, channel.Id, url); await FeedReader.ReadAsync(url);
if (success) }
{ catch (Exception ex)
await ReplyConfirmLocalizedAsync(strs.feed_added); {
return; Log.Information(ex, "Unable to get feeds from that url");
} await ReplyErrorLocalizedAsync(strs.feed_cant_parse);
return;
} }
await ReplyConfirmLocalizedAsync(strs.feed_not_valid); var result = _service.AddFeed(ctx.Guild.Id, channel.Id, url);
if (result == FeedAddResult.Success)
{
await ReplyConfirmLocalizedAsync(strs.feed_added);
return;
}
if (result == FeedAddResult.Duplicate)
{
await ReplyErrorLocalizedAsync(strs.feed_duplicate);
return;
}
if (result == FeedAddResult.LimitReached)
{
await ReplyErrorLocalizedAsync(strs.feed_limit_reached);
return;
}
} }
[Cmd] [Cmd]

View File

@@ -207,7 +207,7 @@ public class FeedsService : INService
.ToList(); .ToList();
} }
public bool AddFeed(ulong guildId, ulong channelId, string rssFeed) public FeedAddResult AddFeed(ulong guildId, ulong channelId, string rssFeed)
{ {
ArgumentNullException.ThrowIfNull(rssFeed, nameof(rssFeed)); ArgumentNullException.ThrowIfNull(rssFeed, nameof(rssFeed));
@@ -221,9 +221,9 @@ public class FeedsService : INService
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs)); var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs));
if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower())) if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower()))
return false; return FeedAddResult.Duplicate;
if (gc.FeedSubs.Count >= 10) if (gc.FeedSubs.Count >= 10)
return false; return FeedAddResult.LimitReached;
gc.FeedSubs.Add(fs); gc.FeedSubs.Add(fs);
uow.SaveChanges(); uow.SaveChanges();
@@ -242,7 +242,7 @@ public class FeedsService : INService
}); });
} }
return true; return FeedAddResult.Success;
} }
public bool RemoveFeed(ulong guildId, int index) public bool RemoveFeed(ulong guildId, int index)
@@ -271,3 +271,11 @@ public class FeedsService : INService
return true; return true;
} }
} }
public enum FeedAddResult
{
Success,
LimitReached,
Invalid,
Duplicate,
}

View File

@@ -9,7 +9,7 @@ namespace NadekoBot.Modules.Utility;
public partial class Utility public partial class Utility
{ {
[Group] [Group]
public partial class CommandMapCommands : NadekoModule<CommandMapService> public partial class CommandMapCommands : NadekoModule<AliasService>
{ {
private readonly DbService _db; private readonly DbService _db;
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;

View File

@@ -6,15 +6,14 @@ using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Utility.Services; namespace NadekoBot.Modules.Utility.Services;
public class CommandMapService : IInputTransformer, INService public class AliasService : IInputTransformer, INService
{ {
public ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new(); public ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new();
private readonly IEmbedBuilderService _eb; private readonly IEmbedBuilderService _eb;
private readonly DbService _db; private readonly DbService _db;
//commandmap public AliasService(DiscordSocketClient client, DbService db, IEmbedBuilderService eb)
public CommandMapService(DiscordSocketClient client, DbService db, IEmbedBuilderService eb)
{ {
_eb = eb; _eb = eb;
@@ -66,7 +65,10 @@ public class CommandMapService : IInputTransformer, INService
} }
else if (input.StartsWith(k + ' ', StringComparison.OrdinalIgnoreCase)) else if (input.StartsWith(k + ' ', StringComparison.OrdinalIgnoreCase))
{ {
newInput = v + ' ' + input[k.Length..]; if (v.Contains("%target%"))
newInput = v.Replace("%target%", input[k.Length..]);
else
newInput = v + ' ' + input[k.Length..];
} }
if (newInput is not null) if (newInput is not null)
@@ -74,17 +76,11 @@ public class CommandMapService : IInputTransformer, INService
try try
{ {
var toDelete = await channel.SendConfirmAsync(_eb, $"{input} => {newInput}"); var toDelete = await channel.SendConfirmAsync(_eb, $"{input} => {newInput}");
_ = Task.Run(async () => toDelete.DeleteAfter(1.5f);
{
await Task.Delay(1500);
await toDelete.DeleteAsync(new()
{
RetryMode = RetryMode.AlwaysRetry
});
});
} }
catch catch
{ {
// ignored
} }
return newInput; return newInput;
@@ -92,34 +88,6 @@ public class CommandMapService : IInputTransformer, INService
} }
return null; return null;
// var keys = maps.Keys.OrderByDescending(x => x.Length);
// foreach (var k in keys)
// {
// string newInput;
// if (input.StartsWith(k + " ", StringComparison.InvariantCultureIgnoreCase))
// newInput = maps[k] + input.Substring(k.Length, input.Length - k.Length);
// else if (input.Equals(k, StringComparison.InvariantCultureIgnoreCase))
// newInput = maps[k];
// else
// continue;
//
// try
// {
// var toDelete = await channel.SendConfirmAsync(_eb, $"{input} => {newInput}");
// _ = Task.Run(async () =>
// {
// await Task.Delay(1500);
// await toDelete.DeleteAsync(new()
// {
// RetryMode = RetryMode.AlwaysRetry
// });
// });
// }
// catch { }
//
// return newInput;
// }
} }
return null; return null;

View File

@@ -1,24 +1,24 @@
#nullable disable #nullable disable
using System.Globalization;
using LinqToDB; using LinqToDB;
using LinqToDB.EntityFrameworkCore; using LinqToDB.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Nadeko.Common;
namespace NadekoBot.Modules.Utility.Services; namespace NadekoBot.Modules.Utility.Services;
public class RemindService : INService, IReadyExecutor public class RemindService : INService, IReadyExecutor
{ {
private readonly Regex _regex = private readonly Regex _regex =
new( new(@"^(?:(?:at|on(?:\sthe)?)?\s*(?<date>(?:\d{2}:\d{2}\s)?\d{1,2}\.\d{1,2}(?:\.\d{2,4})?)|(?:in\s?)?\s*(?:(?<mo>\d+)(?:\s?(?:months?|mos?),?))?(?:(?:\sand\s|\s*)?(?<w>\d+)(?:\s?(?:weeks?|w),?))?(?:(?:\sand\s|\s*)?(?<d>\d+)(?:\s?(?:days?|d),?))?(?:(?:\sand\s|\s*)?(?<h>\d+)(?:\s?(?:hours?|h),?))?(?:(?:\sand\s|\s*)?(?<m>\d+)(?:\s?(?:minutes?|mins?|m),?))?)\s+(?:to:?\s+)?(?<what>(?:\r\n|[\r\n]|.)+)",
@"^(?:in\s?)?\s*(?:(?<mo>\d+)(?:\s?(?:months?|mos?),?))?(?:(?:\sand\s|\s*)?(?<w>\d+)(?:\s?(?:weeks?|w),?))?(?:(?:\sand\s|\s*)?(?<d>\d+)(?:\s?(?:days?|d),?))?(?:(?:\sand\s|\s*)?(?<h>\d+)(?:\s?(?:hours?|h),?))?(?:(?:\sand\s|\s*)?(?<m>\d+)(?:\s?(?:minutes?|mins?|m),?))?\s+(?:to:?\s+)?(?<what>(?:\r\n|[\r\n]|.)+)",
RegexOptions.Compiled | RegexOptions.Multiline); RegexOptions.Compiled | RegexOptions.Multiline);
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private readonly DbService _db; private readonly DbService _db;
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
private readonly IEmbedBuilderService _eb; private readonly IEmbedBuilderService _eb;
private readonly CultureInfo _culture;
public RemindService( public RemindService(
DiscordSocketClient client, DiscordSocketClient client,
@@ -30,6 +30,15 @@ public class RemindService : INService, IReadyExecutor
_db = db; _db = db;
_creds = creds; _creds = creds;
_eb = eb; _eb = eb;
try
{
_culture = new CultureInfo("en-GB");
}
catch
{
_culture = CultureInfo.InvariantCulture;
}
} }
public async Task OnReadyAsync() public async Task OnReadyAsync()
@@ -105,32 +114,57 @@ public class RemindService : INService, IReadyExecutor
return false; return false;
} }
foreach (var groupName in _regex.GetGroupNames()) TimeSpan ts;
var dateString = m.Groups["date"].Value;
if (!string.IsNullOrWhiteSpace(dateString))
{ {
if (groupName is "0" or "what") var now = DateTime.UtcNow;
continue;
if (string.IsNullOrWhiteSpace(m.Groups[groupName].Value))
{
values[groupName] = 0;
continue;
}
if (!int.TryParse(m.Groups[groupName].Value, out var value)) if (!DateTime.TryParse(dateString, _culture, DateTimeStyles.None, out var dt))
{ {
Log.Warning("Reminder regex group {GroupName} has invalid value", groupName); Log.Warning("Invalid remind datetime format");
return false; return false;
} }
if (value < 1) if (now >= dt)
{ {
Log.Warning("Reminder time value has to be an integer greater than 0"); Log.Warning("That remind time has already passed");
return false; return false;
} }
values[groupName] = value; ts = dt - now;
}
else
{
foreach (var groupName in _regex.GetGroupNames())
{
if (groupName is "0" or "what")
continue;
if (string.IsNullOrWhiteSpace(m.Groups[groupName].Value))
{
values[groupName] = 0;
continue;
}
if (!int.TryParse(m.Groups[groupName].Value, out var value))
{
Log.Warning("Reminder regex group {GroupName} has invalid value", groupName);
return false;
}
if (value < 1)
{
Log.Warning("Reminder time value has to be an integer greater than 0");
return false;
}
values[groupName] = value;
}
ts = new TimeSpan((30 * values["mo"]) + (7 * values["w"]) + values["d"], values["h"], values["m"], 0);
} }
var ts = new TimeSpan((30 * values["mo"]) + (7 * values["w"]) + values["d"], values["h"], values["m"], 0);
obj = new() obj = new()
{ {

View File

@@ -17,11 +17,14 @@ public partial class Xp
[Cmd] [Cmd]
public async Task ClubTransfer([Leftover] IUser newOwner) public async Task ClubTransfer([Leftover] IUser newOwner)
{ {
var club = _service.TransferClub(ctx.User, newOwner); var result = _service.TransferClub(ctx.User, newOwner);
if (club is null) if (!result.TryPickT0(out var club, out var error))
{ {
await ReplyErrorLocalizedAsync(strs.club_transfer_failed); if(error == ClubTransferError.NotOwner)
await ReplyErrorLocalizedAsync(strs.club_owner_only);
else
await ReplyErrorLocalizedAsync(strs.club_target_not_member);
} }
else else
{ {
@@ -37,15 +40,20 @@ public partial class Xp
[Cmd] [Cmd]
public async Task ClubAdmin([Leftover] IUser toAdmin) public async Task ClubAdmin([Leftover] IUser toAdmin)
{ {
var admin = await _service.ToggleAdminAsync(ctx.User, toAdmin); var result = await _service.ToggleAdminAsync(ctx.User, toAdmin);
if (admin is null) if (result == ToggleAdminResult.AddedAdmin)
await ReplyErrorLocalizedAsync(strs.club_admin_error);
else if (admin is true)
await ReplyConfirmLocalizedAsync(strs.club_admin_add(Format.Bold(toAdmin.ToString()))); await ReplyConfirmLocalizedAsync(strs.club_admin_add(Format.Bold(toAdmin.ToString())));
else else if (result == ToggleAdminResult.RemovedAdmin)
await ReplyConfirmLocalizedAsync(strs.club_admin_remove(Format.Bold(toAdmin.ToString()))); await ReplyConfirmLocalizedAsync(strs.club_admin_remove(Format.Bold(toAdmin.ToString())));
else if (result == ToggleAdminResult.NotOwner)
await ReplyErrorLocalizedAsync(strs.club_owner_only);
else if (result == ToggleAdminResult.CantTargetThyself)
await ReplyErrorLocalizedAsync(strs.club_admin_invalid_target);
else if (result == ToggleAdminResult.TargetNotMember)
await ReplyErrorLocalizedAsync(strs.club_target_not_member);
} }
[Cmd] [Cmd]
public async Task ClubCreate([Leftover] string clubName) public async Task ClubCreate([Leftover] string clubName)
{ {
@@ -55,17 +63,23 @@ public partial class Xp
return; return;
} }
var succ = await _service.CreateClubAsync(ctx.User, clubName); var result = await _service.CreateClubAsync(ctx.User, clubName);
if (succ is null) if (result == ClubCreateResult.NameTaken)
{ {
await ReplyErrorLocalizedAsync(strs.club_create_error_name); await ReplyErrorLocalizedAsync(strs.club_create_error_name);
return; return;
} }
if (succ is false) if (result == ClubCreateResult.InsufficientLevel)
{ {
await ReplyErrorLocalizedAsync(strs.club_create_error); await ReplyErrorLocalizedAsync(strs.club_create_insuff_lvl);
return;
}
if (result == ClubCreateResult.AlreadyInAClub)
{
await ReplyErrorLocalizedAsync(strs.club_already_in);
return; return;
} }
@@ -75,14 +89,21 @@ public partial class Xp
[Cmd] [Cmd]
public async Task ClubIcon([Leftover] string url = null) public async Task ClubIcon([Leftover] string url = null)
{ {
if ((!Uri.IsWellFormedUriString(url, UriKind.Absolute) && url is not null) if ((!Uri.IsWellFormedUriString(url, UriKind.Absolute) && url is not null))
|| !await _service.SetClubIconAsync(ctx.User.Id, url))
{ {
await ReplyErrorLocalizedAsync(strs.club_icon_error); await ReplyErrorLocalizedAsync(strs.club_icon_url_format);
return; return;
} }
await ReplyConfirmLocalizedAsync(strs.club_icon_set); var result = await _service.SetClubIconAsync(ctx.User.Id, url);
if(result == SetClubIconResult.Success)
await ReplyConfirmLocalizedAsync(strs.club_icon_set);
else if (result == SetClubIconResult.NotOwner)
await ReplyErrorLocalizedAsync(strs.club_owner_only);
else if (result == SetClubIconResult.TooLarge)
await ReplyErrorLocalizedAsync(strs.club_icon_too_large);
else if (result == SetClubIconResult.InvalidFileType)
await ReplyErrorLocalizedAsync(strs.club_icon_invalid_filetype);
} }
private async Task InternalClubInfoAsync(ClubInfo club) private async Task InternalClubInfoAsync(ClubInfo club)
@@ -102,27 +123,27 @@ public partial class Xp
page => page =>
{ {
var embed = _eb.Create() var embed = _eb.Create()
.WithOkColor() .WithOkColor()
.WithTitle($"{club}") .WithTitle($"{club}")
.WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)"))) .WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)")))
.AddField(GetText(strs.desc), .AddField(GetText(strs.desc),
string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description) string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description)
.AddField(GetText(strs.owner), club.Owner.ToString(), true) .AddField(GetText(strs.owner), club.Owner.ToString(), true)
// .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true) // .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true)
.AddField(GetText(strs.members), .AddField(GetText(strs.members),
string.Join("\n", string.Join("\n",
users.Skip(page * 10) users.Skip(page * 10)
.Take(10) .Take(10)
.Select(x => .Select(x =>
{ {
var l = new LevelStats(x.TotalXp); var l = new LevelStats(x.TotalXp);
var lvlStr = Format.Bold($" ⟪{l.Level}⟫"); var lvlStr = Format.Bold($" ⟪{l.Level}⟫");
if (club.OwnerId == x.Id) if (club.OwnerId == x.Id)
return x + "🌟" + lvlStr; return x + "🌟" + lvlStr;
if (x.IsClubAdmin) if (x.IsClubAdmin)
return x + "⭐" + lvlStr; return x + "⭐" + lvlStr;
return x + lvlStr; return x + lvlStr;
}))); })));
if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute)) if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute))
return embed.WithThumbnailUrl(club.ImageUrl); return embed.WithThumbnailUrl(club.ImageUrl);
@@ -175,19 +196,21 @@ public partial class Xp
var club = _service.GetClubWithBansAndApplications(ctx.User.Id); var club = _service.GetClubWithBansAndApplications(ctx.User.Id);
if (club is null) if (club is null)
return ReplyErrorLocalizedAsync(strs.club_not_exists_owner); return ReplyErrorLocalizedAsync(strs.club_admin_perms);
var bans = club.Bans.Select(x => x.User).ToArray(); var bans = club.Bans.Select(x => x.User).ToArray();
return ctx.SendPaginatedConfirmAsync(page, return ctx.SendPaginatedConfirmAsync(page,
_ => _ =>
{ {
var toShow = string.Join("\n", bans.Skip(page * 10).Take(10).Select(x => x.ToString())); var toShow = string.Join("\n", bans
.Skip(page * 10).Take(10)
.Select(x => x.ToString()));
return _eb.Create() return _eb.Create()
.WithTitle(GetText(strs.club_bans_for(club.ToString()))) .WithTitle(GetText(strs.club_bans_for(club.ToString())))
.WithDescription(toShow) .WithDescription(toShow)
.WithOkColor(); .WithOkColor();
}, },
bans.Length, bans.Length,
10); 10);
@@ -201,7 +224,7 @@ public partial class Xp
var club = _service.GetClubWithBansAndApplications(ctx.User.Id); var club = _service.GetClubWithBansAndApplications(ctx.User.Id);
if (club is null) if (club is null)
return ReplyErrorLocalizedAsync(strs.club_not_exists_owner); return ReplyErrorLocalizedAsync(strs.club_admin_perms);
var apps = club.Applicants.Select(x => x.User).ToArray(); var apps = club.Applicants.Select(x => x.User).ToArray();
@@ -211,9 +234,9 @@ public partial class Xp
var toShow = string.Join("\n", apps.Skip(page * 10).Take(10).Select(x => x.ToString())); var toShow = string.Join("\n", apps.Skip(page * 10).Take(10).Select(x => x.ToString()));
return _eb.Create() return _eb.Create()
.WithTitle(GetText(strs.club_apps_for(club.ToString()))) .WithTitle(GetText(strs.club_apps_for(club.ToString())))
.WithDescription(toShow) .WithDescription(toShow)
.WithOkColor(); .WithOkColor();
}, },
apps.Length, apps.Length,
10); 10);
@@ -231,10 +254,15 @@ public partial class Xp
return; return;
} }
if (_service.ApplyToClub(ctx.User, club)) var result = _service.ApplyToClub(ctx.User, club);
if (result == ClubApplyResult.Success)
await ReplyConfirmLocalizedAsync(strs.club_applied(Format.Bold(club.ToString()))); await ReplyConfirmLocalizedAsync(strs.club_applied(Format.Bold(club.ToString())));
else else if (result == ClubApplyResult.Banned)
await ReplyErrorLocalizedAsync(strs.club_apply_error); await ReplyErrorLocalizedAsync(strs.club_join_banned);
else if (result == ClubApplyResult.InsufficientLevel)
await ReplyErrorLocalizedAsync(strs.club_insuff_lvl);
else if (result == ClubApplyResult.AlreadyInAClub)
await ReplyErrorLocalizedAsync(strs.club_already_in);
} }
[Cmd] [Cmd]
@@ -246,19 +274,26 @@ public partial class Xp
[Priority(0)] [Priority(0)]
public async Task ClubAccept([Leftover] string userName) public async Task ClubAccept([Leftover] string userName)
{ {
if (_service.AcceptApplication(ctx.User.Id, userName, out var discordUser)) var result = _service.AcceptApplication(ctx.User.Id, userName, out var discordUser);
if (result == ClubAcceptResult.Accepted)
await ReplyConfirmLocalizedAsync(strs.club_accepted(Format.Bold(discordUser.ToString()))); await ReplyConfirmLocalizedAsync(strs.club_accepted(Format.Bold(discordUser.ToString())));
else else if(result == ClubAcceptResult.NoSuchApplicant)
await ReplyErrorLocalizedAsync(strs.club_accept_error); await ReplyErrorLocalizedAsync(strs.club_accept_invalid_applicant);
else if(result == ClubAcceptResult.NotOwnerOrAdmin)
await ReplyErrorLocalizedAsync(strs.club_admin_perms);
} }
[Cmd] [Cmd]
public async Task Clubleave() public async Task ClubLeave()
{ {
if (_service.LeaveClub(ctx.User)) var res = _service.LeaveClub(ctx.User);
if (res == ClubLeaveResult.Success)
await ReplyConfirmLocalizedAsync(strs.club_left); await ReplyConfirmLocalizedAsync(strs.club_left);
else if (res == ClubLeaveResult.NotInAClub)
await ReplyErrorLocalizedAsync(strs.club_not_in_a_club);
else else
await ReplyErrorLocalizedAsync(strs.club_not_in_club); await ReplyErrorLocalizedAsync(strs.club_owner_cant_leave);
} }
[Cmd] [Cmd]
@@ -270,13 +305,20 @@ public partial class Xp
[Priority(0)] [Priority(0)]
public Task ClubKick([Leftover] string userName) public Task ClubKick([Leftover] string userName)
{ {
if (_service.Kick(ctx.User.Id, userName, out var club)) var result = _service.Kick(ctx.User.Id, userName, out var club);
if(result == ClubKickResult.Success)
{ {
return ReplyConfirmLocalizedAsync(strs.club_user_kick(Format.Bold(userName), return ReplyConfirmLocalizedAsync(strs.club_user_kick(Format.Bold(userName),
Format.Bold(club.ToString()))); Format.Bold(club.ToString())));
} }
return ReplyErrorLocalizedAsync(strs.club_user_kick_fail); if (result == ClubKickResult.Hierarchy)
return ReplyErrorLocalizedAsync(strs.club_kick_hierarchy);
if (result == ClubKickResult.NotOwnerOrAdmin)
return ReplyErrorLocalizedAsync(strs.club_admin_perms);
return ReplyErrorLocalizedAsync(strs.club_target_not_member);
} }
[Cmd] [Cmd]
@@ -288,13 +330,20 @@ public partial class Xp
[Priority(0)] [Priority(0)]
public Task ClubBan([Leftover] string userName) public Task ClubBan([Leftover] string userName)
{ {
if (_service.Ban(ctx.User.Id, userName, out var club)) var result = _service.Ban(ctx.User.Id, userName, out var club);
if (result == ClubBanResult.Success)
{ {
return ReplyConfirmLocalizedAsync(strs.club_user_banned(Format.Bold(userName), return ReplyConfirmLocalizedAsync(strs.club_user_banned(Format.Bold(userName),
Format.Bold(club.ToString()))); Format.Bold(club.ToString())));
} }
return ReplyErrorLocalizedAsync(strs.club_user_ban_fail); if (result == ClubBanResult.Unbannable)
return ReplyErrorLocalizedAsync(strs.club_ban_fail_unbannable);
if (result == ClubBanResult.WrongUser)
return ReplyErrorLocalizedAsync(strs.club_ban_fail_user_not_found);
return ReplyErrorLocalizedAsync(strs.club_admin_perms);
} }
[Cmd] [Cmd]
@@ -306,13 +355,20 @@ public partial class Xp
[Priority(0)] [Priority(0)]
public Task ClubUnBan([Leftover] string userName) public Task ClubUnBan([Leftover] string userName)
{ {
if (_service.UnBan(ctx.User.Id, userName, out var club)) var result = _service.UnBan(ctx.User.Id, userName, out var club);
if (result == ClubUnbanResult.Success)
{ {
return ReplyConfirmLocalizedAsync(strs.club_user_unbanned(Format.Bold(userName), return ReplyConfirmLocalizedAsync(strs.club_user_unbanned(Format.Bold(userName),
Format.Bold(club.ToString()))); Format.Bold(club.ToString())));
} }
return ReplyErrorLocalizedAsync(strs.club_user_unban_fail); if (result == ClubUnbanResult.WrongUser)
{
return ReplyErrorLocalizedAsync(strs.club_unban_fail_user_not_found);
}
return ReplyErrorLocalizedAsync(strs.club_admin_perms);
} }
[Cmd] [Cmd]
@@ -325,10 +381,10 @@ public partial class Xp
: desc; : desc;
var eb = _eb.Create(ctx) var eb = _eb.Create(ctx)
.WithAuthor(ctx.User) .WithAuthor(ctx.User)
.WithTitle(GetText(strs.club_desc_update)) .WithTitle(GetText(strs.club_desc_update))
.WithOkColor() .WithOkColor()
.WithDescription(desc); .WithDescription(desc);
await ctx.Channel.EmbedAsync(eb); await ctx.Channel.EmbedAsync(eb);
} }

View File

@@ -2,9 +2,9 @@
using LinqToDB; using LinqToDB;
using LinqToDB.EntityFrameworkCore; using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Nadeko.Common;
using NadekoBot.Db; using NadekoBot.Db;
using NadekoBot.Db.Models; using NadekoBot.Db.Models;
using OneOf;
namespace NadekoBot.Modules.Xp.Services; namespace NadekoBot.Modules.Xp.Services;
@@ -19,7 +19,8 @@ public class ClubService : INService, IClubService
_httpFactory = httpFactory; _httpFactory = httpFactory;
} }
public async Task<bool?> CreateClubAsync(IUser user, string clubName)
public async Task<ClubCreateResult> CreateClubAsync(IUser user, string clubName)
{ {
//must be lvl 5 and must not be in a club already //must be lvl 5 and must not be in a club already
@@ -27,11 +28,14 @@ public class ClubService : INService, IClubService
var du = uow.GetOrCreateUser(user); var du = uow.GetOrCreateUser(user);
var xp = new LevelStats(du.TotalXp); var xp = new LevelStats(du.TotalXp);
if (xp.Level < 5 || du.ClubId is not null) if (xp.Level < 5)
return false; return ClubCreateResult.InsufficientLevel;
if (du.ClubId is not null)
return ClubCreateResult.AlreadyInAClub;
if (await uow.Clubs.AnyAsyncEF(x => x.Name == clubName)) if (await uow.Clubs.AnyAsyncEF(x => x.Name == clubName))
return null; return ClubCreateResult.NameTaken;
du.IsClubAdmin = true; du.IsClubAdmin = true;
du.Club = new() du.Club = new()
@@ -45,17 +49,20 @@ public class ClubService : INService, IClubService
await uow.GetTable<ClubApplicants>() await uow.GetTable<ClubApplicants>()
.DeleteAsync(x => x.UserId == du.Id); .DeleteAsync(x => x.UserId == du.Id);
return true; return ClubCreateResult.Success;
} }
public ClubInfo TransferClub(IUser from, IUser newOwner) public OneOf<ClubInfo, ClubTransferError> TransferClub(IUser from, IUser newOwner)
{ {
using var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
var club = uow.Clubs.GetByOwner(@from.Id); var club = uow.Clubs.GetByOwner(@from.Id);
var newOwnerUser = uow.GetOrCreateUser(newOwner); var newOwnerUser = uow.GetOrCreateUser(newOwner);
if (club is null || club.Owner.UserId != from.Id || !club.Members.Contains(newOwnerUser)) if (club is null || club.Owner.UserId != from.Id)
return null; return ClubTransferError.NotOwner;
if (!club.Members.Contains(newOwnerUser))
return ClubTransferError.TargetNotMember;
club.Owner.IsClubAdmin = true; // old owner will stay as admin club.Owner.IsClubAdmin = true; // old owner will stay as admin
newOwnerUser.IsClubAdmin = true; newOwnerUser.IsClubAdmin = true;
@@ -64,21 +71,24 @@ public class ClubService : INService, IClubService
return club; return club;
} }
public async Task<bool?> ToggleAdminAsync(IUser owner, IUser toAdmin) public async Task<ToggleAdminResult> ToggleAdminAsync(IUser owner, IUser toAdmin)
{ {
if (owner.Id == toAdmin.Id)
return ToggleAdminResult.CantTargetThyself;
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var club = uow.Clubs.GetByOwner(owner.Id); var club = uow.Clubs.GetByOwner(owner.Id);
var adminUser = uow.GetOrCreateUser(toAdmin); var adminUser = uow.GetOrCreateUser(toAdmin);
if (club is null || club.Owner.UserId != owner.Id || !club.Members.Contains(adminUser)) if (club is null)
return null; return ToggleAdminResult.NotOwner;
if (club.OwnerId == adminUser.Id) if(!club.Members.Contains(adminUser))
return true; return ToggleAdminResult.TargetNotMember;
var newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin; var newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin;
await uow.SaveChangesAsync(); await uow.SaveChangesAsync();
return newState; return newState ? ToggleAdminResult.AddedAdmin : ToggleAdminResult.RemovedAdmin;
} }
public ClubInfo GetClubByMember(IUser user) public ClubInfo GetClubByMember(IUser user)
@@ -88,26 +98,30 @@ public class ClubService : INService, IClubService
return member; return member;
} }
public async Task<bool> SetClubIconAsync(ulong ownerUserId, string url) public async Task<SetClubIconResult> SetClubIconAsync(ulong ownerUserId, string url)
{ {
if (url is not null) if (url is not null)
{ {
using var http = _httpFactory.CreateClient(); using var http = _httpFactory.CreateClient();
using var temp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); using var temp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (!temp.IsImage() || temp.GetContentLength() > 5.Megabytes().Bytes)
return false; if (!temp.IsImage())
return SetClubIconResult.InvalidFileType;
if (temp.GetContentLength() > 5.Megabytes().Bytes)
return SetClubIconResult.TooLarge;
} }
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var club = uow.Clubs.GetByOwner(ownerUserId); var club = uow.Clubs.GetByOwner(ownerUserId);
if (club is null) if (club is null)
return false; return SetClubIconResult.NotOwner;
club.ImageUrl = url; club.ImageUrl = url;
await uow.SaveChangesAsync(); await uow.SaveChangesAsync();
return true; return SetClubIconResult.Success;
} }
public bool GetClubByName(string clubName, out ClubInfo club) public bool GetClubByName(string clubName, out ClubInfo club)
@@ -118,18 +132,22 @@ public class ClubService : INService, IClubService
return club is not null; return club is not null;
} }
public bool ApplyToClub(IUser user, ClubInfo club) public ClubApplyResult ApplyToClub(IUser user, ClubInfo club)
{ {
using var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
var du = uow.GetOrCreateUser(user); var du = uow.GetOrCreateUser(user);
uow.SaveChanges(); uow.SaveChanges();
if (du.Club is not null //user banned or a member of a club, or already applied,
|| club.Bans.Any(x => x.UserId == du.Id) // or doesn't min minumum level requirement, can't apply
|| club.Applicants.Any(x => x.UserId == du.Id)) if (du.Club is not null)
//user banned or a member of a club, or already applied, return ClubApplyResult.AlreadyInAClub;
// or doesn't min minumum level requirement, can't apply
return false; if (club.Bans.Any(x => x.UserId == du.Id))
return ClubApplyResult.Banned;
if (club.Applicants.Any(x => x.UserId == du.Id))
return ClubApplyResult.InsufficientLevel;
var app = new ClubApplicants var app = new ClubApplicants
{ {
@@ -138,23 +156,23 @@ public class ClubService : INService, IClubService
}; };
uow.Set<ClubApplicants>().Add(app); uow.Set<ClubApplicants>().Add(app);
uow.SaveChanges(); uow.SaveChanges();
return true; return ClubApplyResult.Success;
} }
public bool AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser)
public ClubAcceptResult AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser)
{ {
discordUser = null; discordUser = null;
using var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId); var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId);
if (club is null) if (club is null)
return false; return ClubAcceptResult.NotOwnerOrAdmin;
var applicant = var applicant =
club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant()); club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
if (applicant is null) if (applicant is null)
return false; return ClubAcceptResult.NoSuchApplicant;
applicant.User.Club = club; applicant.User.Club = club;
applicant.User.IsClubAdmin = false; applicant.User.IsClubAdmin = false;
@@ -166,7 +184,7 @@ public class ClubService : INService, IClubService
discordUser = applicant.User; discordUser = applicant.User;
uow.SaveChanges(); uow.SaveChanges();
return true; return ClubAcceptResult.Accepted;
} }
public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId) public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId)
@@ -175,17 +193,19 @@ public class ClubService : INService, IClubService
return uow.Clubs.GetByOwnerOrAdmin(ownerUserId); return uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
} }
public bool LeaveClub(IUser user) public ClubLeaveResult LeaveClub(IUser user)
{ {
using var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
var du = uow.GetOrCreateUser(user, x => x.Include(u => u.Club)); var du = uow.GetOrCreateUser(user, x => x.Include(u => u.Club));
if (du.Club is null || du.Club.OwnerId == du.Id) if (du.Club is null)
return false; return ClubLeaveResult.NotInAClub;
if (du.Club.OwnerId == du.Id)
return ClubLeaveResult.OwnerCantLeave;
du.Club = null; du.Club = null;
du.IsClubAdmin = false; du.IsClubAdmin = false;
uow.SaveChanges(); uow.SaveChanges();
return true; return ClubLeaveResult.Success;
} }
public bool SetDescription(ulong userId, string desc) public bool SetDescription(ulong userId, string desc)
@@ -213,23 +233,23 @@ public class ClubService : INService, IClubService
return true; return true;
} }
public bool Ban(ulong bannerId, string userName, out ClubInfo club) public ClubBanResult Ban(ulong bannerId, string userName, out ClubInfo club)
{ {
using var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
club = uow.Clubs.GetByOwnerOrAdmin(bannerId); club = uow.Clubs.GetByOwnerOrAdmin(bannerId);
if (club is null) if (club is null)
return false; return ClubBanResult.NotOwnerOrAdmin;
var usr = club.Members.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant()) var usr = club.Members.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant())
?? club.Applicants ?? club.Applicants
.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant()) .FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant())
?.User; ?.User;
if (usr is null) if (usr is null)
return false; return ClubBanResult.WrongUser;
if (club.OwnerId == usr.Id if (club.OwnerId == usr.Id
|| (usr.IsClubAdmin && club.Owner.UserId != bannerId)) // can't ban the owner kek, whew || (usr.IsClubAdmin && club.Owner.UserId != bannerId)) // can't ban the owner kek, whew
return false; return ClubBanResult.Unbannable;
club.Bans.Add(new() club.Bans.Add(new()
{ {
@@ -244,39 +264,40 @@ public class ClubService : INService, IClubService
uow.SaveChanges(); uow.SaveChanges();
return true; return ClubBanResult.Success;
} }
public bool UnBan(ulong ownerUserId, string userName, out ClubInfo club) public ClubUnbanResult UnBan(ulong ownerUserId, string userName, out ClubInfo club)
{ {
using var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
if (club is null) if (club is null)
return false; return ClubUnbanResult.NotOwnerOrAdmin;
var ban = club.Bans.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant()); var ban = club.Bans.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
if (ban is null) if (ban is null)
return false; return ClubUnbanResult.WrongUser;
club.Bans.Remove(ban); club.Bans.Remove(ban);
uow.SaveChanges(); uow.SaveChanges();
return true; return ClubUnbanResult.Success;
} }
public bool Kick(ulong kickerId, string userName, out ClubInfo club)
public ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club)
{ {
using var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
club = uow.Clubs.GetByOwnerOrAdmin(kickerId); club = uow.Clubs.GetByOwnerOrAdmin(kickerId);
if (club is null) if (club is null)
return false; return ClubKickResult.NotOwnerOrAdmin;
var usr = club.Members.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant()); var usr = club.Members.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
if (usr is null) if (usr is null)
return false; return ClubKickResult.TargetNotAMember;
if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != kickerId)) if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != kickerId))
return false; return ClubKickResult.Hierarchy;
club.Members.Remove(usr); club.Members.Remove(usr);
var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id); var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
@@ -284,7 +305,7 @@ public class ClubService : INService, IClubService
club.Applicants.Remove(app); club.Applicants.Remove(app);
uow.SaveChanges(); uow.SaveChanges();
return true; return ClubKickResult.Success;
} }
public List<ClubInfo> GetClubLeaderboardPage(int page) public List<ClubInfo> GetClubLeaderboardPage(int page)

View File

@@ -1,23 +1,33 @@
using NadekoBot.Db.Models; using NadekoBot.Db.Models;
using OneOf;
namespace NadekoBot.Modules.Xp.Services; namespace NadekoBot.Modules.Xp.Services;
public interface IClubService public interface IClubService
{ {
Task<bool?> CreateClubAsync(IUser user, string clubName); Task<ClubCreateResult> CreateClubAsync(IUser user, string clubName);
ClubInfo? TransferClub(IUser from, IUser newOwner); OneOf<ClubInfo,ClubTransferError> TransferClub(IUser from, IUser newOwner);
Task<bool?> ToggleAdminAsync(IUser owner, IUser toAdmin); Task<ToggleAdminResult> ToggleAdminAsync(IUser owner, IUser toAdmin);
ClubInfo? GetClubByMember(IUser user); ClubInfo? GetClubByMember(IUser user);
Task<bool> SetClubIconAsync(ulong ownerUserId, string? url); Task<SetClubIconResult> SetClubIconAsync(ulong ownerUserId, string? url);
bool GetClubByName(string clubName, out ClubInfo club); bool GetClubByName(string clubName, out ClubInfo club);
bool ApplyToClub(IUser user, ClubInfo club); ClubApplyResult ApplyToClub(IUser user, ClubInfo club);
bool AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser); ClubAcceptResult AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser);
ClubInfo? GetClubWithBansAndApplications(ulong ownerUserId); ClubInfo? GetClubWithBansAndApplications(ulong ownerUserId);
bool LeaveClub(IUser user); ClubLeaveResult LeaveClub(IUser user);
bool SetDescription(ulong userId, string? desc); bool SetDescription(ulong userId, string? desc);
bool Disband(ulong userId, out ClubInfo club); bool Disband(ulong userId, out ClubInfo club);
bool Ban(ulong bannerId, string userName, out ClubInfo club); ClubBanResult Ban(ulong bannerId, string userName, out ClubInfo club);
bool UnBan(ulong ownerUserId, string userName, out ClubInfo club); ClubUnbanResult UnBan(ulong ownerUserId, string userName, out ClubInfo club);
bool Kick(ulong kickerId, string userName, out ClubInfo club); ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club);
List<ClubInfo> GetClubLeaderboardPage(int page); List<ClubInfo> GetClubLeaderboardPage(int page);
} }
public enum ClubApplyResult
{
Success,
AlreadyInAClub,
Banned,
InsufficientLevel
}

View File

@@ -0,0 +1,8 @@
namespace NadekoBot.Modules.Xp.Services;
public enum ClubAcceptResult
{
Accepted,
NotOwnerOrAdmin,
NoSuchApplicant,
}

View File

@@ -0,0 +1,10 @@
namespace NadekoBot.Modules.Xp.Services;
public enum ClubBanResult
{
Success,
NotOwnerOrAdmin,
WrongUser,
Unbannable,
}

View File

@@ -0,0 +1,9 @@
namespace NadekoBot.Modules.Xp.Services;
public enum ClubCreateResult
{
Success,
AlreadyInAClub,
NameTaken,
InsufficientLevel,
}

View File

@@ -0,0 +1,9 @@
namespace NadekoBot.Modules.Xp.Services;
public enum ClubKickResult
{
Success,
NotOwnerOrAdmin,
TargetNotAMember,
Hierarchy
}

View File

@@ -0,0 +1,8 @@
namespace NadekoBot.Modules.Xp.Services;
public enum ClubLeaveResult
{
Success,
OwnerCantLeave,
NotInAClub
}

Some files were not shown because too many files have changed in this diff Show More