mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 09:18:27 -04:00
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e49e3eec69 | ||
|
3992ae392b | ||
|
8f0c5fab47 | ||
|
780a260b88 | ||
|
25692b9585 | ||
|
ed3ce52865 | ||
|
f5f0f1e250 | ||
|
9d9e61fdfb | ||
|
e68e948a80 | ||
|
cb98f4aa15 | ||
|
bfec0cbcbf | ||
|
3e1268f3bb | ||
|
c28f458972 | ||
|
27ac948463 | ||
|
3f9a3c4c18 | ||
|
9a5545a951 | ||
|
584193db18 | ||
|
1a132fd234 | ||
|
fd6a51ac82 | ||
|
eb1fabb2b7 | ||
|
d079e684bd | ||
|
bf817a1436 | ||
|
78f1624aaf | ||
|
793a49fc64 | ||
|
8b6be656b3 | ||
|
89a88304dc | ||
|
a7fe9ae08f | ||
|
0469705037 | ||
|
dc568fe0e2 | ||
|
eb01bb6c08 | ||
|
71a3539d0e | ||
|
c896a0cdb8 | ||
|
8effe817ad | ||
|
eedf6998b6 | ||
|
e6b7c31a72 | ||
|
2f77fd57b0 | ||
|
fda3d92134 | ||
|
fe6f28143b | ||
|
df3909fc55 |
@@ -104,7 +104,7 @@ publish-medusa-package:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
|
||||
script:
|
||||
- LAST_TAG=$(git describe --tags --abbrev=0)
|
||||
- if [ $CI_COMMIT_TAG ];then MEDUSA_VERSION="$CI_COMMIT_TAG"; else MEDUSA_VERSION="$LAST_TAG-$CI_COMMIT_SHA"; fi
|
||||
- if [ $CI_COMMIT_TAG ];then MEDUSA_VERSION="$CI_COMMIT_TAG"; else MEDUSA_VERSION="$LAST_TAG-$CI_COMMIT_SHORT_SHA"; fi
|
||||
- cd src/Nadeko.Medusa/
|
||||
- dotnet pack -c Release /p:Version=$MEDUSA_VERSION -o bin/Release/packed
|
||||
- dotnet nuget push bin/Release/packed/ --source https://www.myget.org/F/nadeko/api/v2/package --api-key "$MYGET_API_KEY"
|
||||
|
51
CHANGELOG.md
51
CHANGELOG.md
@@ -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
|
||||
|
||||
## 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
|
||||
|
||||
|
@@ -7,7 +7,7 @@ Open Terminal (if you don't know how to, click on the magnifying glass on the to
|
||||
###### Homebrew/wget
|
||||
*Skip this step if you already have homebrew installed*
|
||||
- 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
|
||||
- `brew install wget`
|
||||
|
||||
|
@@ -155,7 +155,7 @@ This section will guide you through how to create a simple custom medusa. You ca
|
||||
<ItemGroup>
|
||||
<!-- Base medusa package. You MUST reference this in order to have a working medusa -->
|
||||
<!-- Also, this package comes from MyGet, which requires you to have a NuGet.Config file next to your .csproj -->
|
||||
<PackageReference Include="Nadeko.Medusa" Version="1.0.1">
|
||||
<PackageReference Include="Nadeko.Medusa" Version="4.3.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -248,4 +248,4 @@ hello:
|
||||
- Unload it
|
||||
- `.meunload example_medusa`
|
||||
|
||||
- Congrats! You've just made your first medusa!
|
||||
- Congrats! You've just made your first medusa!
|
||||
|
@@ -17,4 +17,7 @@
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Version)' == '' ">
|
||||
<Version>5.0.0</Version>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
@@ -313,10 +313,29 @@ public sealed class Bot
|
||||
await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
||||
// await _interactionService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
||||
IsReady = true;
|
||||
|
||||
await EnsureBotOwnershipAsync();
|
||||
_ = Task.Run(ExecuteReadySubscriptions);
|
||||
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()
|
||||
{
|
||||
var readyExecutors = Services.GetServices<IReadyExecutor>();
|
||||
|
@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Configs;
|
||||
public sealed partial class BotConfig : ICloneable<BotConfig>
|
||||
{
|
||||
[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
|
||||
next to the response. The color depends whether the command
|
||||
@@ -39,6 +39,10 @@ Allowed values: Simple, Normal, None")]
|
||||
@"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
|
||||
or all owners? (this might cause the bot to lag if there's a lot of owners specified)")]
|
||||
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
|
||||
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.
|
||||
|
@@ -186,7 +186,6 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
||||
return MedusaLoadResult.AlreadyLoaded;
|
||||
|
||||
var safeName = Uri.EscapeDataString(name);
|
||||
name = name.ToLowerInvariant();
|
||||
|
||||
await _lock.WaitAsync();
|
||||
try
|
||||
@@ -525,7 +524,6 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private async Task<MedusaUnloadResult> InternalUnloadAsync(string name)
|
||||
{
|
||||
name = name.ToLowerInvariant();
|
||||
if (!_resolved.Remove(name, out var lsi))
|
||||
return MedusaUnloadResult.NotLoaded;
|
||||
|
||||
|
@@ -1,15 +1,14 @@
|
||||
#nullable disable
|
||||
using CommandLine;
|
||||
using CommandLine;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public static class OptionsParser
|
||||
{
|
||||
public static T ParseFrom<T>(string[] args)
|
||||
public static T ParseFrom<T>(string[]? args)
|
||||
where T : INadekoCommandOptions, new()
|
||||
=> 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
|
||||
{
|
||||
using var p = new Parser(x =>
|
||||
|
9
src/NadekoBot/Db/Models/GamblingStats.cs
Normal file
9
src/NadekoBot/Db/Models/GamblingStats.cs
Normal 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; }
|
||||
}
|
@@ -95,6 +95,8 @@ public class GuildConfig : DbEntity
|
||||
public int WarnExpireHours { get; set; }
|
||||
public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear;
|
||||
|
||||
public bool DisableGlobalExpressions { get; set; } = false;
|
||||
|
||||
#region Boost Message
|
||||
|
||||
public bool SendBoostMessage { get; set; }
|
||||
|
@@ -472,6 +472,14 @@ public abstract class NadekoContext : DbContext
|
||||
.IsUnique());
|
||||
|
||||
#endregion
|
||||
|
||||
#region GamblingStats
|
||||
|
||||
modelBuilder.Entity<GamblingStats>(gs => gs
|
||||
.HasIndex(x => x.Feature)
|
||||
.IsUnique());
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NadekoBot.Services.Database;
|
||||
|
||||
@@ -10,15 +11,45 @@ using NadekoBot.Services.Database;
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
[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
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.7")
|
||||
.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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1072,6 +1103,39 @@ namespace NadekoBot.Migrations.Mysql
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -2236,6 +2300,10 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("rolename");
|
||||
|
||||
b.Property<ulong?>("RoleRequirement")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("rolerequirement");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("type");
|
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
3616
src/NadekoBot/Migrations/Mysql/20221021192758_toggle-global-expressions.Designer.cs
generated
Normal file
3616
src/NadekoBot/Migrations/Mysql/20221021192758_toggle-global-expressions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1101,6 +1101,39 @@ namespace NadekoBot.Migrations.Mysql
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1225,6 +1258,10 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("deletestreamonlinemessage");
|
||||
|
||||
b.Property<bool>("DisableGlobalExpressions")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("disableglobalexpressions");
|
||||
|
||||
b.Property<string>("DmGreetMessageText")
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("dmgreetmessagetext");
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NadekoBot.Services.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
@@ -11,9 +12,10 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
[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
|
||||
modelBuilder
|
||||
@@ -22,6 +24,37 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1126,6 +1159,41 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -2342,6 +2410,10 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("rolename");
|
||||
|
||||
b.Property<decimal?>("RoleRequirement")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("rolerequirement");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
3764
src/NadekoBot/Migrations/PostgreSql/20221021192807_toggle-global-expressions.Designer.cs
generated
Normal file
3764
src/NadekoBot/Migrations/PostgreSql/20221021192807_toggle-global-expressions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class 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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1157,6 +1157,41 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1287,6 +1322,10 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("deletestreamonlinemessage");
|
||||
|
||||
b.Property<bool>("DisableGlobalExpressions")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("disableglobalexpressions");
|
||||
|
||||
b.Property<string>("DmGreetMessageText")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("dmgreetmessagetext");
|
||||
|
2898
src/NadekoBot/Migrations/Sqlite/20221003111019_gambling-stats.Designer.cs
generated
Normal file
2898
src/NadekoBot/Migrations/Sqlite/20221003111019_gambling-stats.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
2901
src/NadekoBot/Migrations/Sqlite/20221021192121_toggle-global-expressions.Designer.cs
generated
Normal file
2901
src/NadekoBot/Migrations/Sqlite/20221021192121_toggle-global-expressions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -864,6 +864,32 @@ namespace NadekoBot.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -960,6 +986,9 @@ namespace NadekoBot.Migrations
|
||||
b.Property<bool>("DeleteStreamOnlineMessage")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DisableGlobalExpressions")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DmGreetMessageText")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
@@ -72,7 +72,7 @@ public partial class Administration
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
[NadekoOptions(typeof(PruneOptions))]
|
||||
[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 userid [x]
|
||||
|
@@ -230,6 +230,19 @@ public partial class Administration
|
||||
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]
|
||||
public async Task ShardStats(int page = 1)
|
||||
{
|
||||
|
@@ -85,12 +85,12 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
autoCommands = uow.AutoCommands.AsNoTracking()
|
||||
.Where(x => x.Interval >= 5)
|
||||
.AsEnumerable()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key,
|
||||
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
|
||||
.ToConcurrent();
|
||||
.Where(x => x.Interval >= 5)
|
||||
.AsEnumerable()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key,
|
||||
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
|
||||
.ToConcurrent();
|
||||
|
||||
var startupCommands = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
|
||||
foreach (var cmd in startupCommands)
|
||||
@@ -169,18 +169,18 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
||||
private async Task LoadOwnerChannels()
|
||||
{
|
||||
var channels = await _creds.OwnerIds.Select(id =>
|
||||
{
|
||||
var user = _client.GetUser(id);
|
||||
if (user is null)
|
||||
return Task.FromResult<IDMChannel>(null);
|
||||
{
|
||||
var user = _client.GetUser(id);
|
||||
if (user is null)
|
||||
return Task.FromResult<IDMChannel>(null);
|
||||
|
||||
return user.CreateDMChannelAsync();
|
||||
})
|
||||
.WhenAll();
|
||||
return user.CreateDMChannelAsync();
|
||||
})
|
||||
.WhenAll();
|
||||
|
||||
ownerChannels = channels.Where(x => x is not null)
|
||||
.ToDictionary(x => x.Recipient.Id, x => x)
|
||||
.ToImmutableDictionary();
|
||||
.ToDictionary(x => x.Recipient.Id, x => x)
|
||||
.ToImmutableDictionary();
|
||||
|
||||
if (!ownerChannels.Any())
|
||||
{
|
||||
@@ -202,7 +202,7 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
||||
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
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})";
|
||||
|
||||
@@ -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
|
||||
{
|
||||
var firstOwnerChannel = ownerChannels.Values.First();
|
||||
@@ -333,6 +345,20 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
||||
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()
|
||||
=> _pubSub.Sub(_activitySetKey,
|
||||
async data =>
|
||||
|
@@ -41,6 +41,17 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
|
||||
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]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ExprAddServer(string key, [Leftover] string message)
|
@@ -7,6 +7,7 @@ using NadekoBot.Modules.Permissions.Common;
|
||||
using NadekoBot.Modules.Permissions.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Nadeko.Common;
|
||||
using YamlDotNet.Serialization;
|
||||
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)
|
||||
// 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)
|
||||
private NadekoExpression[] globalReactions;
|
||||
private ConcurrentDictionary<ulong, NadekoExpression[]> newGuildReactions;
|
||||
private NadekoExpression[] globalExpressions;
|
||||
private ConcurrentDictionary<ulong, NadekoExpression[]> newguildExpressions;
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
@@ -72,6 +73,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
private readonly Random _rng;
|
||||
|
||||
private bool ready;
|
||||
private ConcurrentHashSet<ulong> _disabledGlobalExpressionGuilds;
|
||||
|
||||
public NadekoExpressionsService(
|
||||
PermissionService perms,
|
||||
@@ -113,7 +115,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
.Where(x => allGuildIds.Contains(x.GuildId.Value))
|
||||
.ToListAsync();
|
||||
|
||||
newGuildReactions = guildItems.GroupBy(k => k.GuildId!.Value)
|
||||
newguildExpressions = guildItems.GroupBy(k => k.GuildId!.Value)
|
||||
.ToDictionary(g => g.Key,
|
||||
g => g.Select(x =>
|
||||
{
|
||||
@@ -123,6 +125,11 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
.ToArray())
|
||||
.ToConcurrent();
|
||||
|
||||
_disabledGlobalExpressionGuilds = new (await uow.GuildConfigs
|
||||
.Where(x => x.DisableGlobalExpressions)
|
||||
.Select(x => x.GuildId)
|
||||
.ToListAsyncLinqToDB());
|
||||
|
||||
lock (_gexprWriteLock)
|
||||
{
|
||||
var globalItems = uow.Expressions.AsNoTracking()
|
||||
@@ -135,7 +142,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
globalReactions = globalItems;
|
||||
globalExpressions = globalItems;
|
||||
}
|
||||
|
||||
ready = true;
|
||||
@@ -151,14 +158,17 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
|
||||
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)
|
||||
return expr;
|
||||
}
|
||||
|
||||
var localGrs = globalReactions;
|
||||
if (_disabledGlobalExpressionGuilds.Contains(channel.Guild.Id))
|
||||
return null;
|
||||
|
||||
var localGrs = globalExpressions;
|
||||
|
||||
return MatchExpressions(content, localGrs);
|
||||
}
|
||||
@@ -345,7 +355,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
{
|
||||
if (maybeGuildId is { } guildId)
|
||||
{
|
||||
newGuildReactions.AddOrUpdate(guildId,
|
||||
newguildExpressions.AddOrUpdate(guildId,
|
||||
new[] { expr },
|
||||
(_, old) =>
|
||||
{
|
||||
@@ -363,7 +373,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
{
|
||||
lock (_gexprWriteLock)
|
||||
{
|
||||
var exprs = globalReactions;
|
||||
var exprs = globalExpressions;
|
||||
for (var i = 0; i < exprs.Length; i++)
|
||||
{
|
||||
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);
|
||||
|
||||
if (maybeGuildId is { } guildId)
|
||||
newGuildReactions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr));
|
||||
newguildExpressions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr));
|
||||
else
|
||||
return _pubSub.Pub(_gexprAddedKey, expr);
|
||||
|
||||
@@ -390,7 +400,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
{
|
||||
if (maybeGuildId is { } guildId)
|
||||
{
|
||||
newGuildReactions.AddOrUpdate(guildId,
|
||||
newguildExpressions.AddOrUpdate(guildId,
|
||||
Array.Empty<NadekoExpression>(),
|
||||
(key, old) => DeleteInternal(old, id, out _));
|
||||
|
||||
@@ -399,7 +409,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
|
||||
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)
|
||||
return _pubSub.Pub(_gexprDeletedkey, expr.Id);
|
||||
}
|
||||
@@ -492,7 +502,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
var count = uow.Expressions.ClearFromGuild(guildId);
|
||||
uow.SaveChanges();
|
||||
|
||||
newGuildReactions.TryRemove(guildId, out _);
|
||||
newguildExpressions.TryRemove(guildId, out _);
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -562,10 +572,10 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
{
|
||||
lock (_gexprWriteLock)
|
||||
{
|
||||
var newGlobalReactions = new NadekoExpression[globalReactions.Length + 1];
|
||||
Array.Copy(globalReactions, newGlobalReactions, globalReactions.Length);
|
||||
newGlobalReactions[globalReactions.Length] = c;
|
||||
globalReactions = newGlobalReactions;
|
||||
var newGlobalReactions = new NadekoExpression[globalExpressions.Length + 1];
|
||||
Array.Copy(globalExpressions, newGlobalReactions, globalExpressions.Length);
|
||||
newGlobalReactions[globalExpressions.Length] = c;
|
||||
globalExpressions = newGlobalReactions;
|
||||
}
|
||||
|
||||
return default;
|
||||
@@ -575,11 +585,11 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -596,8 +606,8 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
{
|
||||
lock (_gexprWriteLock)
|
||||
{
|
||||
var newGlobalReactions = DeleteInternal(globalReactions, id, out _);
|
||||
globalReactions = newGlobalReactions;
|
||||
var newGlobalReactions = DeleteInternal(globalExpressions, id, out _);
|
||||
globalExpressions = newGlobalReactions;
|
||||
}
|
||||
|
||||
return default;
|
||||
@@ -612,7 +622,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
|
||||
private Task OnLeftGuild(SocketGuild arg)
|
||||
{
|
||||
newGuildReactions.TryRemove(arg.Id, out _);
|
||||
newguildExpressions.TryRemove(arg.Id, out _);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -622,7 +632,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
await using var uow = _db.GetDbContext();
|
||||
var exprs = await uow.Expressions.AsNoTracking().Where(x => x.GuildId == gc.GuildId).ToArrayAsync();
|
||||
|
||||
newGuildReactions[gc.GuildId] = exprs;
|
||||
newguildExpressions[gc.GuildId] = exprs;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -702,10 +712,25 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
public NadekoExpression[] GetExpressionsFor(ulong? maybeGuildId)
|
||||
{
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@@ -29,6 +29,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
private readonly IBankService _bank;
|
||||
private readonly IPatronageService _ps;
|
||||
private readonly RemindService _remind;
|
||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||
|
||||
private IUserMessage rdMsg;
|
||||
|
||||
@@ -41,7 +42,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
GamblingConfigService configService,
|
||||
IBankService bank,
|
||||
IPatronageService ps,
|
||||
RemindService remind)
|
||||
RemindService remind,
|
||||
GamblingTxTracker gamblingTxTracker)
|
||||
: base(configService)
|
||||
{
|
||||
_gs = gs;
|
||||
@@ -51,6 +53,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
_bank = bank;
|
||||
_ps = ps;
|
||||
_remind = remind;
|
||||
_gamblingTxTracker = gamblingTxTracker;
|
||||
|
||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||
_enUsCulture.NumberDecimalDigits = 0;
|
||||
@@ -65,6 +68,43 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
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]
|
||||
public async Task Economy()
|
||||
{
|
||||
@@ -79,15 +119,15 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
// [21:03] Bob Page: Kinda remids me of US economy
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.economy_state))
|
||||
.AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
|
||||
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
|
||||
.AddField(GetText(strs.currency_planted), N(ec.Planted))
|
||||
.AddField(GetText(strs.owned_waifus_total), N(ec.Waifus))
|
||||
.AddField(GetText(strs.bot_currency), N(ec.Bot))
|
||||
.AddField(GetText(strs.bank_accounts), N(ec.Bank))
|
||||
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank))
|
||||
.WithOkColor();
|
||||
.WithTitle(GetText(strs.economy_state))
|
||||
.AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
|
||||
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
|
||||
.AddField(GetText(strs.currency_planted), N(ec.Planted))
|
||||
.AddField(GetText(strs.owned_waifus_total), N(ec.Waifus))
|
||||
.AddField(GetText(strs.bot_currency), N(ec.Bot))
|
||||
.AddField(GetText(strs.bank_accounts), N(ec.Bank))
|
||||
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank))
|
||||
.WithOkColor();
|
||||
|
||||
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
@@ -105,7 +145,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
await _remind.AddReminderAsync(ctx.User.Id,
|
||||
ctx.User.Id,
|
||||
ctx.Guild.Id,
|
||||
ctx.Guild?.Id,
|
||||
true,
|
||||
when,
|
||||
GetText(strs.timely_time));
|
||||
@@ -253,9 +293,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
||||
?? $"{userId}")))
|
||||
.WithOkColor();
|
||||
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
||||
?? $"{userId}")))
|
||||
.WithOkColor();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var tr in trs)
|
||||
@@ -292,8 +332,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
var tr = await uow.CurrencyTransactions.ToLinqToDBTable()
|
||||
.Where(x => x.Id == intId && x.UserId == ctx.User.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
.Where(x => x.Id == intId && x.UserId == ctx.User.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (tr is null)
|
||||
{
|
||||
@@ -340,7 +380,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
(_, _, ulong userId) => $"{type.Titleize()} - {subType.Titleize()} | [{userId}]",
|
||||
_ => $"{type.Titleize()} - {subType.Titleize()}"
|
||||
};
|
||||
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
public async Task Cash(ulong userId)
|
||||
@@ -354,15 +394,15 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
var balance = await _bank.GetBalanceAsync(ctx.User.Id);
|
||||
|
||||
await N(balance)
|
||||
.Pipe(strs.bank_balance)
|
||||
.Pipe(GetText)
|
||||
.Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true));
|
||||
.Pipe(strs.bank_balance)
|
||||
.Pipe(GetText)
|
||||
.Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true));
|
||||
}
|
||||
|
||||
private NadekoInteraction CreateCashInteraction()
|
||||
=> _inter.Create<object>(ctx.User.Id,
|
||||
new(new(
|
||||
customId: "cash:bank_show_balance",
|
||||
customId: "cash:bank_show_balance",
|
||||
emote: new Emoji("🏦")),
|
||||
BankAction));
|
||||
|
||||
@@ -372,7 +412,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
{
|
||||
user ??= ctx.User;
|
||||
var cur = await GetBalanceStringAsync(user.Id);
|
||||
|
||||
|
||||
var inter = user == ctx.User
|
||||
? CreateCashInteraction()
|
||||
: null;
|
||||
@@ -648,7 +688,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var win = (long)result.Won;
|
||||
string str;
|
||||
if (win > 0)
|
||||
@@ -762,7 +802,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
S = 2,
|
||||
Scissors = 2
|
||||
}
|
||||
|
||||
|
||||
[Cmd]
|
||||
public async Task Rps(InputRpsPick pick, ShmartNumber amount = default)
|
||||
{
|
||||
@@ -778,7 +818,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return "✂️";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!await CheckBetOptional(amount) || amount == 1)
|
||||
return;
|
||||
|
||||
@@ -789,9 +829,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var embed = _eb.Create();
|
||||
|
||||
|
||||
string msg;
|
||||
if (result.Result == RpsResultType.Draw)
|
||||
{
|
||||
@@ -799,8 +839,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
else if (result.Result == RpsResultType.Win)
|
||||
{
|
||||
if((long)result.Won > 0)
|
||||
embed.AddField(GetText(strs.won), N(amount.Value));
|
||||
if ((long)result.Won > 0)
|
||||
embed.AddField(GetText(strs.won), N(amount.Value));
|
||||
|
||||
msg = GetText(strs.rps_win(ctx.User.Mention,
|
||||
GetRpsPick(pick),
|
||||
@@ -819,7 +859,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
|
||||
private static readonly ImmutableArray<string> _emojis =
|
||||
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
|
||||
|
||||
@@ -847,7 +887,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
sb.Append($"{Format.Bold($"x{multi:0.##}")} ⬅️");
|
||||
else
|
||||
sb.Append($"||x{multi:0.##}||");
|
||||
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
@@ -861,101 +901,100 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
|
||||
|
||||
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(", "));
|
||||
}
|
||||
public enum GambleTestTarget
|
||||
{
|
||||
Slot,
|
||||
Betroll,
|
||||
Betflip,
|
||||
BetflipT,
|
||||
BetDraw,
|
||||
BetDrawHL,
|
||||
BetDrawRB,
|
||||
Lula,
|
||||
Rps,
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task BetTest(GambleTestTarget target, int tests = 1000)
|
||||
[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++)
|
||||
{
|
||||
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
|
||||
{
|
||||
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.BetDrawHL => (await _gs.BetDrawAsync(ctx.User.Id, 0, 0, null)).AsT0.Multiplier,
|
||||
GambleTestTarget.Slot => (await _gs.SlotAsync(ctx.User.Id, 0)).AsT0.Multiplier,
|
||||
GambleTestTarget.Betflip => (await _gs.BetFlipAsync(ctx.User.Id, 0, 0)).AsT0.Multiplier,
|
||||
GambleTestTarget.BetflipT => (await _gs.BetFlipAsync(ctx.User.Id, 0, 1)).AsT0.Multiplier,
|
||||
GambleTestTarget.Lula => (await _gs.LulaAsync(ctx.User.Id, 0)).AsT0.Multiplier,
|
||||
GambleTestTarget.Rps => (await _gs.RpsAsync(ctx.User.Id, 0, (byte)(i % 3))).AsT0.Multiplier,
|
||||
GambleTestTarget.Betroll => (await _gs.BetRollAsync(ctx.User.Id, 0)).AsT0.Multiplier,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(target))
|
||||
};
|
||||
|
||||
if (dict.ContainsKey(multi))
|
||||
dict[multi] += 1;
|
||||
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.BetDrawHL => (await _gs.BetDrawAsync(ctx.User.Id, 0, 0, null)).AsT0.Multiplier,
|
||||
GambleTestTarget.Slot => (await _gs.SlotAsync(ctx.User.Id, 0)).AsT0.Multiplier,
|
||||
GambleTestTarget.Betflip => (await _gs.BetFlipAsync(ctx.User.Id, 0, 0)).AsT0.Multiplier,
|
||||
GambleTestTarget.BetflipT => (await _gs.BetFlipAsync(ctx.User.Id, 0, 1)).AsT0.Multiplier,
|
||||
GambleTestTarget.Lula => (await _gs.LulaAsync(ctx.User.Id, 0)).AsT0.Multiplier,
|
||||
GambleTestTarget.Rps => (await _gs.RpsAsync(ctx.User.Id, 0, (byte)(i % 3))).AsT0.Multiplier,
|
||||
GambleTestTarget.Betroll => (await _gs.BetRollAsync(ctx.User.Id, 0)).AsT0.Multiplier,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(target))
|
||||
};
|
||||
|
||||
if (dict.ContainsKey(multi))
|
||||
dict[multi] += 1;
|
||||
else
|
||||
dict.Add(multi, 1);
|
||||
|
||||
if (multi < 1)
|
||||
{
|
||||
if (streak <= 0)
|
||||
--streak;
|
||||
else
|
||||
dict.Add(multi, 1);
|
||||
streak = -1;
|
||||
|
||||
if (multi < 1)
|
||||
{
|
||||
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);
|
||||
}
|
||||
maxL = Math.Max(maxL, -streak);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
decimal payout = 0;
|
||||
foreach (var key in dict.Keys.OrderByDescending(x => x))
|
||||
else if (multi > 1)
|
||||
{
|
||||
sb.AppendLine($"x**{key}** occured `{dict[key]}` times. {dict[key] * 1.0f / tests * 100}%");
|
||||
payout += key * dict[key];
|
||||
}
|
||||
if (streak >= 0)
|
||||
++streak;
|
||||
else
|
||||
streak = 1;
|
||||
|
||||
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}%");
|
||||
maxW = Math.Max(maxW, streak);
|
||||
}
|
||||
}
|
||||
|
||||
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}%");
|
||||
}
|
||||
}
|
@@ -47,27 +47,6 @@ public partial class Gambling
|
||||
public Task Test()
|
||||
=> 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]
|
||||
public async Task Slot(ShmartNumber amount)
|
||||
{
|
||||
|
@@ -126,7 +126,7 @@ public class ChatterBotService : IExecOnMessage
|
||||
Log.Information("{PermissionMessage}", returnMsg);
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await _ccs.TryBlock(sg, usrMsg.Author, CleverBotResponseStr.CLEVERBOT_RESPONSE))
|
||||
|
@@ -88,11 +88,11 @@ public partial class Help : NadekoModule<HelpService>
|
||||
embed = embed.WithOkColor().WithDescription(GetText(strs.module_page_empty));
|
||||
return embed;
|
||||
}
|
||||
|
||||
|
||||
localModules.OrderBy(module => module.Name)
|
||||
.ToList()
|
||||
.ForEach(module => embed.AddField($"{GetModuleEmoji(module.Name)} {module.Name}",
|
||||
GetText(GetModuleLocStr(module.Name))
|
||||
GetModuleDescription(module.Name)
|
||||
+ "\n"
|
||||
+ Format.Code(GetText(strs.module_footer(prefix, module.Name.ToLowerInvariant()))),
|
||||
true));
|
||||
@@ -104,6 +104,25 @@ public partial class Help : NadekoModule<HelpService>
|
||||
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)
|
||||
{
|
||||
switch (moduleName.ToLowerInvariant())
|
||||
|
@@ -1,10 +1,12 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
|
||||
namespace NadekoBot.Modules.Permissions.Services;
|
||||
|
||||
public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, int>> _settings = new();
|
||||
|
||||
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 CmdCdService(Bot bot)
|
||||
public CmdCdService(Bot bot, DbService db)
|
||||
{
|
||||
_db = db;
|
||||
_settings = bot
|
||||
.AllGuildConfigs
|
||||
.ToDictionary(x => x.GuildId, x => x.CommandCooldowns
|
||||
.DistinctBy(x => x.CommandName.ToLowerInvariant())
|
||||
.ToDictionary(c => c.CommandName, c => c.Seconds)
|
||||
.ToConcurrent())
|
||||
.ToConcurrent();
|
||||
@@ -25,13 +29,16 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
|
||||
public Task<bool> ExecPreCommandAsync(ICommandContext context, string moduleName, CommandInfo command)
|
||||
=> 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))
|
||||
return false;
|
||||
return Task.FromResult(false);
|
||||
|
||||
if (!cooldownSettings.TryGetValue(commandName, out var cdSeconds))
|
||||
return false;
|
||||
return Task.FromResult(false);
|
||||
|
||||
var cooldowns = _activeCooldowns.GetOrAdd(
|
||||
(guild.Id, commandName),
|
||||
@@ -40,7 +47,7 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
|
||||
// if user is not already on cooldown, add
|
||||
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
|
||||
@@ -48,14 +55,14 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
|
||||
if (cooldowns.TryGetValue(user.Id, out var oldValue))
|
||||
{
|
||||
var diff = DateTime.UtcNow - oldValue;
|
||||
if (diff.Seconds > cdSeconds)
|
||||
if (diff.TotalSeconds > cdSeconds)
|
||||
{
|
||||
if (cooldowns.TryUpdate(user.Id, DateTime.UtcNow, oldValue))
|
||||
return false;
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
@@ -64,7 +71,6 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
|
||||
|
||||
while (await timer.WaitForNextTickAsync())
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
// once per hour delete expired entries
|
||||
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)
|
||||
{
|
||||
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 _);
|
||||
}
|
||||
@@ -97,16 +103,34 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
|
||||
dict.TryRemove(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)
|
||||
{
|
||||
if (secs <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(secs));
|
||||
|
||||
var sett = _settings.GetOrAdd(guildId, static _ => new());
|
||||
sett[name] = secs;
|
||||
|
||||
// force cleanup
|
||||
if (_activeCooldowns.TryGetValue((guildId, name), out var dict))
|
||||
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)
|
||||
|
@@ -25,6 +25,47 @@ public partial class Permissions
|
||||
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]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task SrvrFilterInv()
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#nullable disable
|
||||
using AngleSharp.Dom;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
@@ -222,4 +223,20 @@ public sealed class FilterService : IExecOnMessage
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
@@ -32,33 +32,43 @@ public partial class Searches
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task Feed(string url, [Leftover] ITextChannel channel = null)
|
||||
{
|
||||
var success = Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||
if (success)
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||
|| (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
|
||||
{
|
||||
channel ??= (ITextChannel)ctx.Channel;
|
||||
try
|
||||
{
|
||||
await FeedReader.ReadAsync(url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information(ex, "Unable to get feeds from that url");
|
||||
success = false;
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.feed_invalid_url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (success)
|
||||
channel ??= (ITextChannel)ctx.Channel;
|
||||
try
|
||||
{
|
||||
success = _service.AddFeed(ctx.Guild.Id, channel.Id, url);
|
||||
if (success)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.feed_added);
|
||||
return;
|
||||
}
|
||||
await FeedReader.ReadAsync(url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
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]
|
||||
|
@@ -207,7 +207,7 @@ public class FeedsService : INService
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public bool AddFeed(ulong guildId, ulong channelId, string rssFeed)
|
||||
public FeedAddResult AddFeed(ulong guildId, ulong channelId, string 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));
|
||||
|
||||
if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower()))
|
||||
return false;
|
||||
return FeedAddResult.Duplicate;
|
||||
if (gc.FeedSubs.Count >= 10)
|
||||
return false;
|
||||
return FeedAddResult.LimitReached;
|
||||
|
||||
gc.FeedSubs.Add(fs);
|
||||
uow.SaveChanges();
|
||||
@@ -242,7 +242,7 @@ public class FeedsService : INService
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
return FeedAddResult.Success;
|
||||
}
|
||||
|
||||
public bool RemoveFeed(ulong guildId, int index)
|
||||
@@ -270,4 +270,12 @@ public class FeedsService : INService
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FeedAddResult
|
||||
{
|
||||
Success,
|
||||
LimitReached,
|
||||
Invalid,
|
||||
Duplicate,
|
||||
}
|
@@ -9,7 +9,7 @@ namespace NadekoBot.Modules.Utility;
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public partial class CommandMapCommands : NadekoModule<CommandMapService>
|
||||
public partial class CommandMapCommands : NadekoModule<AliasService>
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
@@ -6,15 +6,14 @@ using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services;
|
||||
|
||||
public class CommandMapService : IInputTransformer, INService
|
||||
public class AliasService : IInputTransformer, INService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new();
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
private readonly DbService _db;
|
||||
|
||||
//commandmap
|
||||
public CommandMapService(DiscordSocketClient client, DbService db, IEmbedBuilderService eb)
|
||||
public AliasService(DiscordSocketClient client, DbService db, IEmbedBuilderService eb)
|
||||
{
|
||||
_eb = eb;
|
||||
|
||||
@@ -66,7 +65,10 @@ public class CommandMapService : IInputTransformer, INService
|
||||
}
|
||||
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)
|
||||
@@ -74,17 +76,11 @@ public class CommandMapService : IInputTransformer, INService
|
||||
try
|
||||
{
|
||||
var toDelete = await channel.SendConfirmAsync(_eb, $"{input} => {newInput}");
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1500);
|
||||
await toDelete.DeleteAsync(new()
|
||||
{
|
||||
RetryMode = RetryMode.AlwaysRetry
|
||||
});
|
||||
});
|
||||
toDelete.DeleteAfter(1.5f);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return newInput;
|
||||
@@ -92,34 +88,6 @@ public class CommandMapService : IInputTransformer, INService
|
||||
}
|
||||
|
||||
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;
|
@@ -1,24 +1,24 @@
|
||||
#nullable disable
|
||||
using System.Globalization;
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
using Nadeko.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services;
|
||||
|
||||
public class RemindService : INService, IReadyExecutor
|
||||
{
|
||||
private readonly Regex _regex =
|
||||
new(
|
||||
@"^(?: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]|.)+)",
|
||||
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]|.)+)",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
private readonly CultureInfo _culture;
|
||||
|
||||
public RemindService(
|
||||
DiscordSocketClient client,
|
||||
@@ -30,6 +30,15 @@ public class RemindService : INService, IReadyExecutor
|
||||
_db = db;
|
||||
_creds = creds;
|
||||
_eb = eb;
|
||||
|
||||
try
|
||||
{
|
||||
_culture = new CultureInfo("en-GB");
|
||||
}
|
||||
catch
|
||||
{
|
||||
_culture = CultureInfo.InvariantCulture;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
@@ -105,32 +114,57 @@ public class RemindService : INService, IReadyExecutor
|
||||
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")
|
||||
continue;
|
||||
if (string.IsNullOrWhiteSpace(m.Groups[groupName].Value))
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
if (!DateTime.TryParse(dateString, _culture, DateTimeStyles.None, out var dt))
|
||||
{
|
||||
values[groupName] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!int.TryParse(m.Groups[groupName].Value, out var value))
|
||||
{
|
||||
Log.Warning("Reminder regex group {GroupName} has invalid value", groupName);
|
||||
Log.Warning("Invalid remind datetime format");
|
||||
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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
|
@@ -17,11 +17,14 @@ public partial class Xp
|
||||
[Cmd]
|
||||
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
|
||||
{
|
||||
@@ -37,15 +40,20 @@ public partial class Xp
|
||||
[Cmd]
|
||||
public async Task ClubAdmin([Leftover] IUser toAdmin)
|
||||
{
|
||||
var admin = await _service.ToggleAdminAsync(ctx.User, toAdmin);
|
||||
|
||||
if (admin is null)
|
||||
await ReplyErrorLocalizedAsync(strs.club_admin_error);
|
||||
else if (admin is true)
|
||||
var result = await _service.ToggleAdminAsync(ctx.User, toAdmin);
|
||||
|
||||
if (result == ToggleAdminResult.AddedAdmin)
|
||||
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())));
|
||||
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]
|
||||
public async Task ClubCreate([Leftover] string clubName)
|
||||
{
|
||||
@@ -55,17 +63,23 @@ public partial class Xp
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -75,14 +89,21 @@ public partial class Xp
|
||||
[Cmd]
|
||||
public async Task ClubIcon([Leftover] string url = null)
|
||||
{
|
||||
if ((!Uri.IsWellFormedUriString(url, UriKind.Absolute) && url is not null)
|
||||
|| !await _service.SetClubIconAsync(ctx.User.Id, url))
|
||||
if ((!Uri.IsWellFormedUriString(url, UriKind.Absolute) && url is not null))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.club_icon_error);
|
||||
await ReplyErrorLocalizedAsync(strs.club_icon_url_format);
|
||||
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)
|
||||
@@ -102,27 +123,27 @@ public partial class Xp
|
||||
page =>
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle($"{club}")
|
||||
.WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)")))
|
||||
.AddField(GetText(strs.desc),
|
||||
string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description)
|
||||
.AddField(GetText(strs.owner), club.Owner.ToString(), true)
|
||||
// .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true)
|
||||
.AddField(GetText(strs.members),
|
||||
string.Join("\n",
|
||||
users.Skip(page * 10)
|
||||
.Take(10)
|
||||
.Select(x =>
|
||||
{
|
||||
var l = new LevelStats(x.TotalXp);
|
||||
var lvlStr = Format.Bold($" ⟪{l.Level}⟫");
|
||||
if (club.OwnerId == x.Id)
|
||||
return x + "🌟" + lvlStr;
|
||||
if (x.IsClubAdmin)
|
||||
return x + "⭐" + lvlStr;
|
||||
return x + lvlStr;
|
||||
})));
|
||||
.WithOkColor()
|
||||
.WithTitle($"{club}")
|
||||
.WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)")))
|
||||
.AddField(GetText(strs.desc),
|
||||
string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description)
|
||||
.AddField(GetText(strs.owner), club.Owner.ToString(), true)
|
||||
// .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true)
|
||||
.AddField(GetText(strs.members),
|
||||
string.Join("\n",
|
||||
users.Skip(page * 10)
|
||||
.Take(10)
|
||||
.Select(x =>
|
||||
{
|
||||
var l = new LevelStats(x.TotalXp);
|
||||
var lvlStr = Format.Bold($" ⟪{l.Level}⟫");
|
||||
if (club.OwnerId == x.Id)
|
||||
return x + "🌟" + lvlStr;
|
||||
if (x.IsClubAdmin)
|
||||
return x + "⭐" + lvlStr;
|
||||
return x + lvlStr;
|
||||
})));
|
||||
|
||||
if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute))
|
||||
return embed.WithThumbnailUrl(club.ImageUrl);
|
||||
@@ -175,19 +196,21 @@ public partial class Xp
|
||||
|
||||
var club = _service.GetClubWithBansAndApplications(ctx.User.Id);
|
||||
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();
|
||||
|
||||
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()
|
||||
.WithTitle(GetText(strs.club_bans_for(club.ToString())))
|
||||
.WithDescription(toShow)
|
||||
.WithOkColor();
|
||||
.WithTitle(GetText(strs.club_bans_for(club.ToString())))
|
||||
.WithDescription(toShow)
|
||||
.WithOkColor();
|
||||
},
|
||||
bans.Length,
|
||||
10);
|
||||
@@ -201,7 +224,7 @@ public partial class Xp
|
||||
|
||||
var club = _service.GetClubWithBansAndApplications(ctx.User.Id);
|
||||
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();
|
||||
|
||||
@@ -211,9 +234,9 @@ public partial class Xp
|
||||
var toShow = string.Join("\n", apps.Skip(page * 10).Take(10).Select(x => x.ToString()));
|
||||
|
||||
return _eb.Create()
|
||||
.WithTitle(GetText(strs.club_apps_for(club.ToString())))
|
||||
.WithDescription(toShow)
|
||||
.WithOkColor();
|
||||
.WithTitle(GetText(strs.club_apps_for(club.ToString())))
|
||||
.WithDescription(toShow)
|
||||
.WithOkColor();
|
||||
},
|
||||
apps.Length,
|
||||
10);
|
||||
@@ -231,10 +254,15 @@ public partial class Xp
|
||||
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())));
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.club_apply_error);
|
||||
else if (result == ClubApplyResult.Banned)
|
||||
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]
|
||||
@@ -246,19 +274,26 @@ public partial class Xp
|
||||
[Priority(0)]
|
||||
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())));
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.club_accept_error);
|
||||
else if(result == ClubAcceptResult.NoSuchApplicant)
|
||||
await ReplyErrorLocalizedAsync(strs.club_accept_invalid_applicant);
|
||||
else if(result == ClubAcceptResult.NotOwnerOrAdmin)
|
||||
await ReplyErrorLocalizedAsync(strs.club_admin_perms);
|
||||
}
|
||||
|
||||
[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);
|
||||
else if (res == ClubLeaveResult.NotInAClub)
|
||||
await ReplyErrorLocalizedAsync(strs.club_not_in_a_club);
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.club_not_in_club);
|
||||
await ReplyErrorLocalizedAsync(strs.club_owner_cant_leave);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
@@ -270,13 +305,20 @@ public partial class Xp
|
||||
[Priority(0)]
|
||||
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),
|
||||
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]
|
||||
@@ -288,13 +330,20 @@ public partial class Xp
|
||||
[Priority(0)]
|
||||
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),
|
||||
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]
|
||||
@@ -306,13 +355,20 @@ public partial class Xp
|
||||
[Priority(0)]
|
||||
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),
|
||||
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]
|
||||
@@ -323,12 +379,12 @@ public partial class Xp
|
||||
desc = string.IsNullOrWhiteSpace(desc)
|
||||
? "-"
|
||||
: desc;
|
||||
|
||||
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithAuthor(ctx.User)
|
||||
.WithTitle(GetText(strs.club_desc_update))
|
||||
.WithOkColor()
|
||||
.WithDescription(desc);
|
||||
.WithAuthor(ctx.User)
|
||||
.WithTitle(GetText(strs.club_desc_update))
|
||||
.WithOkColor()
|
||||
.WithDescription(desc);
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
|
@@ -2,9 +2,9 @@
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Nadeko.Common;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using OneOf;
|
||||
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
@@ -19,7 +19,8 @@ public class ClubService : INService, IClubService
|
||||
_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
|
||||
|
||||
@@ -27,11 +28,14 @@ public class ClubService : INService, IClubService
|
||||
var du = uow.GetOrCreateUser(user);
|
||||
var xp = new LevelStats(du.TotalXp);
|
||||
|
||||
if (xp.Level < 5 || du.ClubId is not null)
|
||||
return false;
|
||||
if (xp.Level < 5)
|
||||
return ClubCreateResult.InsufficientLevel;
|
||||
|
||||
if (du.ClubId is not null)
|
||||
return ClubCreateResult.AlreadyInAClub;
|
||||
|
||||
if (await uow.Clubs.AnyAsyncEF(x => x.Name == clubName))
|
||||
return null;
|
||||
return ClubCreateResult.NameTaken;
|
||||
|
||||
du.IsClubAdmin = true;
|
||||
du.Club = new()
|
||||
@@ -45,17 +49,20 @@ public class ClubService : INService, IClubService
|
||||
await uow.GetTable<ClubApplicants>()
|
||||
.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();
|
||||
var club = uow.Clubs.GetByOwner(@from.Id);
|
||||
var newOwnerUser = uow.GetOrCreateUser(newOwner);
|
||||
|
||||
if (club is null || club.Owner.UserId != from.Id || !club.Members.Contains(newOwnerUser))
|
||||
return null;
|
||||
if (club is null || club.Owner.UserId != from.Id)
|
||||
return ClubTransferError.NotOwner;
|
||||
|
||||
if (!club.Members.Contains(newOwnerUser))
|
||||
return ClubTransferError.TargetNotMember;
|
||||
|
||||
club.Owner.IsClubAdmin = true; // old owner will stay as admin
|
||||
newOwnerUser.IsClubAdmin = true;
|
||||
@@ -63,22 +70,25 @@ public class ClubService : INService, IClubService
|
||||
uow.SaveChanges();
|
||||
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();
|
||||
var club = uow.Clubs.GetByOwner(owner.Id);
|
||||
var adminUser = uow.GetOrCreateUser(toAdmin);
|
||||
|
||||
if (club is null || club.Owner.UserId != owner.Id || !club.Members.Contains(adminUser))
|
||||
return null;
|
||||
|
||||
if (club.OwnerId == adminUser.Id)
|
||||
return true;
|
||||
|
||||
if (club is null)
|
||||
return ToggleAdminResult.NotOwner;
|
||||
|
||||
if(!club.Members.Contains(adminUser))
|
||||
return ToggleAdminResult.TargetNotMember;
|
||||
|
||||
var newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin;
|
||||
await uow.SaveChangesAsync();
|
||||
return newState;
|
||||
return newState ? ToggleAdminResult.AddedAdmin : ToggleAdminResult.RemovedAdmin;
|
||||
}
|
||||
|
||||
public ClubInfo GetClubByMember(IUser user)
|
||||
@@ -87,27 +97,31 @@ public class ClubService : INService, IClubService
|
||||
var member = uow.Clubs.GetByMember(user.Id);
|
||||
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)
|
||||
{
|
||||
using var http = _httpFactory.CreateClient();
|
||||
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();
|
||||
var club = uow.Clubs.GetByOwner(ownerUserId);
|
||||
|
||||
if (club is null)
|
||||
return false;
|
||||
return SetClubIconResult.NotOwner;
|
||||
|
||||
club.ImageUrl = url;
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
return SetClubIconResult.Success;
|
||||
}
|
||||
|
||||
public bool GetClubByName(string clubName, out ClubInfo club)
|
||||
@@ -118,18 +132,22 @@ public class ClubService : INService, IClubService
|
||||
return club is not null;
|
||||
}
|
||||
|
||||
public bool ApplyToClub(IUser user, ClubInfo club)
|
||||
public ClubApplyResult ApplyToClub(IUser user, ClubInfo club)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var du = uow.GetOrCreateUser(user);
|
||||
uow.SaveChanges();
|
||||
|
||||
if (du.Club is not null
|
||||
|| club.Bans.Any(x => x.UserId == du.Id)
|
||||
|| club.Applicants.Any(x => x.UserId == du.Id))
|
||||
//user banned or a member of a club, or already applied,
|
||||
// or doesn't min minumum level requirement, can't apply
|
||||
return false;
|
||||
//user banned or a member of a club, or already applied,
|
||||
// or doesn't min minumum level requirement, can't apply
|
||||
if (du.Club is not null)
|
||||
return ClubApplyResult.AlreadyInAClub;
|
||||
|
||||
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
|
||||
{
|
||||
@@ -138,23 +156,23 @@ public class ClubService : INService, IClubService
|
||||
};
|
||||
|
||||
uow.Set<ClubApplicants>().Add(app);
|
||||
|
||||
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;
|
||||
using var uow = _db.GetDbContext();
|
||||
var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId);
|
||||
if (club is null)
|
||||
return false;
|
||||
return ClubAcceptResult.NotOwnerOrAdmin;
|
||||
|
||||
var applicant =
|
||||
club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
|
||||
if (applicant is null)
|
||||
return false;
|
||||
return ClubAcceptResult.NoSuchApplicant;
|
||||
|
||||
applicant.User.Club = club;
|
||||
applicant.User.IsClubAdmin = false;
|
||||
@@ -166,7 +184,7 @@ public class ClubService : INService, IClubService
|
||||
|
||||
discordUser = applicant.User;
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
return ClubAcceptResult.Accepted;
|
||||
}
|
||||
|
||||
public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId)
|
||||
@@ -175,17 +193,19 @@ public class ClubService : INService, IClubService
|
||||
return uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
|
||||
}
|
||||
|
||||
public bool LeaveClub(IUser user)
|
||||
public ClubLeaveResult LeaveClub(IUser user)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var du = uow.GetOrCreateUser(user, x => x.Include(u => u.Club));
|
||||
if (du.Club is null || du.Club.OwnerId == du.Id)
|
||||
return false;
|
||||
if (du.Club is null)
|
||||
return ClubLeaveResult.NotInAClub;
|
||||
if (du.Club.OwnerId == du.Id)
|
||||
return ClubLeaveResult.OwnerCantLeave;
|
||||
|
||||
du.Club = null;
|
||||
du.IsClubAdmin = false;
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
return ClubLeaveResult.Success;
|
||||
}
|
||||
|
||||
public bool SetDescription(ulong userId, string desc)
|
||||
@@ -213,23 +233,23 @@ public class ClubService : INService, IClubService
|
||||
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();
|
||||
club = uow.Clubs.GetByOwnerOrAdmin(bannerId);
|
||||
if (club is null)
|
||||
return false;
|
||||
return ClubBanResult.NotOwnerOrAdmin;
|
||||
|
||||
var usr = club.Members.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant())
|
||||
?? club.Applicants
|
||||
.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant())
|
||||
?.User;
|
||||
if (usr is null)
|
||||
return false;
|
||||
return ClubBanResult.WrongUser;
|
||||
|
||||
if (club.OwnerId == usr.Id
|
||||
|| (usr.IsClubAdmin && club.Owner.UserId != bannerId)) // can't ban the owner kek, whew
|
||||
return false;
|
||||
return ClubBanResult.Unbannable;
|
||||
|
||||
club.Bans.Add(new()
|
||||
{
|
||||
@@ -244,39 +264,40 @@ public class ClubService : INService, IClubService
|
||||
|
||||
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();
|
||||
club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
|
||||
if (club is null)
|
||||
return false;
|
||||
return ClubUnbanResult.NotOwnerOrAdmin;
|
||||
|
||||
var ban = club.Bans.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
|
||||
if (ban is null)
|
||||
return false;
|
||||
return ClubUnbanResult.WrongUser;
|
||||
|
||||
club.Bans.Remove(ban);
|
||||
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();
|
||||
club = uow.Clubs.GetByOwnerOrAdmin(kickerId);
|
||||
if (club is null)
|
||||
return false;
|
||||
return ClubKickResult.NotOwnerOrAdmin;
|
||||
|
||||
var usr = club.Members.FirstOrDefault(x => x.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
|
||||
if (usr is null)
|
||||
return false;
|
||||
return ClubKickResult.TargetNotAMember;
|
||||
|
||||
if (club.OwnerId == usr.Id || (usr.IsClubAdmin && club.Owner.UserId != kickerId))
|
||||
return false;
|
||||
return ClubKickResult.Hierarchy;
|
||||
|
||||
club.Members.Remove(usr);
|
||||
var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
|
||||
@@ -284,7 +305,7 @@ public class ClubService : INService, IClubService
|
||||
club.Applicants.Remove(app);
|
||||
uow.SaveChanges();
|
||||
|
||||
return true;
|
||||
return ClubKickResult.Success;
|
||||
}
|
||||
|
||||
public List<ClubInfo> GetClubLeaderboardPage(int page)
|
||||
|
@@ -1,23 +1,33 @@
|
||||
using NadekoBot.Db.Models;
|
||||
using OneOf;
|
||||
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
public interface IClubService
|
||||
{
|
||||
Task<bool?> CreateClubAsync(IUser user, string clubName);
|
||||
ClubInfo? TransferClub(IUser from, IUser newOwner);
|
||||
Task<bool?> ToggleAdminAsync(IUser owner, IUser toAdmin);
|
||||
Task<ClubCreateResult> CreateClubAsync(IUser user, string clubName);
|
||||
OneOf<ClubInfo,ClubTransferError> TransferClub(IUser from, IUser newOwner);
|
||||
Task<ToggleAdminResult> ToggleAdminAsync(IUser owner, IUser toAdmin);
|
||||
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 ApplyToClub(IUser user, ClubInfo club);
|
||||
bool AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser);
|
||||
ClubApplyResult ApplyToClub(IUser user, ClubInfo club);
|
||||
ClubAcceptResult AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser);
|
||||
ClubInfo? GetClubWithBansAndApplications(ulong ownerUserId);
|
||||
bool LeaveClub(IUser user);
|
||||
ClubLeaveResult LeaveClub(IUser user);
|
||||
bool SetDescription(ulong userId, string? desc);
|
||||
bool Disband(ulong userId, out ClubInfo club);
|
||||
bool Ban(ulong bannerId, string userName, out ClubInfo club);
|
||||
bool UnBan(ulong ownerUserId, string userName, out ClubInfo club);
|
||||
bool Kick(ulong kickerId, string userName, out ClubInfo club);
|
||||
ClubBanResult Ban(ulong bannerId, string userName, out ClubInfo club);
|
||||
ClubUnbanResult UnBan(ulong ownerUserId, string userName, out ClubInfo club);
|
||||
ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club);
|
||||
List<ClubInfo> GetClubLeaderboardPage(int page);
|
||||
}
|
||||
|
||||
public enum ClubApplyResult
|
||||
{
|
||||
Success,
|
||||
|
||||
AlreadyInAClub,
|
||||
Banned,
|
||||
InsufficientLevel
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubAcceptResult
|
||||
{
|
||||
Accepted,
|
||||
NotOwnerOrAdmin,
|
||||
NoSuchApplicant,
|
||||
}
|
10
src/NadekoBot/Modules/Xp/Club/Results/ClubBanResult.cs
Normal file
10
src/NadekoBot/Modules/Xp/Club/Results/ClubBanResult.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubBanResult
|
||||
{
|
||||
Success,
|
||||
NotOwnerOrAdmin,
|
||||
WrongUser,
|
||||
Unbannable,
|
||||
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubCreateResult
|
||||
{
|
||||
Success,
|
||||
AlreadyInAClub,
|
||||
NameTaken,
|
||||
InsufficientLevel,
|
||||
}
|
9
src/NadekoBot/Modules/Xp/Club/Results/ClubKickResult.cs
Normal file
9
src/NadekoBot/Modules/Xp/Club/Results/ClubKickResult.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubKickResult
|
||||
{
|
||||
Success,
|
||||
NotOwnerOrAdmin,
|
||||
TargetNotAMember,
|
||||
Hierarchy
|
||||
}
|
8
src/NadekoBot/Modules/Xp/Club/Results/ClubLeaveResult.cs
Normal file
8
src/NadekoBot/Modules/Xp/Club/Results/ClubLeaveResult.cs
Normal 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
Reference in New Issue
Block a user