Compare commits

..

10 Commits

29 changed files with 10380 additions and 63 deletions

View File

@@ -2,6 +2,30 @@
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
## [4.2.14] - 03.07.2022
### Added
- Added `.log userwarned` (Logging user warnings)
- Claiming `.timely` will now show a button which you can click to set a reminder
- Added `%server.icon%` placeholder
- Added `warn` punishment action for protection commands (it won't work with `.warnp`)
### Changed
- `.log userbanned` will now have a ban reason
- When `.die` is used, bot will try to update it's status to `Invisible`
### Fixed
- Fixed elipsis character issue with aliases/quotes. You should now be able to set an elipsis to be an alias of `.quoteprint`
## [4.2.13] - 30.06.2022
### Fixed
- Fixed `.cash` bank interaction not being ephemeral anymore
## [4.2.12] - 30.06.2022 ## [4.2.12] - 30.06.2022
### Fixed ### Fixed

View File

@@ -70,6 +70,7 @@ public sealed class Bot
: GatewayIntents.AllUnprivileged, : GatewayIntents.AllUnprivileged,
LogGatewayIntentWarnings = false, LogGatewayIntentWarnings = false,
FormatUsersInBidirectionalUnicode = false, FormatUsersInBidirectionalUnicode = false,
DefaultRetryMode = RetryMode.AlwaysRetry ^ RetryMode.RetryRatelimit
}); });
_commandService = new(new() _commandService = new(new()
@@ -260,6 +261,7 @@ public sealed class Bot
Client.JoinedGuild += Client_JoinedGuild; Client.JoinedGuild += Client_JoinedGuild;
Client.LeftGuild += Client_LeftGuild; Client.LeftGuild += Client_LeftGuild;
// _ = Client.SetStatusAsync(UserStatus.Online);
Log.Information("Shard {ShardId} logged in", Client.ShardId); Log.Information("Shard {ShardId} logged in", Client.ShardId);
} }

View File

@@ -27,5 +27,6 @@ public enum LogType
UserPresence, UserPresence,
VoicePresence, VoicePresence,
VoicePresenceTts, VoicePresenceTts,
UserMuted UserMuted,
UserWarned,
} }

View File

@@ -67,13 +67,13 @@ public abstract class NadekoModule : ModuleBase
// localized replies // localized replies
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null) public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}"); => SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null) public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}"); => SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null) public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}"); => SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed) public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
{ {

View File

@@ -55,6 +55,7 @@ public class ReplacementBuilder
_reps.TryAdd("%server%", () => g is null ? "DM" : g.Name); _reps.TryAdd("%server%", () => g is null ? "DM" : g.Name);
_reps.TryAdd("%server.id%", () => g is null ? "DM" : g.Id.ToString()); _reps.TryAdd("%server.id%", () => g is null ? "DM" : g.Id.ToString());
_reps.TryAdd("%server.name%", () => g is null ? "DM" : g.Name); _reps.TryAdd("%server.name%", () => g is null ? "DM" : g.Name);
_reps.TryAdd("%server.icon%", () => g is null ? null : g.IconUrl);
_reps.TryAdd("%server.members%", () => g is { } sg ? sg.MemberCount.ToString() : "?"); _reps.TryAdd("%server.members%", () => g is { } sg ? sg.MemberCount.ToString() : "?");
_reps.TryAdd("%server.boosters%", () => g.PremiumSubscriptionCount.ToString()); _reps.TryAdd("%server.boosters%", () => g.PremiumSubscriptionCount.ToString());
_reps.TryAdd("%server.boost_level%", () => ((int)g.PremiumTier).ToString()); _reps.TryAdd("%server.boost_level%", () => ((int)g.PremiumTier).ToString());

View File

@@ -44,11 +44,11 @@ public static class QuoteExtensions
var rngk = new NadekoRandom(); var rngk = new NadekoRandom();
return (await quotes.AsQueryable() return (await quotes.AsQueryable()
.Where(q => q.GuildId == guildId .Where(q => q.GuildId == guildId
&& q.Keyword == keyword && (keyword == null || q.Keyword == keyword)
&& EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%") && (EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%")
// && q.Text.Contains(text, StringComparison.OrdinalIgnoreCase) || EF.Functions.Like(q.AuthorName, text)))
) .ToListAsync())
.ToListAsync()).OrderBy(_ => rngk.Next()) .OrderBy(_ => rngk.Next())
.FirstOrDefault(); .FirstOrDefault();
} }

View File

@@ -48,7 +48,8 @@ public enum PunishmentAction
RemoveRoles, RemoveRoles,
ChatMute, ChatMute,
VoiceMute, VoiceMute,
AddRole AddRole,
Warn
} }
public class AntiSpamIgnore : DbEntity public class AntiSpamIgnore : DbEntity

View File

@@ -29,4 +29,5 @@ public class LogSetting : DbEntity
public ulong? LogVoicePresenceId { get; set; } public ulong? LogVoicePresenceId { get; set; }
public ulong? LogVoicePresenceTTSId { get; set; } public ulong? LogVoicePresenceTTSId { get; set; }
public ulong? LogWarnsId { get; set; }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class logwarns : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<ulong>(
name: "logwarnsid",
table: "logsettings",
type: "bigint unsigned",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "logwarnsid",
table: "logsettings");
}
}
}

View File

@@ -1406,6 +1406,10 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("bigint unsigned") .HasColumnType("bigint unsigned")
.HasColumnName("logvoicepresencettsid"); .HasColumnName("logvoicepresencettsid");
b.Property<ulong?>("LogWarnsId")
.HasColumnType("bigint unsigned")
.HasColumnName("logwarnsid");
b.Property<ulong?>("MessageDeletedId") b.Property<ulong?>("MessageDeletedId")
.HasColumnType("bigint unsigned") .HasColumnType("bigint unsigned")
.HasColumnName("messagedeletedid"); .HasColumnName("messagedeletedid");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class logwarns : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "logwarnsid",
table: "logsettings",
type: "numeric(20,0)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "logwarnsid",
table: "logsettings");
}
}
}

View File

@@ -1472,6 +1472,10 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("numeric(20,0)") .HasColumnType("numeric(20,0)")
.HasColumnName("logvoicepresencettsid"); .HasColumnName("logvoicepresencettsid");
b.Property<decimal?>("LogWarnsId")
.HasColumnType("numeric(20,0)")
.HasColumnName("logwarnsid");
b.Property<decimal?>("MessageDeletedId") b.Property<decimal?>("MessageDeletedId")
.HasColumnType("numeric(20,0)") .HasColumnType("numeric(20,0)")
.HasColumnName("messagedeletedid"); .HasColumnName("messagedeletedid");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class logwarns : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "StreamOnlineMessages",
type: "TEXT",
nullable: true,
oldClrType: typeof(string),
oldType: "TEXT");
migrationBuilder.AddColumn<ulong>(
name: "LogWarnsId",
table: "LogSettings",
type: "INTEGER",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LogWarnsId",
table: "LogSettings");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "StreamOnlineMessages",
type: "TEXT",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "TEXT",
oldNullable: true);
}
}
}

View File

@@ -278,7 +278,6 @@ namespace NadekoBot.Migrations
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int>("Type") b.Property<int>("Type")
@@ -1100,6 +1099,9 @@ namespace NadekoBot.Migrations
b.Property<ulong?>("LogVoicePresenceTTSId") b.Property<ulong?>("LogVoicePresenceTTSId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<ulong?>("LogWarnsId")
.HasColumnType("INTEGER");
b.Property<ulong?>("MessageDeletedId") b.Property<ulong?>("MessageDeletedId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");

View File

@@ -335,6 +335,7 @@ public partial class Administration
{ {
try try
{ {
await _client.SetStatusAsync(UserStatus.DoNotDisturb);
await ReplyConfirmLocalizedAsync(strs.shutting_down); await ReplyConfirmLocalizedAsync(strs.shutting_down);
} }
catch catch

View File

@@ -26,6 +26,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
private readonly IMemoryCache _memoryCache; private readonly IMemoryCache _memoryCache;
private readonly ConcurrentHashSet<ulong> _ignoreMessageIds = new(); private readonly ConcurrentHashSet<ulong> _ignoreMessageIds = new();
private readonly UserPunishService _punishService;
public LogCommandService( public LogCommandService(
DiscordSocketClient client, DiscordSocketClient client,
@@ -35,7 +36,8 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
ProtectionService prot, ProtectionService prot,
GuildTimezoneService tz, GuildTimezoneService tz,
IMemoryCache memoryCache, IMemoryCache memoryCache,
IEmbedBuilderService eb) IEmbedBuilderService eb,
UserPunishService punishService)
{ {
_client = client; _client = client;
_memoryCache = memoryCache; _memoryCache = memoryCache;
@@ -45,6 +47,8 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
_mute = mute; _mute = mute;
_prot = prot; _prot = prot;
_tz = tz; _tz = tz;
_punishService = punishService;
using (var uow = db.GetDbContext()) using (var uow = db.GetDbContext())
{ {
var guildIds = client.Guilds.Select(x => x.Id).ToList(); var guildIds = client.Guilds.Select(x => x.Id).ToList();
@@ -78,6 +82,8 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
_mute.UserUnmuted += MuteCommands_UserUnmuted; _mute.UserUnmuted += MuteCommands_UserUnmuted;
_prot.OnAntiProtectionTriggered += TriggeredAntiProtection; _prot.OnAntiProtectionTriggered += TriggeredAntiProtection;
_punishService.OnUserWarned += PunishServiceOnOnUserWarned;
} }
public async Task OnReadyAsync() public async Task OnReadyAsync()
@@ -183,6 +189,30 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
GuildLogSettings.AddOrUpdate(guildId, _ => logSetting, (_, _) => logSetting); GuildLogSettings.AddOrUpdate(guildId, _ => logSetting, (_, _) => logSetting);
} }
private async Task PunishServiceOnOnUserWarned(Warning arg)
{
if (!GuildLogSettings.TryGetValue(arg.GuildId, out var logSetting) || logSetting.LogWarnsId is null)
return;
var g = _client.GetGuild(arg.GuildId);
ITextChannel? logChannel;
if ((logChannel = await TryGetLogChannel(g, logSetting, LogType.UserWarned)) is null)
return;
var embed = _eb.Create()
.WithOkColor()
.WithTitle($"⚠️ User Warned")
.WithDescription($"<@{arg.UserId}> | {arg.UserId}")
.AddField("Mod", arg.Moderator)
.AddField("Reason", string.IsNullOrWhiteSpace(arg.Reason) ? "-" : arg.Reason, true)
.WithFooter(CurrentTime(g));
await logChannel.EmbedAsync(embed);
}
private Task _client_UserUpdated(SocketUser before, SocketUser uAfter) private Task _client_UserUpdated(SocketUser before, SocketUser uAfter)
{ {
_ = Task.Run(async () => _ = Task.Run(async () =>
@@ -296,6 +326,9 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
channelId = logSetting.LogVoicePresenceTTSId = channelId = logSetting.LogVoicePresenceTTSId =
logSetting.LogVoicePresenceTTSId is null ? cid : default; logSetting.LogVoicePresenceTTSId is null ? cid : default;
break; break;
case LogType.UserWarned:
channelId = logSetting.LogWarnsId = logSetting.LogWarnsId is null ? cid : default;
break;
} }
uow.SaveChanges(); uow.SaveChanges();
@@ -945,11 +978,25 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
ITextChannel? logChannel; ITextChannel? logChannel;
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) == null) if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) == null)
return; return;
string? reason = null;
try
{
var ban = await guild.GetBanAsync(usr);
reason = ban?.Reason;
}
catch
{
}
var embed = _eb.Create() var embed = _eb.Create()
.WithOkColor() .WithOkColor()
.WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned)) .WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned))
.WithDescription(usr.ToString()!) .WithDescription(usr.ToString()!)
.AddField("Id", usr.Id.ToString()) .AddField("Id", usr.Id.ToString())
.AddField("Reason", string.IsNullOrWhiteSpace(reason) ? "-" : reason)
.WithFooter(CurrentTime(guild)); .WithFooter(CurrentTime(guild));
var avatarUrl = usr.GetAvatarUrl(); var avatarUrl = usr.GetAvatarUrl();
@@ -1130,6 +1177,9 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
case LogType.UserMuted: case LogType.UserMuted:
id = logSetting.UserMutedId; id = logSetting.UserMutedId;
break; break;
case LogType.UserWarned:
id = logSetting.LogWarnsId;
break;
} }
if (id is null or 0) if (id is null or 0)
@@ -1200,6 +1250,9 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
case LogType.VoicePresenceTts: case LogType.VoicePresenceTts:
newLogSetting.LogVoicePresenceTTSId = null; newLogSetting.LogVoicePresenceTTSId = null;
break; break;
case LogType.UserWarned:
newLogSetting.LogWarnsId = null;
break;
} }
GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (_, _) => newLogSetting); GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (_, _) => newLogSetting);

View File

@@ -341,7 +341,8 @@ public partial class Administration
public async partial Task WarnPunish(int number, PunishmentAction punish, StoopidTime time = null) public async partial Task WarnPunish(int number, PunishmentAction punish, StoopidTime time = null)
{ {
// this should never happen. Addrole has its own method with higher priority // this should never happen. Addrole has its own method with higher priority
if (punish == PunishmentAction.AddRole) // also disallow warn punishment for getting warned
if (punish is PunishmentAction.AddRole or PunishmentAction.Warn)
return; return;
var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time); var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time);

View File

@@ -18,6 +18,8 @@ public class UserPunishService : INService, IReadyExecutor
private readonly BotConfigService _bcs; private readonly BotConfigService _bcs;
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
public event Func<Warning, Task> OnUserWarned = static delegate { return Task.CompletedTask; };
public UserPunishService( public UserPunishService(
MuteService mute, MuteService mute,
DbService db, DbService db,
@@ -93,6 +95,8 @@ public class UserPunishService : INService, IReadyExecutor
await uow.SaveChangesAsync(); await uow.SaveChangesAsync();
} }
_ = OnUserWarned(warn);
var totalCount = previousCount + weight; var totalCount = previousCount + weight;
var p = ps.Where(x => x.Count > previousCount && x.Count <= totalCount) var p = ps.Where(x => x.Count > previousCount && x.Count <= totalCount)
@@ -185,6 +189,9 @@ public class UserPunishService : INService, IReadyExecutor
guild.Id); guild.Id);
} }
break;
case PunishmentAction.Warn:
await Warn(guild, user.Id, mod, 1, reason);
break; break;
} }
} }

View File

@@ -7,6 +7,7 @@ using NadekoBot.Modules.Utility.Patronage;
using NadekoBot.Modules.Gambling.Bank; using NadekoBot.Modules.Gambling.Bank;
using NadekoBot.Modules.Gambling.Common; using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services; using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Utility.Services;
using NadekoBot.Services.Currency; using NadekoBot.Services.Currency;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -44,6 +45,7 @@ public partial class Gambling : GamblingModule<GamblingService>
private readonly GamblingConfigService _configService; private readonly GamblingConfigService _configService;
private readonly IBankService _bank; private readonly IBankService _bank;
private readonly IPatronageService _ps; private readonly IPatronageService _ps;
private readonly RemindService _remind;
private IUserMessage rdMsg; private IUserMessage rdMsg;
@@ -54,7 +56,8 @@ public partial class Gambling : GamblingModule<GamblingService>
DownloadTracker tracker, DownloadTracker tracker,
GamblingConfigService configService, GamblingConfigService configService,
IBankService bank, IBankService bank,
IPatronageService ps) IPatronageService ps,
RemindService remind)
: base(configService) : base(configService)
{ {
_db = db; _db = db;
@@ -62,6 +65,7 @@ public partial class Gambling : GamblingModule<GamblingService>
_client = client; _client = client;
_bank = bank; _bank = bank;
_ps = ps; _ps = ps;
_remind = remind;
_enUsCulture = new CultureInfo("en-US", false).NumberFormat; _enUsCulture = new CultureInfo("en-US", false).NumberFormat;
_enUsCulture.NumberDecimalDigits = 0; _enUsCulture.NumberDecimalDigits = 0;
@@ -110,6 +114,39 @@ public partial class Gambling : GamblingModule<GamblingService>
PrettyName = "Timely" PrettyName = "Timely"
}; };
public class RemindMeInteraction : NInteraction
{
public RemindMeInteraction(
[NotNull] DiscordSocketClient client,
ulong userId,
[NotNull] Func<SocketMessageComponent, Task> action)
: base(client, userId, action)
{
}
protected override NadekoInteractionData Data
=> new NadekoInteractionData(
Emote: Emoji.Parse("⏰"),
CustomId: "timely:remind_me",
Text: "Remind me"
);
}
private Func<SocketMessageComponent, Task> RemindTimelyAction(DateTime when)
=> async smc =>
{
var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative);
await _remind.AddReminderAsync(ctx.User.Id,
ctx.Channel.Id,
ctx.Guild.Id,
true,
when,
GetText(strs.timely_time));
await smc.RespondConfirmAsync(_eb, GetText(strs.remind_timely(tt)), ephemeral: true);
};
[Cmd] [Cmd]
public async partial Task Timely() public async partial Task Timely()
{ {
@@ -135,7 +172,11 @@ public partial class Gambling : GamblingModule<GamblingService>
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim")); await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
await ReplyConfirmLocalizedAsync(strs.timely(N(val), period)); var inter = new RemindMeInteraction(_client,
ctx.User.Id,
RemindTimelyAction(DateTime.UtcNow.Add(TimeSpan.FromHours(period))));
await ReplyConfirmLocalizedAsync(strs.timely(N(val), period), inter.GetInteraction());
} }
[Cmd] [Cmd]

View File

@@ -27,7 +27,8 @@ public class CommandMapService : IInputTransformer, INService
AliasMaps = new(configs.ToDictionary(x => x.GuildId, AliasMaps = new(configs.ToDictionary(x => x.GuildId,
x => new ConcurrentDictionary<string, string>(x.CommandAliases.DistinctBy(ca => ca.Trigger) x => new ConcurrentDictionary<string, string>(x.CommandAliases.DistinctBy(ca => ca.Trigger)
.ToDictionary(ca => ca.Trigger, ca => ca.Mapping)))); .ToDictionary(ca => ca.Trigger, ca => ca.Mapping),
StringComparer.OrdinalIgnoreCase)));
_db = db; _db = db;
} }
@@ -56,17 +57,29 @@ public class CommandMapService : IInputTransformer, INService
if (AliasMaps.TryGetValue(guild.Id, out var maps)) if (AliasMaps.TryGetValue(guild.Id, out var maps))
{ {
var keys = maps.Keys.OrderByDescending(x => x.Length); string word;
var index = input.IndexOf(' ', StringComparison.InvariantCulture);
foreach (var k in keys) if (index == -1)
{ {
string newInput; word = input;
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 else
continue; {
word = input[..index];
}
string newInput;
if (maps.TryGetValue(word, out var alias))
{
if (index == -1)
newInput = alias;
else
newInput = alias + ' ' + input[index..];
}
else
{
return null;
}
try try
{ {
@@ -83,7 +96,34 @@ public class CommandMapService : IInputTransformer, INService
catch { } catch { }
return newInput; return newInput;
}
// var keys = maps.Keys.OrderByDescending(x => x.Length);
// foreach (var k in keys)
// {
// string newInput;
// if (input.StartsWith(k + " ", StringComparison.InvariantCultureIgnoreCase))
// newInput = maps[k] + input.Substring(k.Length, input.Length - k.Length);
// else if (input.Equals(k, StringComparison.InvariantCultureIgnoreCase))
// newInput = maps[k];
// else
// continue;
//
// try
// {
// var toDelete = await channel.SendConfirmAsync(_eb, $"{input} => {newInput}");
// _ = Task.Run(async () =>
// {
// await Task.Delay(1500);
// await toDelete.DeleteAsync(new()
// {
// RetryMode = RetryMode.AlwaysRetry
// });
// });
// }
// catch { }
//
// return newInput;
// }
} }
return null; return null;

View File

@@ -1,4 +1,4 @@
#nullable disable #nullable disable warnings
using NadekoBot.Common.Yml; using NadekoBot.Common.Yml;
using NadekoBot.Db; using NadekoBot.Db;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
@@ -134,30 +134,40 @@ public partial class Utility
.WithFooter( .WithFooter(
GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})")))); GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})"))));
[Cmd] private async Task QuoteSearchinternalAsync(string? keyword, string textOrAuthor)
[RequireContext(ContextType.Guild)]
public async partial Task QuoteSearch(string keyword, [Leftover] string text)
{ {
if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(textOrAuthor))
return; return;
keyword = keyword.ToUpperInvariant(); keyword = keyword?.ToUpperInvariant();
Quote keywordquote; Quote quote;
await using (var uow = _db.GetDbContext()) await using (var uow = _db.GetDbContext())
{ {
keywordquote = await uow.Quotes.SearchQuoteKeywordTextAsync(ctx.Guild.Id, keyword, text); quote = await uow.Quotes.SearchQuoteKeywordTextAsync(ctx.Guild.Id, keyword, textOrAuthor);
} }
if (keywordquote is null) if (quote is null)
return; return;
await ctx.Channel.SendMessageAsync($"`#{keywordquote.Id}` 💬 " await ctx.Channel.SendMessageAsync($"`#{quote.Id}` 💬 "
+ keyword.ToLowerInvariant() + quote.Keyword.ToLowerInvariant()
+ ": " + ": "
+ keywordquote.Text.SanitizeAllMentions()); + quote.Text.SanitizeAllMentions());
} }
[Cmd]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public partial Task QuoteSearch(string textOrAuthor)
=> QuoteSearchinternalAsync(null, textOrAuthor);
[Cmd]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public partial Task QuoteSearch(string keyword, [Leftover] string textOrAuthor)
=> QuoteSearchinternalAsync(keyword, textOrAuthor);
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async partial Task QuoteId(int id) public async partial Task QuoteId(int id)

View File

@@ -178,4 +178,26 @@ public class RemindService : INService, IReadyExecutor
public string What { get; set; } public string What { get; set; }
public TimeSpan Time { get; set; } public TimeSpan Time { get; set; }
} }
public async Task AddReminderAsync(ulong userId,
ulong channelId,
ulong? guildId,
bool isPrivate,
DateTime time,
string message)
{
var rem = new Reminder
{
UserId = userId,
ChannelId = channelId,
ServerId = guildId ?? 0,
IsPrivate = isPrivate,
When = time,
Message = message,
};
await using var ctx = _db.GetDbContext();
await ctx.Reminders
.AddAsync(rem);
}
} }

View File

@@ -7,7 +7,7 @@ namespace NadekoBot.Services;
public sealed class StatsService : IStatsService, IReadyExecutor, INService public sealed class StatsService : IStatsService, IReadyExecutor, INService
{ {
public const string BOT_VERSION = "4.2.12"; public const string BOT_VERSION = "4.2.14";
public string Author public string Author
=> "Kwoth#2452"; => "Kwoth#2452";

View File

@@ -58,7 +58,8 @@ public static class SocketMessageComponentExtensions
bool ephemeral = false) bool ephemeral = false)
=> smc.RespondAsync(plainText, => smc.RespondAsync(plainText,
embed: embed?.Build(), embed: embed?.Build(),
embeds: embeds?.Map(x => x.Build())); embeds: embeds?.Map(x => x.Build()),
ephemeral: ephemeral);
public static Task RespondAsync( public static Task RespondAsync(
this SocketMessageComponent ch, this SocketMessageComponent ch,

View File

@@ -601,9 +601,12 @@ quoteshow:
args: args:
- "123" - "123"
quotesearch: quotesearch:
desc: "Shows a random quote for a keyword that contains any text specified in the search." desc: "Shows a random quote given a search query. Partially matches in several ways: 1) Only content of any quote, 2) only by author, 3) keyword and content, 3) or keyword and author"
args: args:
- "keyword text" - "\"find this long text\""
- "AuthorName"
- "keyword some text"
- "keyword AuthorName"
quoteid: quoteid:
desc: "Displays the quote with the specified ID number. Quote ID numbers can be found by typing `{0}liqu [num]` where `[num]` is a number of a page which contains 15 quotes." desc: "Displays the quote with the specified ID number. Quote ID numbers can be found by typing `{0}liqu [num]` where `[num]` is a number of a page which contains 15 quotes."
args: args:

View File

@@ -596,6 +596,7 @@
"quote_deleted": "Quote #{0} deleted.", "quote_deleted": "Quote #{0} deleted.",
"region": "Region", "region": "Region",
"remind": "I will remind {0} to {1} in {2} `({3:d.M.yyyy.} at {4:HH:mm})`", "remind": "I will remind {0} to {1} in {2} `({3:d.M.yyyy.} at {4:HH:mm})`",
"remind_timely": "I will remind you about your timely reward {0}",
"remind_invalid": "Not a valid remind format. Remind must have a target, timer and a reason. Check the command list.", "remind_invalid": "Not a valid remind format. Remind must have a target, timer and a reason. Check the command list.",
"remind_too_long": "Remind time has exceeded maximum.", "remind_too_long": "Remind time has exceeded maximum.",
"repeater_redundant_no": "Repeater **#{0}** won't post redundant messages anymore.", "repeater_redundant_no": "Repeater **#{0}** won't post redundant messages anymore.",
@@ -883,6 +884,7 @@
"timely_set": "Users will be able to claim {0} every {1}h", "timely_set": "Users will be able to claim {0} every {1}h",
"timely_set_none": "Users will not be able to claim any timely currency.", "timely_set_none": "Users will not be able to claim any timely currency.",
"timely_reset": "All users will be able to claim timely currency again.", "timely_reset": "All users will be able to claim timely currency again.",
"timely_time": "It's time for your timely reward.",
"price": "Price", "price": "Price",
"market_cap": "Market Cap", "market_cap": "Market Cap",
"market_cap_dominance": "Dominance", "market_cap_dominance": "Dominance",