mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Compare commits
23 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 |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -2,6 +2,33 @@
|
||||
|
||||
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||
|
||||
## [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
|
||||
|
@@ -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!
|
||||
|
@@ -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 =>
|
||||
|
@@ -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; }
|
||||
|
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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1258,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");
|
||||
|
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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1322,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");
|
||||
|
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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -986,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;
|
||||
}
|
||||
}
|
@@ -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,5 +1,4 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
|
||||
@@ -30,8 +29,11 @@ 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 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 Task.FromResult(false);
|
||||
|
||||
@@ -53,7 +55,7 @@ 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 Task.FromResult(false);
|
||||
@@ -69,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)
|
||||
{
|
||||
@@ -90,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 _);
|
||||
}
|
||||
|
@@ -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,8 +32,8 @@ public partial class Searches
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task Feed(string url, [Leftover] ITextChannel channel = null)
|
||||
{
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||
|| (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.feed_invalid_url);
|
||||
return;
|
||||
|
@@ -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);
|
||||
@@ -187,9 +208,9 @@ public partial class Xp
|
||||
.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);
|
||||
@@ -213,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);
|
||||
@@ -242,7 +263,6 @@ public partial class Xp
|
||||
await ReplyErrorLocalizedAsync(strs.club_insuff_lvl);
|
||||
else if (result == ClubApplyResult.AlreadyInAClub)
|
||||
await ReplyErrorLocalizedAsync(strs.club_already_in);
|
||||
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
@@ -254,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]
|
||||
@@ -278,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]
|
||||
@@ -322,7 +356,7 @@ public partial class Xp
|
||||
public Task ClubUnBan([Leftover] string userName)
|
||||
{
|
||||
var result = _service.UnBan(ctx.User.Id, userName, out var club);
|
||||
|
||||
|
||||
if (result == ClubUnbanResult.Success)
|
||||
{
|
||||
return ReplyConfirmLocalizedAsync(strs.club_user_unbanned(Format.Bold(userName),
|
||||
@@ -345,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)
|
||||
@@ -146,18 +160,19 @@ public class ClubService : INService, IClubService
|
||||
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;
|
||||
@@ -169,7 +184,7 @@ public class ClubService : INService, IClubService
|
||||
|
||||
discordUser = applicant.User;
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
return ClubAcceptResult.Accepted;
|
||||
}
|
||||
|
||||
public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId)
|
||||
@@ -178,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)
|
||||
@@ -267,19 +284,20 @@ public class ClubService : INService, IClubService
|
||||
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);
|
||||
@@ -287,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)
|
||||
@@ -298,20 +316,4 @@ public class ClubService : INService, IClubService
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.Clubs.GetClubLeaderboardPage(page);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ClubUnbanResult
|
||||
{
|
||||
Success,
|
||||
NotOwnerOrAdmin,
|
||||
WrongUser
|
||||
}
|
||||
|
||||
public enum ClubBanResult
|
||||
{
|
||||
Success,
|
||||
NotOwnerOrAdmin,
|
||||
WrongUser,
|
||||
Unbannable,
|
||||
|
||||
}
|
@@ -1,24 +1,25 @@
|
||||
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);
|
||||
ClubApplyResult ApplyToClub(IUser user, ClubInfo club);
|
||||
bool AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser);
|
||||
ClubAcceptResult AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser);
|
||||
ClubInfo? GetClubWithBansAndApplications(ulong ownerUserId);
|
||||
bool LeaveClub(IUser user);
|
||||
ClubLeaveResult LeaveClub(IUser user);
|
||||
bool SetDescription(ulong userId, string? desc);
|
||||
bool Disband(ulong userId, out ClubInfo club);
|
||||
ClubBanResult Ban(ulong bannerId, string userName, out ClubInfo club);
|
||||
ClubUnbanResult UnBan(ulong ownerUserId, string userName, out ClubInfo club);
|
||||
bool Kick(ulong kickerId, string userName, out ClubInfo club);
|
||||
ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club);
|
||||
List<ClubInfo> GetClubLeaderboardPage(int page);
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubTransferError
|
||||
{
|
||||
NotOwner,
|
||||
TargetNotMember
|
||||
}
|
8
src/NadekoBot/Modules/Xp/Club/Results/ClubUnbanResult.cs
Normal file
8
src/NadekoBot/Modules/Xp/Club/Results/ClubUnbanResult.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
public enum ClubUnbanResult
|
||||
{
|
||||
Success,
|
||||
NotOwnerOrAdmin,
|
||||
WrongUser
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
public enum SetClubIconResult
|
||||
{
|
||||
Success,
|
||||
InvalidFileType,
|
||||
TooLarge,
|
||||
NotOwner,
|
||||
}
|
10
src/NadekoBot/Modules/Xp/Club/Results/ToggleAdminResult.cs
Normal file
10
src/NadekoBot/Modules/Xp/Club/Results/ToggleAdminResult.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
public enum ToggleAdminResult
|
||||
{
|
||||
AddedAdmin,
|
||||
RemovedAdmin,
|
||||
NotOwner,
|
||||
TargetNotMember,
|
||||
CantTargetThyself,
|
||||
}
|
@@ -71,8 +71,11 @@ public sealed class GamblingTxTracker : ITxTracker, INService, IReadyExecutor
|
||||
}
|
||||
}
|
||||
|
||||
public Task TrackAdd(long amount, TxData txData)
|
||||
public Task TrackAdd(long amount, TxData? txData)
|
||||
{
|
||||
if (txData is null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (_gamblingTypes.Contains(txData.Type))
|
||||
{
|
||||
_stats.AddOrUpdate(txData.Type,
|
||||
@@ -83,8 +86,11 @@ public sealed class GamblingTxTracker : ITxTracker, INService, IReadyExecutor
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task TrackRemove(long amount, TxData txData)
|
||||
public Task TrackRemove(long amount, TxData? txData)
|
||||
{
|
||||
if (txData is null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (_gamblingTypes.Contains(txData.Type))
|
||||
{
|
||||
_stats.AddOrUpdate(txData.Type,
|
||||
|
@@ -1,6 +1,5 @@
|
||||
using NadekoBot.Services.Currency;
|
||||
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public interface ICurrencyService
|
||||
@@ -10,32 +9,32 @@ public interface ICurrencyService
|
||||
Task AddBulkAsync(
|
||||
IReadOnlyCollection<ulong> userIds,
|
||||
long amount,
|
||||
TxData txData,
|
||||
TxData? txData,
|
||||
CurrencyType type = CurrencyType.Default);
|
||||
|
||||
Task RemoveBulkAsync(
|
||||
IReadOnlyCollection<ulong> userIds,
|
||||
long amount,
|
||||
TxData txData,
|
||||
TxData? txData,
|
||||
CurrencyType type = CurrencyType.Default);
|
||||
|
||||
Task AddAsync(
|
||||
ulong userId,
|
||||
long amount,
|
||||
TxData txData);
|
||||
TxData? txData);
|
||||
|
||||
Task AddAsync(
|
||||
IUser user,
|
||||
long amount,
|
||||
TxData txData);
|
||||
TxData? txData);
|
||||
|
||||
Task<bool> RemoveAsync(
|
||||
ulong userId,
|
||||
long amount,
|
||||
TxData txData);
|
||||
TxData? txData);
|
||||
|
||||
Task<bool> RemoveAsync(
|
||||
IUser user,
|
||||
long amount,
|
||||
TxData txData);
|
||||
TxData? txData);
|
||||
}
|
@@ -4,6 +4,6 @@ namespace NadekoBot.Services;
|
||||
|
||||
public interface ITxTracker
|
||||
{
|
||||
Task TrackAdd(long amount, TxData txData);
|
||||
Task TrackRemove(long amount, TxData txData);
|
||||
Task TrackAdd(long amount, TxData? txData);
|
||||
Task TrackRemove(long amount, TxData? txData);
|
||||
}
|
@@ -7,7 +7,7 @@ namespace NadekoBot.Services;
|
||||
|
||||
public sealed class StatsService : IStatsService, IReadyExecutor, INService
|
||||
{
|
||||
public const string BOT_VERSION = "4.3.9";
|
||||
public const string BOT_VERSION = "4.3.10";
|
||||
|
||||
public string Author
|
||||
=> "Kwoth#2452";
|
||||
|
@@ -56,5 +56,11 @@ public sealed class BotConfigService : ConfigServiceBase<BotConfig>
|
||||
c.Version = 4;
|
||||
c.CheckForUpdates = true;
|
||||
});
|
||||
|
||||
if(data.Version < 5)
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 5;
|
||||
});
|
||||
}
|
||||
}
|
@@ -144,11 +144,11 @@ public static class Extensions
|
||||
public static IEmbedBuilder WithErrorColor(this IEmbedBuilder eb)
|
||||
=> eb.WithColor(EmbedColor.Error);
|
||||
|
||||
public static IMessage DeleteAfter(this IUserMessage msg, int seconds, ILogCommandService? logService = null)
|
||||
public static IMessage DeleteAfter(this IUserMessage msg, float seconds, ILogCommandService? logService = null)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(seconds * 1000);
|
||||
await Task.Delay((int)(seconds * 1000));
|
||||
if (logService is not null)
|
||||
logService.AddDeleteIgnore(msg.Id);
|
||||
|
||||
|
@@ -246,6 +246,9 @@ roles:
|
||||
channeltopic:
|
||||
- channeltopic
|
||||
- ct
|
||||
filterlist:
|
||||
- filterlist
|
||||
- fl
|
||||
chnlfilterinv:
|
||||
- chnlfilterinv
|
||||
- cfi
|
||||
@@ -741,6 +744,10 @@ forwardmessages:
|
||||
forwardtoall:
|
||||
- forwardtoall
|
||||
- fwtoall
|
||||
forwardtochannel:
|
||||
- forwardtochannel
|
||||
- fwtoch
|
||||
- fwtochannel
|
||||
resetperms:
|
||||
- resetperms
|
||||
antiraid:
|
||||
@@ -1267,6 +1274,9 @@ quotesimport:
|
||||
showembed:
|
||||
- showembed
|
||||
# NadekoExpressions
|
||||
exprtoggleglobal:
|
||||
- exprtoggleglobal
|
||||
- extg
|
||||
exprreact:
|
||||
- exreact
|
||||
- exr
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# DO NOT CHANGE
|
||||
version: 4
|
||||
version: 5
|
||||
# Most commands, when executed, have a small colored line
|
||||
# next to the response. The color depends whether the command
|
||||
# is completed, errored or in progress (pending)
|
||||
@@ -25,6 +25,9 @@ forwardMessages: false
|
||||
# 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)
|
||||
forwardToAllOwners: false
|
||||
# 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
|
||||
forwardToChannel:
|
||||
# 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.
|
||||
# Supports embeds. How it looks: https://puu.sh/B0BLV.png
|
||||
|
@@ -236,6 +236,10 @@ fwclear:
|
||||
desc: "Deletes all filtered words on this server."
|
||||
args:
|
||||
- ""
|
||||
filterlist:
|
||||
desc: "Lists invite and link filter channels and status."
|
||||
args:
|
||||
- ""
|
||||
aliasesclear:
|
||||
desc: "Deletes all aliases on this server."
|
||||
args:
|
||||
@@ -1253,19 +1257,23 @@ forwardtoall:
|
||||
desc: "Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the creds.yml file"
|
||||
args:
|
||||
- ""
|
||||
forwardtochannel:
|
||||
desc: "Toggles forwarding of non-command messages sent to bot's DM to the current channel"
|
||||
args:
|
||||
- ""
|
||||
resetperms:
|
||||
desc: "Resets the bot's permissions module on this server to the default value."
|
||||
args:
|
||||
- ""
|
||||
antiraid:
|
||||
desc: "Sets an anti-raid protection on the server. Provide no parameters to disable. First parameter is number of people which will trigger the protection. Second parameter is a time interval in which that number of people needs to join in order to trigger the protection, and third parameter is punishment for those people. You can specify an additional time argument to do a timed punishment for actions which support it (Ban, Mute, etc) up to 24h. Available punishments: Ban, Kick, Softban, Mute, VoiceMute, ChatMute, RemoveRoles"
|
||||
desc: "Sets an anti-raid protection on the server. Provide no parameters to disable. First parameter is number of people which will trigger the protection. Second parameter is a time interval in which that number of people needs to join in order to trigger the protection, and third parameter is punishment for those people. You can specify an additional time argument to do a timed punishment for actions which support it (Ban, Mute, etc) up to 24h. Available punishments: Ban, Kick, Softban, Mute, VoiceMute, ChatMute, RemoveRoles, AddRole, Warn, TimeOut"
|
||||
args:
|
||||
- "5 20 Kick"
|
||||
- "7 9 Ban"
|
||||
- "10 10 Ban 6h30m"
|
||||
- ""
|
||||
antispam:
|
||||
desc: "Stops people from repeating same message X times in a row. Provide no parameters to disable. You can specify to either mute, kick or ban the offenders. You can specify an additional time argument to do a timed punishment for actions which support it (Ban, Mute, etc) up to 24h. Max message count is 10. Available punishments: Ban, Kick, Softban, Mute, VoiceMute, ChatMute, AddRole, RemoveRoles"
|
||||
desc: "Stops people from repeating same message X times in a row. Provide no parameters to disable. You can specify to either mute, kick or ban the offenders. You can specify an additional time argument to do a timed punishment for actions which support it (Ban, Mute, etc) up to 24h. Max message count is 10. Available punishments: Ban, Kick, Softban, Mute, VoiceMute, ChatMute, AddRole, RemoveRoles, Warn, TimeOut"
|
||||
args:
|
||||
- "3 Mute"
|
||||
- "5 Ban"
|
||||
@@ -1573,6 +1581,10 @@ rategirl:
|
||||
desc: "Use the universal hot-crazy wife zone matrix to determine the girl's worth. It is everything young men need to know about women. At any moment in time, any woman you have previously located on this chart can vanish from that location and appear anywhere else on the chart."
|
||||
args:
|
||||
- "@SomeGurl"
|
||||
exprtoggleglobal:
|
||||
desc: "Toggles whether global expressions are usable on this server."
|
||||
args:
|
||||
- ""
|
||||
exprreact:
|
||||
desc: "Sets or resets reactions (up to 3) which will be added to the response message of the Expression with the specified ID. Provide no emojis to reset."
|
||||
args:
|
||||
|
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"expr_global_disabled": "Global expressions are now disabled on this server.",
|
||||
"expr_global_enabled": "Global expressions are no longer disabled on this server.",
|
||||
"expr_deleted": "Expression deleted",
|
||||
"expr_insuff_perms": "Insufficient permissions. Requires Bot ownership for global expressions, and Administrator for server expressions.",
|
||||
"expressions": "Expressions",
|
||||
@@ -68,6 +70,8 @@
|
||||
"fwall_stop": "I will forward DMs only to the first owner.",
|
||||
"fwdm_start": "I will forward DMs from now on.",
|
||||
"fwdm_stop": "I will stop forwarding DMs from now on.",
|
||||
"fwch_start": "Any message sent to bot's DMs will be forwarded to this channel.",
|
||||
"fwch_stop": "Messages sent to bot's DMs will no longer be forwarded to this channel.",
|
||||
"greetdel_off": "Automatic deletion of greet messages has been disabled.",
|
||||
"greetdel_on": "Greet messages will be deleted after {0} seconds.",
|
||||
"greetdmmsg_cur": "Current DM greet message: {0}",
|
||||
@@ -150,7 +154,6 @@
|
||||
"rc": "Color of {0} role has been changed.",
|
||||
"rc_perms": "Error occurred due to invalid color or insufficient permissions.",
|
||||
"color": "Color",
|
||||
"icon": "Icon",
|
||||
"hoisted": "Hoisted",
|
||||
"mentionable": "Mentionable",
|
||||
"remrole": "Successfully removed role {0} from user {1}",
|
||||
@@ -271,7 +274,6 @@
|
||||
"usage": "Usage",
|
||||
"options": "Options",
|
||||
"requires": "Requires",
|
||||
"tag": "Tag",
|
||||
"animal_race": "Animal race",
|
||||
"animal_race_failed": "Failed to start since there was not enough participants.",
|
||||
"animal_race_full": "Race is full! Starting immediately.",
|
||||
@@ -443,7 +445,6 @@
|
||||
"removed": "removed permission #{0} - {1}",
|
||||
"rx_disable": "Disabled usage of {0} {1} for {2} role.",
|
||||
"rx_enable": "Enabled usage of {0} {1} for {2} role.",
|
||||
"sec": "sec.",
|
||||
"sx_disable": "Disabled usage of {0} {1} on this server.",
|
||||
"sx_enable": "Enabled usage of {0} {1} on this server.",
|
||||
"unblacklisted": "Unblacklisted {0} with ID {1}",
|
||||
@@ -841,19 +842,22 @@
|
||||
"club_join_banned": "You're banned from that club.",
|
||||
"club_already_in": "You are already a member of a club.",
|
||||
"club_create_error_name": "Failed creating the club. A club with that name already exists.",
|
||||
"club_create_error": "Failed creating the club. Make sure you're above level 5 and not a member of a club already.",
|
||||
"club_name_too_long": "Club name is too long.",
|
||||
"club_created": "Club {0} successfully created!",
|
||||
"club_create_insuff_lvl": "You don't meet the minimum level requirements in order to create a club.",
|
||||
"club_not_exists": "That club doesn't exist.",
|
||||
"club_applied": "You've applied for membership in {0} club.",
|
||||
"club_accepted": "Accepted user {0} to the club.",
|
||||
"club_accept_error": "User not found",
|
||||
"club_accept_invalid_applicant": "That user has not applied to your club.",
|
||||
"club_left": "You've left the club.",
|
||||
"club_not_in_club": "You are not in a club, or you're trying to leave the club you're the owner of.",
|
||||
"club_not_in_a_club": "You are not in a club.",
|
||||
"club_owner_cant_leave": "Club owner can't leave the club - you must either transfer ownership or disband the club.",
|
||||
"club_user_kick": "User {0} kicked from {1} club.",
|
||||
"club_user_not_in_club": "{0} is not in a club.",
|
||||
"club_user_kick_fail": "Error kicking. You're either not the club owner, or that user is not in your club.",
|
||||
"club_user_banned": "Banned user {0} from {1} club.",
|
||||
"club_owner_only": "This action can only be performed by the club owner.",
|
||||
"club_admin_invalid_target": "You can't target yourself or the club owner.",
|
||||
"club_target_not_member": "Specified user is not a member of your club.",
|
||||
"club_admin_perms": "You must be a club admin or owner in order to perform this action.",
|
||||
"club_ban_fail_user_not_found": "That user is not in your club or applied to it.",
|
||||
"club_ban_fail_unbannable": "Only the club owner can ban club admins.",
|
||||
@@ -863,11 +867,14 @@
|
||||
"club_desc_update_failed": "Failed changing club description.",
|
||||
"club_disbanded": "Club {0} has been disbanded",
|
||||
"club_disband_error": "Error. You are either not in a club, or you are not the owner of your club.",
|
||||
"club_icon_error": "Not a valid image url or you're not the club owner.",
|
||||
"club_icon_too_large": "Image is too large.",
|
||||
"club_icon_invalid_filetype": "Specified image has an invalid filetype. Make sure you're specifying a direct image url.",
|
||||
"club_icon_url_format": "You must specify an absolute image url/.",
|
||||
"club_icon_set": "New club icon set.",
|
||||
"club_bans_for": "Bans for {0} club",
|
||||
"club_apps_for": "Applicants for {0} club",
|
||||
"club_leaderboard": "Club leaderboard - page {0}",
|
||||
"club_kick_hierarchy": "Only club owner can kick club admins. Owner can't be kicked.",
|
||||
"template_reloaded": "Xp template has been reloaded.",
|
||||
"expr_edited": "Expression Edited",
|
||||
"self_assign_are_exclusive": "You can only choose 1 role from each group.",
|
||||
@@ -881,7 +888,6 @@
|
||||
"poll_closed": "Poll Closed!",
|
||||
"club_admin_add": "{0} is now a club admin.",
|
||||
"club_admin_remove": "{0} is no longer club admin.",
|
||||
"club_admin_error": "Error. You are either not the owner of the club, or that user is not in your club.",
|
||||
"started": "Started. Reposting every {0}s.",
|
||||
"stopped": "Stopped reposting.",
|
||||
"feed_added": "Feed added.",
|
||||
@@ -928,7 +934,6 @@
|
||||
"mass_ban_completed": "Banned {0} users.",
|
||||
"mass_kill_completed": "Mass Banning and Blacklisting of {0} users is complete.",
|
||||
"club_transfered": "Ownership of the club {0} has been transferred to {1}",
|
||||
"club_transfer_failed": "Transfer failed. You must be the club owner. Target must be a member of your club.",
|
||||
"roll_duel_challenge": "challenged {1} for a roll duel for {2}",
|
||||
"roll_duel": "Roll Duel",
|
||||
"roll_duel_no_funds": "Either you or your opponent don't have enough funds.",
|
||||
|
Reference in New Issue
Block a user