mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 01:38:27 -04:00
Applied codestyle to all .cs files
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#nullable disable
|
||||
using NCalc;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NadekoBot.Modules.Utility;
|
||||
@@ -8,10 +9,11 @@ public partial class Utility
|
||||
[Group]
|
||||
public class CalcCommands : NadekoSubmodule
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Calculate([Leftover] string expression)
|
||||
{
|
||||
var expr = new NCalc.Expression(expression, NCalc.EvaluateOptions.IgnoreCase | NCalc.EvaluateOptions.NoCache);
|
||||
var expr = new Expression(expression, EvaluateOptions.IgnoreCase | EvaluateOptions.NoCache);
|
||||
expr.EvaluateParameter += Expr_EvaluateParameter;
|
||||
var result = expr.Evaluate();
|
||||
if (!expr.HasErrors())
|
||||
@@ -20,7 +22,7 @@ public partial class Utility
|
||||
await SendErrorAsync("⚙ " + GetText(strs.error), expr.Error);
|
||||
}
|
||||
|
||||
private static void Expr_EvaluateParameter(string name, NCalc.ParameterArgs args)
|
||||
private static void Expr_EvaluateParameter(string name, ParameterArgs args)
|
||||
{
|
||||
switch (name.ToLowerInvariant())
|
||||
{
|
||||
@@ -30,26 +32,19 @@ public partial class Utility
|
||||
case "e":
|
||||
args.Result = Math.E;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task CalcOps()
|
||||
{
|
||||
var selection = typeof(Math).GetTypeInfo()
|
||||
.GetMethods()
|
||||
.DistinctBy(x => x.Name)
|
||||
.Select(x => x.Name)
|
||||
.Except(new[]
|
||||
{
|
||||
"ToString",
|
||||
"Equals",
|
||||
"GetHashCode",
|
||||
"GetType"
|
||||
});
|
||||
.GetMethods()
|
||||
.DistinctBy(x => x.Name)
|
||||
.Select(x => x.Name)
|
||||
.Except(new[] { "ToString", "Equals", "GetHashCode", "GetType" });
|
||||
await SendConfirmAsync(GetText(strs.calcops(Prefix)), string.Join(", ", selection));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Utility;
|
||||
|
||||
@@ -20,7 +20,8 @@ public partial class Utility
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AliasesClear()
|
||||
@@ -29,7 +30,8 @@ public partial class Utility
|
||||
await ReplyConfirmLocalizedAsync(strs.aliases_cleared(count));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Alias(string trigger, [Leftover] string mapping = null)
|
||||
@@ -43,8 +45,7 @@ public partial class Utility
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mapping))
|
||||
{
|
||||
if (!_service.AliasMaps.TryGetValue(ctx.Guild.Id, out var maps) ||
|
||||
!maps.TryRemove(trigger, out _))
|
||||
if (!_service.AliasMaps.TryGetValue(ctx.Guild.Id, out var maps) || !maps.TryRemove(trigger, out _))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.alias_remove_fail(Format.Code(trigger)));
|
||||
return;
|
||||
@@ -53,11 +54,7 @@ public partial class Utility
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
var toAdd = new CommandAlias()
|
||||
{
|
||||
Mapping = mapping,
|
||||
Trigger = trigger
|
||||
};
|
||||
var toAdd = new CommandAlias { Mapping = mapping, Trigger = trigger };
|
||||
var tr = config.CommandAliases.FirstOrDefault(x => x.Trigger == trigger);
|
||||
if (tr != null)
|
||||
uow.Set<CommandAlias>().Remove(tr);
|
||||
@@ -67,46 +64,45 @@ public partial class Utility
|
||||
await ReplyConfirmLocalizedAsync(strs.alias_removed(Format.Code(trigger)));
|
||||
return;
|
||||
}
|
||||
_service.AliasMaps.AddOrUpdate(ctx.Guild.Id, _ =>
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
|
||||
_service.AliasMaps.AddOrUpdate(ctx.Guild.Id,
|
||||
_ =>
|
||||
{
|
||||
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
config.CommandAliases.Add(new()
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
Mapping = mapping,
|
||||
Trigger = trigger
|
||||
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
config.CommandAliases.Add(new() { Mapping = mapping, Trigger = trigger });
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return new(new Dictionary<string, string>
|
||||
{
|
||||
{ trigger.Trim().ToLowerInvariant(), mapping.ToLowerInvariant() }
|
||||
});
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return new(new Dictionary<string, string>() {
|
||||
{trigger.Trim().ToLowerInvariant(), mapping.ToLowerInvariant() },
|
||||
});
|
||||
}, (_, map) =>
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
},
|
||||
(_, map) =>
|
||||
{
|
||||
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
var toAdd = new CommandAlias()
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
Mapping = mapping,
|
||||
Trigger = trigger
|
||||
};
|
||||
var toRemove = config.CommandAliases.Where(x => x.Trigger == trigger);
|
||||
if (toRemove.Any())
|
||||
uow.RemoveRange(toRemove.ToArray());
|
||||
config.CommandAliases.Add(toAdd);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
map.AddOrUpdate(trigger, mapping, (key, old) => mapping);
|
||||
return map;
|
||||
});
|
||||
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
var toAdd = new CommandAlias { Mapping = mapping, Trigger = trigger };
|
||||
var toRemove = config.CommandAliases.Where(x => x.Trigger == trigger);
|
||||
if (toRemove.Any())
|
||||
uow.RemoveRange(toRemove.ToArray());
|
||||
config.CommandAliases.Add(toAdd);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
map.AddOrUpdate(trigger, mapping, (key, old) => mapping);
|
||||
return map;
|
||||
});
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.alias_added(Format.Code(trigger), Format.Code(mapping)));
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AliasList(int page = 1)
|
||||
{
|
||||
@@ -124,14 +120,17 @@ public partial class Utility
|
||||
|
||||
var arr = maps.ToArray();
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
return _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.alias_list))
|
||||
.WithDescription(string.Join("\n",
|
||||
arr.Skip(curPage * 10).Take(10).Select(x => $"`{x.Key}` => `{x.Value}`")));
|
||||
|
||||
}, arr.Length, 10);
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
return _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.alias_list))
|
||||
.WithDescription(string.Join("\n",
|
||||
arr.Skip(curPage * 10).Take(10).Select(x => $"`{x.Key}` => `{x.Value}`")));
|
||||
},
|
||||
arr.Length,
|
||||
10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,4 +9,4 @@ public class ConvertUnit
|
||||
public string[] Triggers { get; set; }
|
||||
public string UnitType { get; set; }
|
||||
public decimal Modifier { get; set; }
|
||||
}
|
||||
}
|
@@ -3,15 +3,18 @@ namespace NadekoBot.Modules.Utility.Common.Exceptions;
|
||||
|
||||
public class StreamRoleNotFoundException : Exception
|
||||
{
|
||||
public StreamRoleNotFoundException() : base("Stream role wasn't found.")
|
||||
public StreamRoleNotFoundException()
|
||||
: base("Stream role wasn't found.")
|
||||
{
|
||||
}
|
||||
|
||||
public StreamRoleNotFoundException(string message) : base(message)
|
||||
public StreamRoleNotFoundException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public StreamRoleNotFoundException(string message, Exception innerException) : base(message, innerException)
|
||||
public StreamRoleNotFoundException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,15 +3,18 @@ namespace NadekoBot.Modules.Utility.Common.Exceptions;
|
||||
|
||||
public class StreamRolePermissionException : Exception
|
||||
{
|
||||
public StreamRolePermissionException() : base("Stream role was unable to be applied.")
|
||||
public StreamRolePermissionException()
|
||||
: base("Stream role was unable to be applied.")
|
||||
{
|
||||
}
|
||||
|
||||
public StreamRolePermissionException(string message) : base(message)
|
||||
public StreamRolePermissionException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public StreamRolePermissionException(string message, Exception innerException) : base(message, innerException)
|
||||
public StreamRolePermissionException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -61,10 +61,10 @@ public sealed class PatreonResponse
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public List<PatreonMember> Data { get; set; }
|
||||
|
||||
|
||||
[JsonPropertyName("included")]
|
||||
public List<PatreonUser> Included { get; set; }
|
||||
|
||||
|
||||
[JsonPropertyName("links")]
|
||||
public PatreonLinks Links { get; set; }
|
||||
}
|
||||
@@ -73,12 +73,13 @@ public sealed class PatreonLinks
|
||||
{
|
||||
[JsonPropertyName("next")]
|
||||
public string Next { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PatreonUser
|
||||
{
|
||||
[JsonPropertyName("attributes")]
|
||||
public PatreonUserAttributes Attributes { get; set; }
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
// public string Type { get; set; }
|
||||
@@ -89,6 +90,7 @@ public sealed class PatreonUserAttributes
|
||||
[JsonPropertyName("social_connections")]
|
||||
public PatreonSocials SocialConnections { get; set; }
|
||||
}
|
||||
|
||||
public sealed class PatreonSocials
|
||||
{
|
||||
[JsonPropertyName("discord")]
|
||||
@@ -100,7 +102,7 @@ public sealed class DiscordSocial
|
||||
[JsonPropertyName("user_id")]
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public sealed class PatreonMember
|
||||
{
|
||||
[JsonPropertyName("attributes")]
|
||||
@@ -129,4 +131,4 @@ public sealed class PatreonUserData
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
@@ -4,5 +4,5 @@ namespace NadekoBot.Modules.Utility.Common;
|
||||
public enum StreamRoleListType
|
||||
{
|
||||
Whitelist,
|
||||
Blacklist,
|
||||
}
|
||||
Blacklist
|
||||
}
|
@@ -10,20 +10,21 @@ public partial class Utility
|
||||
public ConfigCommands(IEnumerable<IConfigService> settingServices)
|
||||
=> _settingServices = settingServices;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task ConfigReload(string name)
|
||||
{
|
||||
var setting = _settingServices.FirstOrDefault(x =>
|
||||
x.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase));
|
||||
var setting = _settingServices.FirstOrDefault(x
|
||||
=> x.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (setting is null)
|
||||
{
|
||||
var configNames = _settingServices.Select(x => x.Name);
|
||||
var embed = _eb.Create()
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.config_not_found(Format.Code(name))))
|
||||
.AddField(GetText(strs.config_list), string.Join("\n", configNames));
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.config_not_found(Format.Code(name))))
|
||||
.AddField(GetText(strs.config_list), string.Join("\n", configNames));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
return;
|
||||
@@ -32,36 +33,37 @@ public partial class Utility
|
||||
setting.Reload();
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task Config(string name = null, string prop = null, [Leftover] string value = null)
|
||||
{
|
||||
var configNames = _settingServices.Select(x => x.Name);
|
||||
|
||||
|
||||
// if name is not provided, print available configs
|
||||
name = name?.ToLowerInvariant();
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.config_list))
|
||||
.WithDescription(string.Join("\n", configNames));
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.config_list))
|
||||
.WithDescription(string.Join("\n", configNames));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
return;
|
||||
}
|
||||
|
||||
var setting = _settingServices.FirstOrDefault(x =>
|
||||
x.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase));
|
||||
var setting = _settingServices.FirstOrDefault(x
|
||||
=> x.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
// if config name is not found, print error and the list of configs
|
||||
if (setting is null)
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.config_not_found(Format.Code(name))))
|
||||
.AddField(GetText(strs.config_list), string.Join("\n", configNames));
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.config_not_found(Format.Code(name))))
|
||||
.AddField(GetText(strs.config_list), string.Join("\n", configNames));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
return;
|
||||
@@ -75,31 +77,29 @@ public partial class Utility
|
||||
if (string.IsNullOrWhiteSpace(prop))
|
||||
{
|
||||
var propStrings = GetPropsAndValuesString(setting, propNames);
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle($"⚙️ {setting.Name}")
|
||||
.WithDescription(propStrings);
|
||||
var embed = _eb.Create().WithOkColor().WithTitle($"⚙️ {setting.Name}").WithDescription(propStrings);
|
||||
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
return;
|
||||
}
|
||||
// if the prop is invalid -> print error and list of
|
||||
|
||||
|
||||
var exists = propNames.Any(x => x == prop);
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
var propStrings = GetPropsAndValuesString(setting, propNames);
|
||||
var propErrorEmbed = _eb.Create()
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.config_prop_not_found(Format.Code(prop), Format.Code(name))))
|
||||
.AddField($"⚙️ {setting.Name}", propStrings);
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(
|
||||
strs.config_prop_not_found(Format.Code(prop), Format.Code(name))))
|
||||
.AddField($"⚙️ {setting.Name}", propStrings);
|
||||
|
||||
await ctx.Channel.EmbedAsync(propErrorEmbed);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// if prop is sent, but value is not, then we have to check
|
||||
// if prop is valid ->
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
@@ -108,17 +108,14 @@ public partial class Utility
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = "-";
|
||||
|
||||
if (prop != "currency.sign")
|
||||
{
|
||||
value = Format.Code(Format.Sanitize(value?.TrimTo(1000)), "json");
|
||||
}
|
||||
|
||||
if (prop != "currency.sign") value = Format.Code(Format.Sanitize(value?.TrimTo(1000)), "json");
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.AddField("Config", Format.Code(setting.Name), true)
|
||||
.AddField("Prop", Format.Code(prop), true)
|
||||
.AddField("Value", value);
|
||||
.WithOkColor()
|
||||
.AddField("Config", Format.Code(setting.Name), true)
|
||||
.AddField("Prop", Format.Code(prop), true)
|
||||
.AddField("Value", value);
|
||||
|
||||
var comment = setting.GetComment(prop);
|
||||
if (!string.IsNullOrWhiteSpace(comment))
|
||||
@@ -148,11 +145,10 @@ public partial class Utility
|
||||
val = val?.TrimTo(28);
|
||||
return val?.Replace("\n", "") ?? "-";
|
||||
});
|
||||
|
||||
var strings = names.Zip(propValues, (name, value) =>
|
||||
$"{name, -25} = {value}\n");
|
||||
|
||||
var strings = names.Zip(propValues, (name, value) => $"{name,-25} = {value}\n");
|
||||
|
||||
return Format.Code(string.Concat(strings), "hs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,22 +17,23 @@ public partial class Utility
|
||||
_stats = stats;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ServerInfo(string guildName = null)
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
guildName = guildName?.ToUpperInvariant();
|
||||
SocketGuild guild;
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(guildName))
|
||||
guild = (SocketGuild)channel.Guild;
|
||||
else
|
||||
guild = _client.Guilds.FirstOrDefault(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant());
|
||||
|
||||
|
||||
if (guild is null)
|
||||
return;
|
||||
|
||||
|
||||
var ownername = guild.GetUser(guild.OwnerId);
|
||||
var textchn = guild.TextChannels.Count;
|
||||
var voicechn = guild.VoiceChannels.Count;
|
||||
@@ -42,35 +43,30 @@ public partial class Utility
|
||||
var features = string.Join(", ", guild.Features);
|
||||
if (string.IsNullOrWhiteSpace(features))
|
||||
features = "-";
|
||||
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithAuthor(GetText(strs.server_info))
|
||||
.WithTitle(guild.Name)
|
||||
.AddField(GetText(strs.id), guild.Id.ToString(), true)
|
||||
.AddField(GetText(strs.owner), ownername.ToString(), true)
|
||||
.AddField(GetText(strs.members), guild.MemberCount.ToString(), true)
|
||||
.AddField(GetText(strs.channels), channels, true)
|
||||
.AddField(GetText(strs.created_at), $"{createdAt:dd.MM.yyyy HH:mm}", true)
|
||||
.AddField(GetText(strs.roles), (guild.Roles.Count - 1).ToString(), true)
|
||||
.AddField(GetText(strs.features), features)
|
||||
.WithOkColor();
|
||||
|
||||
.WithAuthor(GetText(strs.server_info))
|
||||
.WithTitle(guild.Name)
|
||||
.AddField(GetText(strs.id), guild.Id.ToString(), true)
|
||||
.AddField(GetText(strs.owner), ownername.ToString(), true)
|
||||
.AddField(GetText(strs.members), guild.MemberCount.ToString(), true)
|
||||
.AddField(GetText(strs.channels), channels, true)
|
||||
.AddField(GetText(strs.created_at), $"{createdAt:dd.MM.yyyy HH:mm}", true)
|
||||
.AddField(GetText(strs.roles), (guild.Roles.Count - 1).ToString(), true)
|
||||
.AddField(GetText(strs.features), features)
|
||||
.WithOkColor();
|
||||
|
||||
if (Uri.IsWellFormedUriString(guild.IconUrl, UriKind.Absolute))
|
||||
embed.WithThumbnailUrl(guild.IconUrl);
|
||||
|
||||
|
||||
if (guild.Emotes.Any())
|
||||
{
|
||||
embed.AddField(GetText(strs.custom_emojis) + $"({guild.Emotes.Count})",
|
||||
string.Join(" ", guild.Emotes
|
||||
.Shuffle()
|
||||
.Take(20)
|
||||
.Select(e => $"{e.Name} {e.ToString()}"))
|
||||
.TrimTo(1020));
|
||||
}
|
||||
string.Join(" ", guild.Emotes.Shuffle().Take(20).Select(e => $"{e.Name} {e}")).TrimTo(1020));
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ChannelInfo(ITextChannel channel = null)
|
||||
{
|
||||
@@ -80,16 +76,17 @@ public partial class Utility
|
||||
var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(ch.Id >> 22);
|
||||
var usercount = (await ch.GetUsersAsync().FlattenAsync()).Count();
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(ch.Name)
|
||||
.WithDescription(ch.Topic?.SanitizeMentions(true))
|
||||
.AddField(GetText(strs.id), ch.Id.ToString(), true)
|
||||
.AddField(GetText(strs.created_at), $"{createdAt:dd.MM.yyyy HH:mm}", true)
|
||||
.AddField(GetText(strs.users), usercount.ToString(), true)
|
||||
.WithOkColor();
|
||||
.WithTitle(ch.Name)
|
||||
.WithDescription(ch.Topic?.SanitizeMentions(true))
|
||||
.AddField(GetText(strs.id), ch.Id.ToString(), true)
|
||||
.AddField(GetText(strs.created_at), $"{createdAt:dd.MM.yyyy HH:mm}", true)
|
||||
.AddField(GetText(strs.users), usercount.ToString(), true)
|
||||
.WithOkColor();
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task UserInfo(IGuildUser usr = null)
|
||||
{
|
||||
@@ -98,17 +95,15 @@ public partial class Utility
|
||||
if (user is null)
|
||||
return;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.AddField(GetText(strs.name), $"**{user.Username}**#{user.Discriminator}", true);
|
||||
if (!string.IsNullOrWhiteSpace(user.Nickname))
|
||||
{
|
||||
embed.AddField(GetText(strs.nickname), user.Nickname, true);
|
||||
}
|
||||
var embed = _eb.Create().AddField(GetText(strs.name), $"**{user.Username}**#{user.Discriminator}", true);
|
||||
if (!string.IsNullOrWhiteSpace(user.Nickname)) embed.AddField(GetText(strs.nickname), user.Nickname, true);
|
||||
embed.AddField(GetText(strs.id), user.Id.ToString(), true)
|
||||
.AddField(GetText(strs.joined_server), $"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "?"}", true)
|
||||
.AddField(GetText(strs.joined_discord), $"{user.CreatedAt:dd.MM.yyyy HH:mm}", true)
|
||||
.AddField(GetText(strs.roles), $"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Take(10).Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions(true)}", true)
|
||||
.WithOkColor();
|
||||
.AddField(GetText(strs.joined_server), $"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "?"}", true)
|
||||
.AddField(GetText(strs.joined_discord), $"{user.CreatedAt:dd.MM.yyyy HH:mm}", true)
|
||||
.AddField(GetText(strs.roles),
|
||||
$"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Take(10).Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions(true)}",
|
||||
true)
|
||||
.WithOkColor();
|
||||
|
||||
var av = user.RealAvatarUrl();
|
||||
if (av != null && av.IsAbsoluteUri)
|
||||
@@ -116,7 +111,8 @@ public partial class Utility
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task Activity(int page = 1)
|
||||
@@ -130,19 +126,20 @@ public partial class Utility
|
||||
var startCount = page * activityPerPage;
|
||||
|
||||
var str = new StringBuilder();
|
||||
foreach (var kvp in CmdHandler.UserMessagesSent.OrderByDescending(kvp => kvp.Value).Skip(page * activityPerPage).Take(activityPerPage))
|
||||
{
|
||||
str.AppendLine(GetText(strs.activity_line(
|
||||
++startCount,
|
||||
foreach (var kvp in CmdHandler.UserMessagesSent.OrderByDescending(kvp => kvp.Value)
|
||||
.Skip(page * activityPerPage)
|
||||
.Take(activityPerPage))
|
||||
str.AppendLine(GetText(strs.activity_line(++startCount,
|
||||
Format.Bold(kvp.Key.ToString()),
|
||||
kvp.Value / _stats.GetUptime().TotalSeconds, kvp.Value)));
|
||||
}
|
||||
kvp.Value / _stats.GetUptime().TotalSeconds,
|
||||
kvp.Value)));
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithTitle(GetText(strs.activity_page(page + 1)))
|
||||
.WithOkColor()
|
||||
.WithFooter(GetText(strs.activity_users_total(CmdHandler.UserMessagesSent.Count)))
|
||||
.WithDescription(str.ToString()));
|
||||
.WithTitle(GetText(strs.activity_page(page + 1)))
|
||||
.WithOkColor()
|
||||
.WithFooter(GetText(
|
||||
strs.activity_users_total(CmdHandler.UserMessagesSent.Count)))
|
||||
.WithDescription(str.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,7 +8,8 @@ public partial class Utility
|
||||
[Group]
|
||||
public class InviteCommands : NadekoSubmodule<InviteService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(ChannelPerm.CreateInstantInvite)]
|
||||
[UserPerm(ChannelPerm.CreateInstantInvite)]
|
||||
@@ -20,16 +21,17 @@ public partial class Utility
|
||||
return;
|
||||
|
||||
var ch = (ITextChannel)ctx.Channel;
|
||||
var invite = await ch.CreateInviteAsync(opts.Expire, opts.MaxUses, isTemporary: opts.Temporary, isUnique: opts.Unique);
|
||||
var invite = await ch.CreateInviteAsync(opts.Expire, opts.MaxUses, opts.Temporary, opts.Unique);
|
||||
|
||||
await SendConfirmAsync($"{ctx.User.Mention} https://discord.gg/{invite.Code}");
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(ChannelPerm.ManageChannels)]
|
||||
[UserPerm(ChannelPerm.ManageChannels)]
|
||||
public async Task InviteList(int page = 1, [Leftover]ITextChannel ch = null)
|
||||
public async Task InviteList(int page = 1, [Leftover] ITextChannel ch = null)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
@@ -37,44 +39,40 @@ public partial class Utility
|
||||
|
||||
var invites = await channel.GetInvitesAsync();
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, cur =>
|
||||
{
|
||||
var i = 1;
|
||||
var invs = invites
|
||||
.Skip(cur * 9)
|
||||
.Take(9)
|
||||
.ToList();
|
||||
|
||||
if (!invs.Any())
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
cur =>
|
||||
{
|
||||
return _eb.Create()
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.no_invites));
|
||||
}
|
||||
var i = 1;
|
||||
var invs = invites.Skip(cur * 9).Take(9).ToList();
|
||||
|
||||
var embed = _eb.Create().WithOkColor();
|
||||
foreach (var inv in invites)
|
||||
{
|
||||
var expiryString = inv.MaxAge is null or 0 || inv.CreatedAt is null
|
||||
? "∞"
|
||||
: (inv.CreatedAt.Value.AddSeconds(inv.MaxAge.Value).UtcDateTime - DateTime.UtcNow)
|
||||
.ToString(@"d\.hh\:mm\:ss");
|
||||
var creator = inv.Inviter.ToString().TrimTo(25);
|
||||
var usesString = $"{inv.Uses} / {(inv.MaxUses == 0 ? "∞" : inv.MaxUses?.ToString())}";
|
||||
|
||||
var desc = $@"`{GetText(strs.inv_uses)}` **{usesString}**
|
||||
if (!invs.Any())
|
||||
return _eb.Create().WithErrorColor().WithDescription(GetText(strs.no_invites));
|
||||
|
||||
var embed = _eb.Create().WithOkColor();
|
||||
foreach (var inv in invites)
|
||||
{
|
||||
var expiryString = inv.MaxAge is null or 0 || inv.CreatedAt is null
|
||||
? "∞"
|
||||
: (inv.CreatedAt.Value.AddSeconds(inv.MaxAge.Value).UtcDateTime - DateTime.UtcNow).ToString(
|
||||
@"d\.hh\:mm\:ss");
|
||||
var creator = inv.Inviter.ToString().TrimTo(25);
|
||||
var usesString = $"{inv.Uses} / {(inv.MaxUses == 0 ? "∞" : inv.MaxUses?.ToString())}";
|
||||
|
||||
var desc = $@"`{GetText(strs.inv_uses)}` **{usesString}**
|
||||
`{GetText(strs.inv_expire)}` **{expiryString}**
|
||||
|
||||
{inv.Url} ";
|
||||
embed.AddField($"#{i++} {creator}", desc);
|
||||
}
|
||||
embed.AddField($"#{i++} {creator}", desc);
|
||||
}
|
||||
|
||||
return embed;
|
||||
|
||||
}, invites.Count, 9);
|
||||
return embed;
|
||||
},
|
||||
invites.Count,
|
||||
9);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(ChannelPerm.ManageChannels)]
|
||||
[UserPerm(ChannelPerm.ManageChannels)]
|
||||
@@ -82,7 +80,7 @@ public partial class Utility
|
||||
{
|
||||
if (--index < 0)
|
||||
return;
|
||||
|
||||
|
||||
var ch = (ITextChannel)ctx.Channel;
|
||||
|
||||
var invites = await ch.GetInvitesAsync();
|
||||
@@ -95,4 +93,4 @@ public partial class Utility
|
||||
await ReplyAsync(GetText(strs.invite_deleted(Format.Bold(inv.Code))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.Yml;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace NadekoBot.Modules.Utility;
|
||||
|
||||
@@ -11,6 +12,25 @@ public partial class Utility
|
||||
[Group]
|
||||
public class QuoteCommands : NadekoSubmodule
|
||||
{
|
||||
private const string _prependExport =
|
||||
@"# Keys are keywords, Each key has a LIST of quotes in the following format:
|
||||
# - id: Alphanumeric id used for commands related to the quote. (Note, when using .quotesimport, a new id will be generated.)
|
||||
# an: Author name
|
||||
# aid: Author id
|
||||
# txt: Quote text
|
||||
";
|
||||
|
||||
private static readonly ISerializer _exportSerializer = new SerializerBuilder()
|
||||
.WithEventEmitter(args
|
||||
=> new MultilineScalarFlowStyleEmitter(args))
|
||||
.WithNamingConvention(
|
||||
CamelCaseNamingConvention.Instance)
|
||||
.WithIndentedSequences()
|
||||
.ConfigureDefaultValuesHandling(DefaultValuesHandling
|
||||
.OmitDefaults)
|
||||
.DisableAliases()
|
||||
.Build();
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly IHttpClientFactory _http;
|
||||
|
||||
@@ -20,13 +40,15 @@ public partial class Utility
|
||||
_http = http;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public Task ListQuotes(OrderType order = OrderType.Keyword)
|
||||
=> ListQuotes(1, order);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task ListQuotes(int page = 1, OrderType order = OrderType.Keyword)
|
||||
@@ -43,12 +65,15 @@ public partial class Utility
|
||||
|
||||
if (quotes.Any())
|
||||
await SendConfirmAsync(GetText(strs.quotes_page(page + 1)),
|
||||
string.Join("\n", quotes.Select(q => $"`#{q.Id}` {Format.Bold(q.Keyword.SanitizeAllMentions()),-20} by {q.AuthorName.SanitizeAllMentions()}")));
|
||||
string.Join("\n",
|
||||
quotes.Select(q
|
||||
=> $"`#{q.Id}` {Format.Bold(q.Keyword.SanitizeAllMentions()),-20} by {q.AuthorName.SanitizeAllMentions()}")));
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.quotes_page_none);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task QuotePrint([Leftover] string keyword)
|
||||
{
|
||||
@@ -71,9 +96,7 @@ public partial class Utility
|
||||
if (quote is null)
|
||||
return;
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(Context)
|
||||
.Build();
|
||||
var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
||||
|
||||
var text = SmartText.CreateFrom(quote.Text);
|
||||
text = rep.Replace(text);
|
||||
@@ -81,7 +104,8 @@ public partial class Utility
|
||||
await ctx.Channel.SendAsync($"`#{quote.Id}` 📣 " + text, true);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task QuoteShow(int id)
|
||||
{
|
||||
@@ -104,14 +128,16 @@ public partial class Utility
|
||||
|
||||
private async Task ShowQuoteData(Quote data)
|
||||
=> await ctx.Channel.EmbedAsync(_eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.quote_id($"#{data.Id}")))
|
||||
.AddField(GetText(strs.trigger), data.Keyword)
|
||||
.AddField(GetText(strs.response), Format.Sanitize(data.Text).Replace("](", "]\\("))
|
||||
.WithFooter(GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})")))
|
||||
);
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.quote_id($"#{data.Id}")))
|
||||
.AddField(GetText(strs.trigger), data.Keyword)
|
||||
.AddField(GetText(strs.response),
|
||||
Format.Sanitize(data.Text).Replace("](", "]\\("))
|
||||
.WithFooter(
|
||||
GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})"))));
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task QuoteSearch(string keyword, [Leftover] string text)
|
||||
{
|
||||
@@ -129,11 +155,14 @@ public partial class Utility
|
||||
if (keywordquote is null)
|
||||
return;
|
||||
|
||||
await ctx.Channel.SendMessageAsync($"`#{keywordquote.Id}` 💬 " + keyword.ToLowerInvariant() + ": " +
|
||||
keywordquote.Text.SanitizeAllMentions());
|
||||
await ctx.Channel.SendMessageAsync($"`#{keywordquote.Id}` 💬 "
|
||||
+ keyword.ToLowerInvariant()
|
||||
+ ": "
|
||||
+ keywordquote.Text.SanitizeAllMentions());
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task QuoteId(int id)
|
||||
{
|
||||
@@ -142,9 +171,7 @@ public partial class Utility
|
||||
|
||||
Quote quote;
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(Context)
|
||||
.Build();
|
||||
var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
||||
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
@@ -157,23 +184,26 @@ public partial class Utility
|
||||
return;
|
||||
}
|
||||
|
||||
var infoText = $"`#{quote.Id} added by {quote.AuthorName.SanitizeAllMentions()}` 🗯️ " + quote.Keyword.ToLowerInvariant().SanitizeAllMentions() + ":\n";
|
||||
var infoText = $"`#{quote.Id} added by {quote.AuthorName.SanitizeAllMentions()}` 🗯️ "
|
||||
+ quote.Keyword.ToLowerInvariant().SanitizeAllMentions()
|
||||
+ ":\n";
|
||||
|
||||
|
||||
|
||||
var text = SmartText.CreateFrom(quote.Text);
|
||||
text = rep.Replace(text);
|
||||
await ctx.Channel.SendAsync(infoText + text, true);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task QuoteAdd(string keyword, [Leftover] string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text))
|
||||
return;
|
||||
|
||||
|
||||
keyword = keyword.ToUpperInvariant();
|
||||
|
||||
|
||||
Quote q;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
@@ -183,14 +213,16 @@ public partial class Utility
|
||||
AuthorName = ctx.Message.Author.Username,
|
||||
GuildId = ctx.Guild.Id,
|
||||
Keyword = keyword,
|
||||
Text = text,
|
||||
Text = text
|
||||
});
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.quote_added_new(Format.Code(q.Id.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task QuoteDelete(int id)
|
||||
{
|
||||
@@ -214,13 +246,15 @@ public partial class Utility
|
||||
response = GetText(strs.quote_deleted(id));
|
||||
}
|
||||
}
|
||||
|
||||
if (success)
|
||||
await SendConfirmAsync(response);
|
||||
else
|
||||
await SendErrorAsync(response);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task DelAllQuotes([Leftover] string keyword)
|
||||
@@ -240,39 +274,8 @@ public partial class Utility
|
||||
await ReplyConfirmLocalizedAsync(strs.quotes_deleted(Format.Bold(keyword.SanitizeAllMentions())));
|
||||
}
|
||||
|
||||
public class ExportedQuote
|
||||
{
|
||||
public static ExportedQuote FromModel(Quote quote)
|
||||
=> new()
|
||||
{
|
||||
Id = ((kwum)quote.Id).ToString(),
|
||||
An = quote.AuthorName,
|
||||
Aid = quote.AuthorId,
|
||||
Txt = quote.Text
|
||||
};
|
||||
|
||||
public string Id { get; set; }
|
||||
public string An { get; set; }
|
||||
public ulong Aid { get; set; }
|
||||
public string Txt { get; set; }
|
||||
}
|
||||
|
||||
private const string _prependExport =
|
||||
@"# Keys are keywords, Each key has a LIST of quotes in the following format:
|
||||
# - id: Alphanumeric id used for commands related to the quote. (Note, when using .quotesimport, a new id will be generated.)
|
||||
# an: Author name
|
||||
# aid: Author id
|
||||
# txt: Quote text
|
||||
";
|
||||
private static readonly ISerializer _exportSerializer = new SerializerBuilder()
|
||||
.WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
|
||||
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
||||
.WithIndentedSequences()
|
||||
.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults)
|
||||
.DisableAliases()
|
||||
.Build();
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task QuotesExport()
|
||||
@@ -280,31 +283,27 @@ public partial class Utility
|
||||
IEnumerable<Quote> quotes;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
quotes = uow.Quotes
|
||||
.GetForGuild(ctx.Guild.Id)
|
||||
.ToList();
|
||||
quotes = uow.Quotes.GetForGuild(ctx.Guild.Id).ToList();
|
||||
}
|
||||
|
||||
var crsDict = quotes
|
||||
.GroupBy(x => x.Keyword)
|
||||
.ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel));
|
||||
|
||||
var text = _prependExport + _exportSerializer
|
||||
.Serialize(crsDict)
|
||||
.UnescapeUnicodeCodePoints();
|
||||
var crsDict = quotes.GroupBy(x => x.Keyword)
|
||||
.ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel));
|
||||
|
||||
var text = _prependExport + _exportSerializer.Serialize(crsDict).UnescapeUnicodeCodePoints();
|
||||
|
||||
await using var stream = await text.ToStream();
|
||||
await ctx.Channel.SendFileAsync(stream, "quote-export.yml", text: null);
|
||||
await ctx.Channel.SendFileAsync(stream, "quote-export.yml");
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Ratelimit(300)]
|
||||
#if GLOBAL_NADEKO
|
||||
[OwnerOnly]
|
||||
#endif
|
||||
public async Task QuotesImport([Leftover]string input = null)
|
||||
public async Task QuotesImport([Leftover] string input = null)
|
||||
{
|
||||
input = input?.Trim();
|
||||
|
||||
@@ -335,10 +334,10 @@ public partial class Utility
|
||||
await ReplyErrorLocalizedAsync(strs.expr_import_invalid_data);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> ImportCrsAsync(ulong guildId, string input)
|
||||
{
|
||||
Dictionary<string, List<ExportedQuote>> data;
|
||||
@@ -357,21 +356,33 @@ public partial class Utility
|
||||
foreach (var entry in data)
|
||||
{
|
||||
var keyword = entry.Key;
|
||||
await uow.Quotes
|
||||
.AddRangeAsync(entry.Value
|
||||
.Where(quote => !string.IsNullOrWhiteSpace(quote.Txt))
|
||||
.Select(quote => new Quote()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Keyword = keyword,
|
||||
Text = quote.Txt,
|
||||
AuthorId = quote.Aid,
|
||||
AuthorName = quote.An,
|
||||
}));
|
||||
await uow.Quotes.AddRangeAsync(entry.Value.Where(quote => !string.IsNullOrWhiteSpace(quote.Txt))
|
||||
.Select(quote => new Quote
|
||||
{
|
||||
GuildId = guildId,
|
||||
Keyword = keyword,
|
||||
Text = quote.Txt,
|
||||
AuthorId = quote.Aid,
|
||||
AuthorName = quote.An
|
||||
}));
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public class ExportedQuote
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string An { get; set; }
|
||||
public ulong Aid { get; set; }
|
||||
public string Txt { get; set; }
|
||||
|
||||
public static ExportedQuote FromModel(Quote quote)
|
||||
=> new()
|
||||
{
|
||||
Id = ((kwum)quote.Id).ToString(), An = quote.AuthorName, Aid = quote.AuthorId, Txt = quote.Text
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
#nullable disable
|
||||
using Humanizer.Localisation;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Utility;
|
||||
|
||||
@@ -12,6 +12,20 @@ public partial class Utility
|
||||
[Group]
|
||||
public class RemindCommands : NadekoSubmodule<RemindService>
|
||||
{
|
||||
public enum MeOrHere
|
||||
{
|
||||
Me,
|
||||
Here
|
||||
}
|
||||
|
||||
public enum Server
|
||||
{
|
||||
Server = int.MinValue,
|
||||
Srvr = int.MinValue,
|
||||
Serv = int.MinValue,
|
||||
S = int.MinValue
|
||||
}
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly GuildTimezoneService _tz;
|
||||
|
||||
@@ -21,13 +35,8 @@ public partial class Utility
|
||||
_tz = tz;
|
||||
}
|
||||
|
||||
public enum MeOrHere
|
||||
{
|
||||
Me,
|
||||
Here
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Remind(MeOrHere meorhere, [Leftover] string remindString)
|
||||
{
|
||||
@@ -36,22 +45,23 @@ public partial class Utility
|
||||
await ReplyErrorLocalizedAsync(strs.remind_invalid);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ulong target;
|
||||
target = meorhere == MeOrHere.Me ? ctx.User.Id : ctx.Channel.Id;
|
||||
if (!await RemindInternal(target, meorhere == MeOrHere.Me || ctx.Guild is null, remindData.Time, remindData.What))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.remind_too_long);
|
||||
}
|
||||
if (!await RemindInternal(target,
|
||||
meorhere == MeOrHere.Me || ctx.Guild is null,
|
||||
remindData.Time,
|
||||
remindData.What)) await ReplyErrorLocalizedAsync(strs.remind_too_long);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(0)]
|
||||
public async Task Remind(ITextChannel channel, [Leftover] string remindString)
|
||||
{
|
||||
var perms = ((IGuildUser) ctx.User).GetPermissions(channel);
|
||||
var perms = ((IGuildUser)ctx.User).GetPermissions(channel);
|
||||
if (!perms.SendMessages || !perms.ViewChannel)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.cant_read_or_send);
|
||||
@@ -66,55 +76,39 @@ public partial class Utility
|
||||
|
||||
|
||||
if (!await RemindInternal(channel.Id, false, remindData.Time, remindData.What))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.remind_too_long);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Server
|
||||
{
|
||||
Server = int.MinValue,
|
||||
Srvr = int.MinValue,
|
||||
Serv = int.MinValue,
|
||||
S = int.MinValue,
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(0)]
|
||||
public Task RemindList(Server _, int page = 1)
|
||||
=> RemindListInternal(page, true);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(1)]
|
||||
public Task RemindList(int page = 1)
|
||||
=> RemindListInternal(page, false);
|
||||
|
||||
|
||||
private async Task RemindListInternal(int page, bool isServer)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(isServer ? strs.reminder_server_list : strs.reminder_list));
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(isServer ? strs.reminder_server_list : strs.reminder_list));
|
||||
|
||||
List<Reminder> rems;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
if (isServer)
|
||||
{
|
||||
rems = uow.Reminders
|
||||
.RemindersForServer(ctx.Guild.Id, page)
|
||||
.ToList();
|
||||
}
|
||||
rems = uow.Reminders.RemindersForServer(ctx.Guild.Id, page).ToList();
|
||||
else
|
||||
{
|
||||
rems = uow.Reminders
|
||||
.RemindersFor(ctx.User.Id, page)
|
||||
.ToList();
|
||||
}
|
||||
rems = uow.Reminders.RemindersFor(ctx.User.Id, page).ToList();
|
||||
}
|
||||
|
||||
if (rems.Any())
|
||||
@@ -125,11 +119,11 @@ public partial class Utility
|
||||
var when = rem.When;
|
||||
var diff = when - DateTime.UtcNow;
|
||||
embed.AddField(
|
||||
$"#{++i + (page * 10)} {rem.When:HH:mm yyyy-MM-dd} UTC " +
|
||||
$"(in {diff.Humanize(2, minUnit: TimeUnit.Minute, culture: Culture)})",
|
||||
$"#{++i + (page * 10)} {rem.When:HH:mm yyyy-MM-dd} UTC "
|
||||
+ $"(in {diff.Humanize(2, minUnit: TimeUnit.Minute, culture: Culture)})",
|
||||
$@"`Target:` {(rem.IsPrivate ? "DM" : "Channel")}
|
||||
`TargetId:` {rem.ChannelId}
|
||||
`Message:` {rem.Message?.TrimTo(50)}", false);
|
||||
`Message:` {rem.Message?.TrimTo(50)}");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -141,18 +135,20 @@ public partial class Utility
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(0)]
|
||||
public Task RemindDelete(Server _, int index)
|
||||
=> RemindDelete(index, true);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(1)]
|
||||
public Task RemindDelete(int index)
|
||||
=> RemindDelete(index, false);
|
||||
|
||||
|
||||
private async Task RemindDelete(int index, bool isServer)
|
||||
{
|
||||
if (--index < 0)
|
||||
@@ -162,13 +158,9 @@ public partial class Utility
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var rems = isServer
|
||||
? uow.Reminders
|
||||
.RemindersForServer(ctx.Guild.Id, index / 10)
|
||||
.ToList()
|
||||
: uow.Reminders
|
||||
.RemindersFor(ctx.User.Id, index / 10)
|
||||
.ToList();
|
||||
|
||||
? uow.Reminders.RemindersForServer(ctx.Guild.Id, index / 10).ToList()
|
||||
: uow.Reminders.RemindersFor(ctx.User.Id, index / 10).ToList();
|
||||
|
||||
var pageIndex = index % 10;
|
||||
if (rems.Count > pageIndex)
|
||||
{
|
||||
@@ -179,16 +171,16 @@ public partial class Utility
|
||||
}
|
||||
|
||||
if (rem is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.reminder_not_exist);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.reminder_deleted(index + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> RemindInternal(ulong targetId, bool isPrivate, TimeSpan ts, string message)
|
||||
private async Task<bool> RemindInternal(
|
||||
ulong targetId,
|
||||
bool isPrivate,
|
||||
TimeSpan ts,
|
||||
string message)
|
||||
{
|
||||
var time = DateTime.UtcNow + ts;
|
||||
|
||||
@@ -197,11 +189,8 @@ public partial class Utility
|
||||
|
||||
if (ctx.Guild != null)
|
||||
{
|
||||
var perms = ((IGuildUser) ctx.User).GetPermissions((IGuildChannel) ctx.Channel);
|
||||
if (!perms.MentionEveryone)
|
||||
{
|
||||
message = message.SanitizeAllMentions();
|
||||
}
|
||||
var perms = ((IGuildUser)ctx.User).GetPermissions((IGuildChannel)ctx.Channel);
|
||||
if (!perms.MentionEveryone) message = message.SanitizeAllMentions();
|
||||
}
|
||||
|
||||
var rem = new Reminder
|
||||
@@ -220,17 +209,16 @@ public partial class Utility
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var gTime = ctx.Guild is null
|
||||
? time
|
||||
: TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id));
|
||||
var gTime = ctx.Guild is null ? time : TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id));
|
||||
try
|
||||
{
|
||||
await SendConfirmAsync(
|
||||
"⏰ " + GetText(strs.remind(
|
||||
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),
|
||||
Format.Bold(message),
|
||||
ts.Humanize(3, minUnit: TimeUnit.Second, culture: Culture),
|
||||
gTime, gTime)));
|
||||
await SendConfirmAsync("⏰ "
|
||||
+ GetText(strs.remind(
|
||||
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),
|
||||
Format.Bold(message),
|
||||
ts.Humanize(3, minUnit: TimeUnit.Second, culture: Culture),
|
||||
gTime,
|
||||
gTime)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -239,4 +227,4 @@ public partial class Utility
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
using NadekoBot.Common.TypeReaders.Models;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Utility;
|
||||
|
||||
@@ -9,7 +9,8 @@ public partial class Utility
|
||||
[Group]
|
||||
public class RepeatCommands : NadekoSubmodule<RepeaterService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task RepeatInvoke(int index)
|
||||
@@ -18,13 +19,11 @@ public partial class Utility
|
||||
return;
|
||||
|
||||
var success = await _service.TriggerExternal(ctx.Guild.Id, index);
|
||||
if (!success)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.repeat_invoke_none);
|
||||
}
|
||||
if (!success) await ReplyErrorLocalizedAsync(strs.repeat_invoke_none);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task RepeatRemove(int index)
|
||||
@@ -32,117 +31,114 @@ public partial class Utility
|
||||
if (--index < 0)
|
||||
return;
|
||||
|
||||
var removed = await _service.RemoveByIndexAsync(ctx.Guild.Id, index);
|
||||
var removed = await _service.RemoveByIndexAsync(ctx.Guild.Id, index);
|
||||
if (removed is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.repeater_remove_fail);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var description = GetRepeaterInfoString(removed);
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.repeater_removed(index + 1)))
|
||||
.WithDescription(description));
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.repeater_removed(index + 1)))
|
||||
.WithDescription(description));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task RepeatRedundant(int index)
|
||||
{
|
||||
if (--index < 0)
|
||||
return;
|
||||
|
||||
|
||||
var result = await _service.ToggleRedundantAsync(ctx.Guild.Id, index);
|
||||
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.index_out_of_range);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (result.Value)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.repeater_redundant_no(index + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.repeater_redundant_yes(index + 1));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(-1)]
|
||||
public Task Repeat([Leftover]string message)
|
||||
public Task Repeat([Leftover] string message)
|
||||
=> Repeat(null, null, message);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(0)]
|
||||
public Task Repeat(StoopidTime interval, [Leftover]string message)
|
||||
public Task Repeat(StoopidTime interval, [Leftover] string message)
|
||||
=> Repeat(null, interval, message);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(1)]
|
||||
public Task Repeat(GuildDateTime dt, [Leftover] string message)
|
||||
=> Repeat(dt, null, message);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(2)]
|
||||
public async Task Repeat(GuildDateTime? dt, StoopidTime? interval, [Leftover]string message)
|
||||
public async Task Repeat(GuildDateTime? dt, StoopidTime? interval, [Leftover] string message)
|
||||
{
|
||||
var startTimeOfDay = dt?.InputTimeUtc.TimeOfDay;
|
||||
// if interval not null, that means user specified it (don't change it)
|
||||
|
||||
|
||||
// if interval is null set the default to:
|
||||
// if time of day is specified: 1 day
|
||||
// else 5 minutes
|
||||
var realInterval = interval?.Time ?? (startTimeOfDay is null
|
||||
? TimeSpan.FromMinutes(5)
|
||||
: TimeSpan.FromDays(1));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message)
|
||||
|| (interval != null &&
|
||||
(interval.Time > TimeSpan.FromMinutes(25000) || interval.Time < TimeSpan.FromMinutes(1))))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var realInterval =
|
||||
interval?.Time ?? (startTimeOfDay is null ? TimeSpan.FromMinutes(5) : TimeSpan.FromDays(1));
|
||||
|
||||
message = ((IGuildUser) ctx.User).GuildPermissions.MentionEveryone
|
||||
if (string.IsNullOrWhiteSpace(message)
|
||||
|| (interval != null
|
||||
&& (interval.Time > TimeSpan.FromMinutes(25000) || interval.Time < TimeSpan.FromMinutes(1))))
|
||||
return;
|
||||
|
||||
message = ((IGuildUser)ctx.User).GuildPermissions.MentionEveryone
|
||||
? message
|
||||
: message.SanitizeMentions(true);
|
||||
|
||||
var runner = await _service.AddRepeaterAsync(
|
||||
ctx.Channel.Id,
|
||||
var runner = await _service.AddRepeaterAsync(ctx.Channel.Id,
|
||||
ctx.Guild.Id,
|
||||
realInterval,
|
||||
message,
|
||||
false,
|
||||
startTimeOfDay
|
||||
);
|
||||
startTimeOfDay);
|
||||
|
||||
if (runner is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.repeater_exceed_limit(5));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var description = GetRepeaterInfoString(runner);
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.repeater_created))
|
||||
.WithDescription(description));
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.repeater_created))
|
||||
.WithDescription(description));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task RepeatList()
|
||||
@@ -153,46 +149,37 @@ public partial class Utility
|
||||
await ReplyConfirmLocalizedAsync(strs.repeaters_none);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.list_of_repeaters))
|
||||
.WithOkColor();
|
||||
|
||||
var embed = _eb.Create().WithTitle(GetText(strs.list_of_repeaters)).WithOkColor();
|
||||
|
||||
var i = 0;
|
||||
foreach(var runner in repeaters.OrderBy(r => r.Repeater.Id))
|
||||
foreach (var runner in repeaters.OrderBy(r => r.Repeater.Id))
|
||||
{
|
||||
var description = GetRepeaterInfoString(runner);
|
||||
var name = $"#`{++i}`";
|
||||
embed.AddField(
|
||||
name,
|
||||
description
|
||||
);
|
||||
embed.AddField(name, description);
|
||||
}
|
||||
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
|
||||
private string GetRepeaterInfoString(RunningRepeater runner)
|
||||
{
|
||||
var intervalString = Format.Bold(runner.Repeater.Interval.ToPrettyStringHm());
|
||||
var executesIn = runner.NextTime < DateTime.UtcNow
|
||||
? TimeSpan.Zero
|
||||
: runner.NextTime - DateTime.UtcNow;
|
||||
var executesIn = runner.NextTime < DateTime.UtcNow ? TimeSpan.Zero : runner.NextTime - DateTime.UtcNow;
|
||||
var executesInString = Format.Bold(executesIn.ToPrettyStringHm());
|
||||
var message = Format.Sanitize(runner.Repeater.Message.TrimTo(50));
|
||||
|
||||
|
||||
var description = string.Empty;
|
||||
if (_service.IsNoRedundant(runner.Repeater.Id))
|
||||
{
|
||||
description = Format.Underline(Format.Bold(GetText(strs.no_redundant))) + "\n\n";
|
||||
}
|
||||
|
||||
description += $"<#{runner.Repeater.ChannelId}>\n" +
|
||||
$"`{GetText(strs.interval_colon)}` {intervalString}\n" +
|
||||
$"`{GetText(strs.executes_in_colon)}` {executesInString}\n" +
|
||||
$"`{GetText(strs.message_colon)}` {message}";
|
||||
|
||||
|
||||
description += $"<#{runner.Repeater.ChannelId}>\n"
|
||||
+ $"`{GetText(strs.interval_colon)}` {intervalString}\n"
|
||||
+ $"`{GetText(strs.executes_in_colon)}` {executesInString}\n"
|
||||
+ $"`{GetText(strs.message_colon)}` {message}";
|
||||
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +1,15 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services;
|
||||
|
||||
public class CommandMapService : IInputTransformer, INService
|
||||
{
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new();
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
private readonly DbService _db;
|
||||
|
||||
@@ -22,16 +21,13 @@ public class CommandMapService : IInputTransformer, INService
|
||||
using var uow = db.GetDbContext();
|
||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||
var configs = uow.Set<GuildConfig>()
|
||||
.Include(gc => gc.CommandAliases)
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.ToList();
|
||||
|
||||
AliasMaps = new(configs
|
||||
.ToDictionary(
|
||||
x => x.GuildId,
|
||||
x => new ConcurrentDictionary<string, string>(x.CommandAliases
|
||||
.DistinctBy(ca => ca.Trigger)
|
||||
.ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
|
||||
.Include(gc => gc.CommandAliases)
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.ToList();
|
||||
|
||||
AliasMaps = new(configs.ToDictionary(x => x.GuildId,
|
||||
x => new ConcurrentDictionary<string, string>(x.CommandAliases.DistinctBy(ca => ca.Trigger)
|
||||
.ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
|
||||
|
||||
_db = db;
|
||||
}
|
||||
@@ -49,7 +45,11 @@ public class CommandMapService : IInputTransformer, INService
|
||||
return count;
|
||||
}
|
||||
|
||||
public async Task<string> TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input)
|
||||
public async Task<string> TransformInput(
|
||||
IGuild guild,
|
||||
IMessageChannel channel,
|
||||
IUser user,
|
||||
string input)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
@@ -57,11 +57,9 @@ public class CommandMapService : IInputTransformer, INService
|
||||
return input;
|
||||
|
||||
if (guild != null)
|
||||
{
|
||||
if (AliasMaps.TryGetValue(guild.Id, out var maps))
|
||||
{
|
||||
var keys = maps.Keys
|
||||
.OrderByDescending(x => x.Length);
|
||||
var keys = maps.Keys.OrderByDescending(x => x.Length);
|
||||
|
||||
foreach (var k in keys)
|
||||
{
|
||||
@@ -79,18 +77,15 @@ public class CommandMapService : IInputTransformer, INService
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1500);
|
||||
await toDelete.DeleteAsync(new()
|
||||
{
|
||||
RetryMode = RetryMode.AlwaysRetry
|
||||
});
|
||||
await toDelete.DeleteAsync(new() { RetryMode = RetryMode.AlwaysRetry });
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
|
||||
return newInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,11 +6,8 @@ namespace NadekoBot.Modules.Utility.Services;
|
||||
|
||||
public class ConverterService : INService
|
||||
{
|
||||
public ConvertUnit[] Units =>
|
||||
_cache.Redis.GetDatabase()
|
||||
.StringGet("converter_units")
|
||||
.ToString()
|
||||
.MapJson<ConvertUnit[]>();
|
||||
public ConvertUnit[] Units
|
||||
=> _cache.Redis.GetDatabase().StringGet("converter_units").ToString().MapJson<ConvertUnit[]>();
|
||||
|
||||
private readonly Timer _currencyUpdater;
|
||||
private readonly TimeSpan _updateInterval = new(12, 0, 0);
|
||||
@@ -18,20 +15,21 @@ public class ConverterService : INService
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public ConverterService(DiscordSocketClient client, DbService db,
|
||||
IDataCache cache, IHttpClientFactory factory)
|
||||
public ConverterService(
|
||||
DiscordSocketClient client,
|
||||
DbService db,
|
||||
IDataCache cache,
|
||||
IHttpClientFactory factory)
|
||||
{
|
||||
_db = db;
|
||||
_cache = cache;
|
||||
_httpFactory = factory;
|
||||
|
||||
if (client.ShardId == 0)
|
||||
{
|
||||
_currencyUpdater = new(async shouldLoad => await UpdateCurrency((bool)shouldLoad),
|
||||
client.ShardId == 0,
|
||||
TimeSpan.Zero,
|
||||
_updateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Rates> GetCurrencyRates()
|
||||
@@ -49,26 +47,21 @@ public class ConverterService : INService
|
||||
if (shouldLoad)
|
||||
{
|
||||
var currencyRates = await GetCurrencyRates();
|
||||
var baseType = new ConvertUnit()
|
||||
var baseType = new ConvertUnit
|
||||
{
|
||||
Triggers = new[] { currencyRates.Base },
|
||||
Modifier = decimal.One,
|
||||
UnitType = unitTypeString
|
||||
Triggers = new[] { currencyRates.Base }, Modifier = decimal.One, UnitType = unitTypeString
|
||||
};
|
||||
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
|
||||
{
|
||||
Triggers = new[] { u.Key },
|
||||
Modifier = u.Value,
|
||||
UnitType = unitTypeString
|
||||
}).ToArray();
|
||||
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit
|
||||
{
|
||||
Triggers = new[] { u.Key }, Modifier = u.Value, UnitType = unitTypeString
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
var fileData = JsonConvert.DeserializeObject<ConvertUnit[]>(
|
||||
File.ReadAllText("data/units.json"))
|
||||
.Where(x => x.UnitType != "currency");
|
||||
var fileData = JsonConvert.DeserializeObject<ConvertUnit[]>(File.ReadAllText("data/units.json"))
|
||||
.Where(x => x.UnitType != "currency");
|
||||
|
||||
var data = JsonConvert.SerializeObject(range.Append(baseType).Concat(fileData).ToList());
|
||||
_cache.Redis.GetDatabase()
|
||||
.StringSet("converter_units", data);
|
||||
_cache.Redis.GetDatabase().StringSet("converter_units", data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -82,6 +75,7 @@ public class Rates
|
||||
{
|
||||
public string Base { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
[JsonProperty("rates")]
|
||||
public Dictionary<string, decimal> ConversionRates { get; set; }
|
||||
}
|
||||
}
|
@@ -7,17 +7,34 @@ public class InviteService : INService
|
||||
{
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
[Option('m', "max-uses", Required = false, Default = 0, HelpText = "Maximum number of times the invite can be used. Default 0 (never).")]
|
||||
public int MaxUses { get; set; } = 0;
|
||||
[Option('m',
|
||||
"max-uses",
|
||||
Required = false,
|
||||
Default = 0,
|
||||
HelpText = "Maximum number of times the invite can be used. Default 0 (never).")]
|
||||
public int MaxUses { get; set; }
|
||||
|
||||
[Option('u', "unique", Required = false, Default = false, HelpText = "Not setting this flag will result in bot getting the existing invite with the same settings if it exists, instead of creating a new one.")]
|
||||
[Option('u',
|
||||
"unique",
|
||||
Required = false,
|
||||
Default = false,
|
||||
HelpText =
|
||||
"Not setting this flag will result in bot getting the existing invite with the same settings if it exists, instead of creating a new one.")]
|
||||
public bool Unique { get; set; } = false;
|
||||
|
||||
[Option('t', "temporary", Required = false, Default = false, HelpText = "If this flag is set, the user will be kicked from the guild once they close their client.")]
|
||||
[Option('t',
|
||||
"temporary",
|
||||
Required = false,
|
||||
Default = false,
|
||||
HelpText = "If this flag is set, the user will be kicked from the guild once they close their client.")]
|
||||
public bool Temporary { get; set; } = false;
|
||||
|
||||
[Option('e', "expire", Required = false, Default = 0, HelpText = "Time in seconds to expire the invite. Default 0 (no expiry).")]
|
||||
public int Expire { get; set; } = 0;
|
||||
[Option('e',
|
||||
"expire",
|
||||
Required = false,
|
||||
Default = 0,
|
||||
HelpText = "Time in seconds to expire the invite. Default 0 (no expiry).")]
|
||||
public int Expire { get; set; }
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
@@ -28,4 +45,4 @@ public class InviteService : INService
|
||||
Expire = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,23 +1,24 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Modules.Utility.Common.Patreon;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Utility.Common.Patreon;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using StackExchange.Redis;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services;
|
||||
|
||||
public class PatreonRewardsService : INService
|
||||
{
|
||||
public TimeSpan Interval { get; } = TimeSpan.FromMinutes(3);
|
||||
|
||||
public DateTime LastUpdate { get; private set; } = DateTime.UtcNow;
|
||||
private readonly SemaphoreSlim getPledgesLocker = new(1, 1);
|
||||
|
||||
private readonly Timer _updater;
|
||||
private readonly SemaphoreSlim claimLockJustInCase = new(1, 1);
|
||||
|
||||
public TimeSpan Interval { get; } = TimeSpan.FromMinutes(3);
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _currency;
|
||||
private readonly GamblingConfigService _gamblingConfigService;
|
||||
@@ -27,8 +28,6 @@ public class PatreonRewardsService : INService
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public DateTime LastUpdate { get; private set; } = DateTime.UtcNow;
|
||||
|
||||
public PatreonRewardsService(
|
||||
DbService db,
|
||||
ICurrencyService currency,
|
||||
@@ -49,8 +48,7 @@ public class PatreonRewardsService : INService
|
||||
_client = client;
|
||||
|
||||
if (client.ShardId == 0)
|
||||
_updater = new(async _ => await RefreshPledges(_credsProvider.GetCreds()),
|
||||
null, TimeSpan.Zero, Interval);
|
||||
_updater = new(async _ => await RefreshPledges(_credsProvider.GetCreds()), null, TimeSpan.Zero, Interval);
|
||||
}
|
||||
|
||||
private DateTime LastAccessTokenUpdate(IBotCredentials creds)
|
||||
@@ -65,36 +63,17 @@ public class PatreonRewardsService : INService
|
||||
return lastTime;
|
||||
}
|
||||
|
||||
|
||||
private sealed class PatreonRefreshData
|
||||
{
|
||||
[JsonPropertyName("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
[JsonPropertyName("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
[JsonPropertyName("expires_in")]
|
||||
public long ExpiresIn { get; set; }
|
||||
|
||||
[JsonPropertyName("scope")]
|
||||
public string Scope { get; set; }
|
||||
|
||||
[JsonPropertyName("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateAccessToken(IBotCredentials creds)
|
||||
{
|
||||
Log.Information("Updating patreon access token...");
|
||||
try
|
||||
{
|
||||
using var http = _httpFactory.CreateClient();
|
||||
var res = await http.PostAsync($"https://www.patreon.com/api/oauth2/token" +
|
||||
$"?grant_type=refresh_token" +
|
||||
$"&refresh_token={creds.Patreon.RefreshToken}" +
|
||||
$"&client_id={creds.Patreon.ClientId}" +
|
||||
$"&client_secret={creds.Patreon.ClientSecret}",
|
||||
var res = await http.PostAsync("https://www.patreon.com/api/oauth2/token"
|
||||
+ "?grant_type=refresh_token"
|
||||
+ $"&refresh_token={creds.Patreon.RefreshToken}"
|
||||
+ $"&client_id={creds.Patreon.ClientId}"
|
||||
+ $"&client_secret={creds.Patreon.ClientSecret}",
|
||||
new StringContent(string.Empty));
|
||||
|
||||
res.EnsureSuccessStatusCode();
|
||||
@@ -103,7 +82,7 @@ public class PatreonRewardsService : INService
|
||||
|
||||
if (data is null)
|
||||
throw new("Invalid patreon response.");
|
||||
|
||||
|
||||
_credsProvider.ModifyCredsFile(oldData =>
|
||||
{
|
||||
oldData.Patreon.AccessToken = data.AccessToken;
|
||||
@@ -126,9 +105,7 @@ public class PatreonRewardsService : INService
|
||||
var _1 = creds.Patreon.ClientId;
|
||||
var _2 = creds.Patreon.ClientSecret;
|
||||
var _4 = creds.Patreon.RefreshToken;
|
||||
return !(string.IsNullOrWhiteSpace(_1)
|
||||
|| string.IsNullOrWhiteSpace(_2)
|
||||
|| string.IsNullOrWhiteSpace(_4));
|
||||
return !(string.IsNullOrWhiteSpace(_1) || string.IsNullOrWhiteSpace(_2) || string.IsNullOrWhiteSpace(_4));
|
||||
}
|
||||
|
||||
public async Task RefreshPledges(IBotCredentials creds)
|
||||
@@ -154,7 +131,6 @@ public class PatreonRewardsService : INService
|
||||
await getPledgesLocker.WaitAsync();
|
||||
try
|
||||
{
|
||||
|
||||
var members = new List<PatreonMember>();
|
||||
var users = new List<PatreonUser>();
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
@@ -163,10 +139,10 @@ public class PatreonRewardsService : INService
|
||||
http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization",
|
||||
$"Bearer {creds.Patreon.AccessToken}");
|
||||
|
||||
var page = $"https://www.patreon.com/api/oauth2/v2/campaigns/{creds.Patreon.CampaignId}/members" +
|
||||
"?fields%5Bmember%5D=full_name,currently_entitled_amount_cents" +
|
||||
"&fields%5Buser%5D=social_connections" +
|
||||
"&include=user";
|
||||
var page = $"https://www.patreon.com/api/oauth2/v2/campaigns/{creds.Patreon.CampaignId}/members"
|
||||
+ "?fields%5Bmember%5D=full_name,currently_entitled_amount_cents"
|
||||
+ "&fields%5Buser%5D=social_connections"
|
||||
+ "&include=user";
|
||||
PatreonResponse data = null;
|
||||
do
|
||||
{
|
||||
@@ -175,35 +151,33 @@ public class PatreonRewardsService : INService
|
||||
|
||||
if (data is null)
|
||||
break;
|
||||
|
||||
|
||||
members.AddRange(data.Data);
|
||||
users.AddRange(data.Included);
|
||||
} while (!string.IsNullOrWhiteSpace(page = data?.Links?.Next));
|
||||
}
|
||||
|
||||
var userData = members.Join(users,
|
||||
m => m.Relationships.User.Data.Id,
|
||||
u => u.Id,
|
||||
(m, u) => new
|
||||
{
|
||||
PatreonUserId = m.Relationships.User.Data.Id,
|
||||
UserId = ulong.TryParse(u.Attributes?.SocialConnections?.Discord?.UserId ?? string.Empty,
|
||||
out var userId)
|
||||
? userId
|
||||
: 0,
|
||||
EntitledTo = m.Attributes.CurrentlyEntitledAmountCents,
|
||||
})
|
||||
.Where(x => x is
|
||||
{
|
||||
UserId: not 0,
|
||||
EntitledTo: > 0
|
||||
})
|
||||
.ToList();
|
||||
m => m.Relationships.User.Data.Id,
|
||||
u => u.Id,
|
||||
(m, u) => new
|
||||
{
|
||||
PatreonUserId = m.Relationships.User.Data.Id,
|
||||
UserId = ulong.TryParse(
|
||||
u.Attributes?.SocialConnections?.Discord?.UserId ?? string.Empty,
|
||||
out var userId)
|
||||
? userId
|
||||
: 0,
|
||||
EntitledTo = m.Attributes.CurrentlyEntitledAmountCents
|
||||
})
|
||||
.Where(x => x is
|
||||
{
|
||||
UserId: not 0,
|
||||
EntitledTo: > 0
|
||||
})
|
||||
.ToList();
|
||||
|
||||
foreach (var pledge in userData)
|
||||
{
|
||||
await ClaimReward(pledge.UserId, pledge.PatreonUserId, pledge.EntitledTo);
|
||||
}
|
||||
foreach (var pledge in userData) await ClaimReward(pledge.UserId, pledge.PatreonUserId, pledge.EntitledTo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -213,7 +187,6 @@ public class PatreonRewardsService : INService
|
||||
{
|
||||
getPledgesLocker.Release();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<int> ClaimReward(ulong userId, string patreonUserId, int cents)
|
||||
@@ -233,18 +206,16 @@ public class PatreonRewardsService : INService
|
||||
{
|
||||
users.Add(new()
|
||||
{
|
||||
PatreonUserId = patreonUserId,
|
||||
LastReward = now,
|
||||
AmountRewardedThisMonth = eligibleFor,
|
||||
PatreonUserId = patreonUserId, LastReward = now, AmountRewardedThisMonth = eligibleFor
|
||||
});
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
await _currency.AddAsync(userId, "Patreon reward - new", eligibleFor, gamble: true);
|
||||
|
||||
await _currency.AddAsync(userId, "Patreon reward - new", eligibleFor, true);
|
||||
|
||||
Log.Information($"Sending new currency reward to {userId}");
|
||||
await SendMessageToUser(userId, $"Thank you for your pledge! " +
|
||||
$"You've been awarded **{eligibleFor}**{settings.Currency.Sign} !");
|
||||
await SendMessageToUser(userId,
|
||||
"Thank you for your pledge! " + $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} !");
|
||||
return eligibleFor;
|
||||
}
|
||||
|
||||
@@ -255,11 +226,12 @@ public class PatreonRewardsService : INService
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
await _currency.AddAsync(userId, "Patreon reward - recurring", eligibleFor, gamble: true);
|
||||
await _currency.AddAsync(userId, "Patreon reward - recurring", eligibleFor, true);
|
||||
|
||||
Log.Information($"Sending recurring currency reward to {userId}");
|
||||
await SendMessageToUser(userId, $"Thank you for your continued support! " +
|
||||
$"You've been awarded **{eligibleFor}**{settings.Currency.Sign} for this month's support!");
|
||||
await SendMessageToUser(userId,
|
||||
"Thank you for your continued support! "
|
||||
+ $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} for this month's support!");
|
||||
|
||||
return eligibleFor;
|
||||
}
|
||||
@@ -272,11 +244,12 @@ public class PatreonRewardsService : INService
|
||||
usr.AmountRewardedThisMonth = toAward;
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
await _currency.AddAsync(userId, "Patreon reward - update", toAward, gamble: true);
|
||||
|
||||
await _currency.AddAsync(userId, "Patreon reward - update", toAward, true);
|
||||
|
||||
Log.Information($"Sending updated currency reward to {userId}");
|
||||
await SendMessageToUser(userId, $"Thank you for increasing your pledge! " +
|
||||
$"You've been awarded an additional **{toAward}**{settings.Currency.Sign} !");
|
||||
await SendMessageToUser(userId,
|
||||
"Thank you for increasing your pledge! "
|
||||
+ $"You've been awarded an additional **{toAward}**{settings.Currency.Sign} !");
|
||||
return toAward;
|
||||
}
|
||||
|
||||
@@ -295,7 +268,7 @@ public class PatreonRewardsService : INService
|
||||
var user = (IUser)_client.GetUser(userId) ?? await _client.Rest.GetUserAsync(userId);
|
||||
if (user is null)
|
||||
return;
|
||||
|
||||
|
||||
await user.SendConfirmAsync(_eb, message);
|
||||
}
|
||||
catch
|
||||
@@ -303,4 +276,23 @@ public class PatreonRewardsService : INService
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private sealed class PatreonRefreshData
|
||||
{
|
||||
[JsonPropertyName("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
[JsonPropertyName("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
[JsonPropertyName("expires_in")]
|
||||
public long ExpiresIn { get; set; }
|
||||
|
||||
[JsonPropertyName("scope")]
|
||||
public string Scope { get; set; }
|
||||
|
||||
[JsonPropertyName("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
}
|
||||
}
|
@@ -1,21 +1,27 @@
|
||||
#nullable disable
|
||||
using System.Text.RegularExpressions;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services;
|
||||
|
||||
public class RemindService : INService
|
||||
{
|
||||
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]|.)+)",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
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]|.)+)",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public RemindService(DiscordSocketClient client, DbService db, IBotCredentials creds, IEmbedBuilderService eb)
|
||||
public RemindService(
|
||||
DiscordSocketClient client,
|
||||
DbService db,
|
||||
IBotCredentials creds,
|
||||
IEmbedBuilderService eb)
|
||||
{
|
||||
_client = client;
|
||||
_db = db;
|
||||
@@ -35,16 +41,16 @@ public class RemindService : INService
|
||||
var reminders = await GetRemindersBeforeAsync(now);
|
||||
if (reminders.Count == 0)
|
||||
continue;
|
||||
|
||||
|
||||
Log.Information($"Executing {reminders.Count} reminders.");
|
||||
|
||||
|
||||
// make groups of 5, with 1.5 second inbetween each one to ensure against ratelimits
|
||||
foreach (var group in reminders.Chunk(5))
|
||||
{
|
||||
var executedReminders = group.ToList();
|
||||
await executedReminders.Select(ReminderTimerAction).WhenAll();
|
||||
await RemoveReminders(executedReminders);
|
||||
await Task.Delay(1500);
|
||||
await Task.Delay(1500);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -58,8 +64,7 @@ public class RemindService : INService
|
||||
private async Task RemoveReminders(List<Reminder> reminders)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
uow.Set<Reminder>()
|
||||
.RemoveRange(reminders);
|
||||
uow.Set<Reminder>().RemoveRange(reminders);
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
@@ -68,14 +73,9 @@ public class RemindService : INService
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.Reminders
|
||||
.FromSqlInterpolated($"select * from reminders where ((serverid >> 22) % {_creds.TotalShards}) == {_client.ShardId} and \"when\" < {now};")
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public struct RemindObject
|
||||
{
|
||||
public string What { get; set; }
|
||||
public TimeSpan Time { get; set; }
|
||||
.FromSqlInterpolated(
|
||||
$"select * from reminders where ((serverid >> 22) % {_creds.TotalShards}) == {_client.ShardId} and \"when\" < {now};")
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public bool TryParseRemindMessage(string input, out RemindObject obj)
|
||||
@@ -83,13 +83,10 @@ public class RemindService : INService
|
||||
var m = _regex.Match(input);
|
||||
|
||||
obj = default;
|
||||
if (m.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m.Length == 0) return false;
|
||||
|
||||
var values = new Dictionary<string, int>();
|
||||
|
||||
|
||||
var what = m.Groups["what"].Value;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(what))
|
||||
@@ -97,7 +94,7 @@ public class RemindService : INService
|
||||
Log.Warning("No message provided for the reminder.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
foreach (var groupName in _regex.GetGroupNames())
|
||||
{
|
||||
if (groupName is "0" or "what") continue;
|
||||
@@ -106,6 +103,7 @@ public class RemindService : INService
|
||||
values[groupName] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!int.TryParse(m.Groups[groupName].Value, out var value))
|
||||
{
|
||||
Log.Warning($"Reminder regex group {groupName} has invalid value.");
|
||||
@@ -117,23 +115,13 @@ public class RemindService : INService
|
||||
Log.Warning("Reminder time value has to be an integer greater than 0.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
values[groupName] = value;
|
||||
}
|
||||
|
||||
var ts = new TimeSpan
|
||||
(
|
||||
(30 * values["mo"]) + (7 * values["w"]) + values["d"],
|
||||
values["h"],
|
||||
values["m"],
|
||||
0
|
||||
);
|
||||
|
||||
obj = new()
|
||||
{
|
||||
Time = ts,
|
||||
What = what
|
||||
};
|
||||
var ts = new TimeSpan((30 * values["mo"]) + (7 * values["w"]) + values["d"], values["h"], values["m"], 0);
|
||||
|
||||
obj = new() { Time = ts, What = what };
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -154,16 +142,25 @@ public class RemindService : INService
|
||||
{
|
||||
ch = _client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId);
|
||||
}
|
||||
|
||||
if (ch is null)
|
||||
return;
|
||||
|
||||
await ch.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("Reminder")
|
||||
.AddField("Created At", r.DateAdded.HasValue ? r.DateAdded.Value.ToLongDateString() : "?")
|
||||
.AddField("By", (await ch.GetUserAsync(r.UserId))?.ToString() ?? r.UserId.ToString()),
|
||||
msg: r.Message);
|
||||
.WithOkColor()
|
||||
.WithTitle("Reminder")
|
||||
.AddField("Created At",
|
||||
r.DateAdded.HasValue ? r.DateAdded.Value.ToLongDateString() : "?")
|
||||
.AddField("By",
|
||||
(await ch.GetUserAsync(r.UserId))?.ToString() ?? r.UserId.ToString()),
|
||||
r.Message);
|
||||
}
|
||||
catch (Exception ex) { Log.Information(ex.Message + $"({r.Id})"); }
|
||||
}
|
||||
}
|
||||
|
||||
public struct RemindObject
|
||||
{
|
||||
public string What { get; set; }
|
||||
public TimeSpan Time { get; set; }
|
||||
}
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using LinqToDB;
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
@@ -20,30 +19,29 @@ public sealed class RepeaterService : IReadyExecutor, INService
|
||||
|
||||
private readonly object _queueLocker = new();
|
||||
|
||||
public RepeaterService(DiscordSocketClient client, DbService db, IBotCredentials creds, IEmbedBuilderService eb)
|
||||
public RepeaterService(
|
||||
DiscordSocketClient client,
|
||||
DbService db,
|
||||
IBotCredentials creds,
|
||||
IEmbedBuilderService eb)
|
||||
{
|
||||
_db = db;
|
||||
_creds = creds;
|
||||
_eb = eb;
|
||||
_client = client;
|
||||
|
||||
|
||||
var uow = _db.GetDbContext();
|
||||
var shardRepeaters = uow
|
||||
.Set<Repeater>()
|
||||
.FromSqlInterpolated($@"select * from repeaters
|
||||
var shardRepeaters = uow.Set<Repeater>()
|
||||
.FromSqlInterpolated($@"select * from repeaters
|
||||
where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
.AsNoTracking()
|
||||
.ToList();
|
||||
.AsNoTracking()
|
||||
.ToList();
|
||||
|
||||
_noRedundant = new(shardRepeaters
|
||||
.Where(x => x.NoRedundant)
|
||||
.Select(x => x.Id));
|
||||
_noRedundant = new(shardRepeaters.Where(x => x.NoRedundant).Select(x => x.Id));
|
||||
|
||||
_repeaterQueue = new(shardRepeaters
|
||||
.Select(rep => new RunningRepeater(rep))
|
||||
.OrderBy(x => x.NextTime));
|
||||
_repeaterQueue = new(shardRepeaters.Select(rep => new RunningRepeater(rep)).OrderBy(x => x.NextTime));
|
||||
}
|
||||
|
||||
|
||||
public Task OnReadyAsync()
|
||||
{
|
||||
_ = Task.Run(RunRepeatersLoop);
|
||||
@@ -53,19 +51,16 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
private async Task RunRepeatersLoop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// calculate timeout for the first item
|
||||
var timeout = GetNextTimeout();
|
||||
|
||||
|
||||
// wait it out, and recalculate afterwards
|
||||
// because repeaters might've been modified meanwhile
|
||||
if (timeout > TimeSpan.Zero)
|
||||
{
|
||||
await Task.Delay(timeout > TimeSpan.FromMinutes(1)
|
||||
? TimeSpan.FromMinutes(1)
|
||||
: timeout);
|
||||
await Task.Delay(timeout > TimeSpan.FromMinutes(1) ? TimeSpan.FromMinutes(1) : timeout);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -78,7 +73,6 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
var current = _repeaterQueue.First;
|
||||
while (true)
|
||||
{
|
||||
|
||||
if (current is null || current.Value.NextTime > now)
|
||||
break;
|
||||
|
||||
@@ -88,23 +82,16 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
}
|
||||
|
||||
// execute
|
||||
foreach (var chunk in toExecute.Chunk(5))
|
||||
{
|
||||
await chunk.Select(Trigger).WhenAll();
|
||||
}
|
||||
foreach (var chunk in toExecute.Chunk(5)) await chunk.Select(Trigger).WhenAll();
|
||||
|
||||
// reinsert
|
||||
foreach (var rep in toExecute)
|
||||
{
|
||||
await HandlePostExecute(rep);
|
||||
}
|
||||
foreach (var rep in toExecute) await HandlePostExecute(rep);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Critical error in repeater queue: {ErrorMessage}", ex.Message);
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandlePostExecute(RunningRepeater rep)
|
||||
@@ -132,12 +119,11 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
public async Task<bool> TriggerExternal(ulong guildId, int index)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
var toTrigger = await uow.Repeaters
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsyncEF();
|
||||
|
||||
var toTrigger = await uow.Repeaters.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsyncEF();
|
||||
|
||||
if (toTrigger is null)
|
||||
return false;
|
||||
@@ -148,7 +134,7 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
node = _repeaterQueue.FindNode(x => x.Repeater.Id == toTrigger.Id);
|
||||
if (node is null)
|
||||
return false;
|
||||
|
||||
|
||||
_repeaterQueue.Remove(node);
|
||||
}
|
||||
|
||||
@@ -156,7 +142,7 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
await HandlePostExecute(node.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void AddToQueue(RunningRepeater rep)
|
||||
{
|
||||
lock (_queueLocker)
|
||||
@@ -191,21 +177,23 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
return first.Value.NextTime - DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task Trigger(RunningRepeater rr)
|
||||
{
|
||||
var repeater = rr.Repeater;
|
||||
|
||||
|
||||
void ChannelMissingError()
|
||||
{
|
||||
rr.ErrorCount = Int32.MaxValue;
|
||||
Log.Warning("[Repeater] Channel [{Channelid}] for not found or insufficient permissions. " +
|
||||
"Repeater will be removed. ", repeater.ChannelId);
|
||||
rr.ErrorCount = int.MaxValue;
|
||||
Log.Warning("[Repeater] Channel [{Channelid}] for not found or insufficient permissions. "
|
||||
+ "Repeater will be removed. ",
|
||||
repeater.ChannelId);
|
||||
}
|
||||
|
||||
var channel = _client.GetChannel(repeater.ChannelId) as ITextChannel;
|
||||
if (channel is null)
|
||||
try { channel = await _client.Rest.GetChannelAsync(repeater.ChannelId) as ITextChannel; } catch { }
|
||||
try { channel = await _client.Rest.GetChannelAsync(repeater.ChannelId) as ITextChannel; }
|
||||
catch { }
|
||||
|
||||
if (channel is null)
|
||||
{
|
||||
@@ -219,9 +207,8 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
ChannelMissingError();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (_noRedundant.Contains(repeater.Id))
|
||||
{
|
||||
try
|
||||
{
|
||||
var lastMsgInChannel = await channel.GetMessagesAsync(2).Flatten().FirstAsync();
|
||||
@@ -231,41 +218,36 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex,
|
||||
"[Repeater] Error while getting last channel message in {GuildId}/{ChannelId} " +
|
||||
"Bot probably doesn't have the permission to read message history",
|
||||
"[Repeater] Error while getting last channel message in {GuildId}/{ChannelId} "
|
||||
+ "Bot probably doesn't have the permission to read message history",
|
||||
guild.Id,
|
||||
channel.Id);
|
||||
}
|
||||
}
|
||||
|
||||
if (repeater.LastMessageId is { } lastMessageId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var oldMsg = await channel.GetMessageAsync(lastMessageId);
|
||||
if (oldMsg != null)
|
||||
{
|
||||
await oldMsg.DeleteAsync();
|
||||
}
|
||||
if (oldMsg != null) await oldMsg.DeleteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "[Repeater] Error while deleting previous message in {GuildId}/{ChannelId}", guild.Id, channel.Id);
|
||||
Log.Warning(ex,
|
||||
"[Repeater] Error while deleting previous message in {GuildId}/{ChannelId}",
|
||||
guild.Id,
|
||||
channel.Id);
|
||||
}
|
||||
}
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(guild.CurrentUser, channel, guild, _client)
|
||||
.Build();
|
||||
|
||||
var rep = new ReplacementBuilder().WithDefault(guild.CurrentUser, channel, guild, _client).Build();
|
||||
|
||||
try
|
||||
{
|
||||
var text = SmartText.CreateFrom(repeater.Message);
|
||||
text = rep.Replace(text);
|
||||
|
||||
|
||||
var newMsg = await channel.SendAsync(text);
|
||||
_ = newMsg.AddReactionAsync(new Emoji("🔄"));
|
||||
|
||||
|
||||
if (_noRedundant.Contains(repeater.Id))
|
||||
{
|
||||
await SetRepeaterLastMessageInternal(repeater.Id, newMsg.Id);
|
||||
@@ -278,17 +260,15 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
{
|
||||
Log.Error(ex, "[Repeater] Error sending repeat message ({ErrorCount})", rr.ErrorCount++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveRepeaterInternal(Repeater r)
|
||||
{
|
||||
_noRedundant.TryRemove(r.Id);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow
|
||||
.Repeaters
|
||||
.DeleteAsync(x => x.Id == r.Id);
|
||||
|
||||
await uow.Repeaters.DeleteAsync(x => x.Id == r.Id);
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
@@ -299,7 +279,7 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
var node = _repeaterQueue.FindNode(x => x.Repeater.Id == id);
|
||||
if (node is null)
|
||||
return null;
|
||||
|
||||
|
||||
_repeaterQueue.Remove(node);
|
||||
return node.Value;
|
||||
}
|
||||
@@ -308,13 +288,9 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
private async Task SetRepeaterLastMessageInternal(int repeaterId, ulong lastMsgId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.Repeaters
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == repeaterId)
|
||||
.UpdateAsync(rep => new()
|
||||
{
|
||||
LastMessageId = lastMsgId
|
||||
});
|
||||
await uow.Repeaters.AsQueryable()
|
||||
.Where(x => x.Id == repeaterId)
|
||||
.UpdateAsync(rep => new() { LastMessageId = lastMsgId });
|
||||
}
|
||||
|
||||
public async Task<RunningRepeater?> AddRepeaterAsync(
|
||||
@@ -323,10 +299,9 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
TimeSpan interval,
|
||||
string message,
|
||||
bool isNoRedundant,
|
||||
TimeSpan? startTimeOfDay
|
||||
)
|
||||
TimeSpan? startTimeOfDay)
|
||||
{
|
||||
var rep = new Repeater()
|
||||
var rep = new Repeater
|
||||
{
|
||||
ChannelId = channelId,
|
||||
GuildId = guildId,
|
||||
@@ -360,15 +335,14 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toRemove = await uow.Repeaters
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsyncEF();
|
||||
var toRemove = await uow.Repeaters.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsyncEF();
|
||||
|
||||
if (toRemove is null)
|
||||
return null;
|
||||
|
||||
|
||||
// first try removing from queue because it can fail
|
||||
// while triggering. Instruct user to try again
|
||||
var removed = RemoveFromQueue(toRemove.Id);
|
||||
@@ -392,25 +366,19 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
public async Task<bool?> ToggleRedundantAsync(ulong guildId, int index)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toToggle = await uow
|
||||
.Repeaters
|
||||
.AsQueryable()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsyncEF();
|
||||
var toToggle = await uow.Repeaters.AsQueryable()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsyncEF();
|
||||
|
||||
if (toToggle is null)
|
||||
return null;
|
||||
|
||||
|
||||
var newValue = toToggle.NoRedundant = !toToggle.NoRedundant;
|
||||
if (newValue)
|
||||
{
|
||||
_noRedundant.Add(toToggle.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_noRedundant.TryRemove(toToggle.Id);
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
return newValue;
|
||||
@@ -418,4 +386,4 @@ where ((guildid >> 22) % {_creds.TotalShards}) == {_client.ShardId};")
|
||||
|
||||
public bool IsNoRedundant(int repeaterId)
|
||||
=> _noRedundant.Contains(repeaterId);
|
||||
}
|
||||
}
|
@@ -6,13 +6,13 @@ namespace NadekoBot.Modules.Utility.Services;
|
||||
public sealed class RunningRepeater
|
||||
{
|
||||
public DateTime NextTime { get; private set; }
|
||||
|
||||
|
||||
public Repeater Repeater { get; }
|
||||
public int ErrorCount { get; set; }
|
||||
|
||||
|
||||
public RunningRepeater(Repeater repeater)
|
||||
{
|
||||
this.Repeater = repeater;
|
||||
Repeater = repeater;
|
||||
NextTime = CalculateInitialExecution();
|
||||
}
|
||||
|
||||
@@ -38,16 +38,12 @@ public sealed class RunningRepeater
|
||||
// if added timeofday is less than specified timeofday for initial trigger
|
||||
// that means the repeater first ran that same day at that exact specified time
|
||||
if (added.TimeOfDay <= initialTriggerTimeOfDay)
|
||||
{
|
||||
// in that case, just add the difference to make sure the timeofday is the same
|
||||
initialDateTime = added + (initialTriggerTimeOfDay - added.TimeOfDay);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if not, then it ran at that time the following day
|
||||
// in other words; Add one day, and subtract how much time passed since that time of day
|
||||
initialDateTime = added + TimeSpan.FromDays(1) - (added.TimeOfDay - initialTriggerTimeOfDay);
|
||||
}
|
||||
|
||||
return CalculateInitialInterval(initialDateTime);
|
||||
}
|
||||
@@ -57,7 +53,7 @@ public sealed class RunningRepeater
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate when is the proper time to run the repeater again based on initial time repeater ran.
|
||||
/// Calculate when is the proper time to run the repeater again based on initial time repeater ran.
|
||||
/// </summary>
|
||||
/// <param name="repeaterter"></param>
|
||||
/// <param name="initialDateTime">Initial time repeater ran at (or should run at).</param>
|
||||
@@ -65,11 +61,8 @@ public sealed class RunningRepeater
|
||||
{
|
||||
// if the initial time is greater than now, that means the repeater didn't still execute a single time.
|
||||
// just schedule it
|
||||
if (initialDateTime > DateTime.UtcNow)
|
||||
{
|
||||
return initialDateTime;
|
||||
}
|
||||
|
||||
if (initialDateTime > DateTime.UtcNow) return initialDateTime;
|
||||
|
||||
// else calculate based on minutes difference
|
||||
|
||||
// get the difference
|
||||
@@ -92,8 +85,8 @@ public sealed class RunningRepeater
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is RunningRepeater rr && rr.Repeater.Id == this.Repeater.Id;
|
||||
=> obj is RunningRepeater rr && rr.Repeater.Id == Repeater.Id;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> this.Repeater.Id;
|
||||
}
|
||||
=> Repeater.Id;
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Utility.Common;
|
||||
using NadekoBot.Modules.Utility.Common.Exceptions;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Net;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services;
|
||||
|
||||
@@ -12,16 +12,15 @@ public class StreamRoleService : INService
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ConcurrentDictionary<ulong, StreamRoleSettings> guildSettings;
|
||||
|
||||
|
||||
public StreamRoleService(DiscordSocketClient client, DbService db, Bot bot)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
|
||||
guildSettings = bot.AllGuildConfigs
|
||||
.ToDictionary(x => x.GuildId, x => x.StreamRole)
|
||||
.Where(x => x.Value is { Enabled: true })
|
||||
.ToConcurrent();
|
||||
guildSettings = bot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.StreamRole)
|
||||
.Where(x => x.Value is { Enabled: true })
|
||||
.ToConcurrent();
|
||||
|
||||
_client.GuildMemberUpdated += Client_GuildMemberUpdated;
|
||||
|
||||
@@ -43,23 +42,26 @@ public class StreamRoleService : INService
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
//if user wasn't streaming or didn't have a game status at all
|
||||
if (guildSettings.TryGetValue(after.Guild.Id, out var setting))
|
||||
{
|
||||
await RescanUser(after, setting);
|
||||
}
|
||||
if (guildSettings.TryGetValue(after.Guild.Id, out var setting)) await RescanUser(after, setting);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds or removes a user from a blacklist or a whitelist in the specified guild.
|
||||
/// Adds or removes a user from a blacklist or a whitelist in the specified guild.
|
||||
/// </summary>
|
||||
/// <param name="guild">Guild</param>
|
||||
/// <param name="action">Add or rem action</param>
|
||||
/// <param name="userId">User's Id</param>
|
||||
/// <param name="userName">User's name#discrim</param>
|
||||
/// <returns>Whether the operation was successful</returns>
|
||||
public async Task<bool> ApplyListAction(StreamRoleListType listType, IGuild guild, AddRemove action, ulong userId, string userName)
|
||||
public async Task<bool> ApplyListAction(
|
||||
StreamRoleListType listType,
|
||||
IGuild guild,
|
||||
AddRemove action,
|
||||
ulong userId,
|
||||
string userName)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(userName, nameof(userName));
|
||||
|
||||
@@ -70,11 +72,7 @@ public class StreamRoleService : INService
|
||||
|
||||
if (listType == StreamRoleListType.Whitelist)
|
||||
{
|
||||
var userObj = new StreamRoleWhitelistedUser()
|
||||
{
|
||||
UserId = userId,
|
||||
Username = userName,
|
||||
};
|
||||
var userObj = new StreamRoleWhitelistedUser { UserId = userId, Username = userName };
|
||||
|
||||
if (action == AddRemove.Rem)
|
||||
{
|
||||
@@ -86,15 +84,13 @@ public class StreamRoleService : INService
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success = streamRoleSettings.Whitelist.Add(userObj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var userObj = new StreamRoleBlacklistedUser()
|
||||
{
|
||||
UserId = userId,
|
||||
Username = userName,
|
||||
};
|
||||
var userObj = new StreamRoleBlacklistedUser { UserId = userId, Username = userName };
|
||||
|
||||
if (action == AddRemove.Rem)
|
||||
{
|
||||
@@ -106,21 +102,21 @@ public class StreamRoleService : INService
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success = streamRoleSettings.Blacklist.Add(userObj);
|
||||
}
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
UpdateCache(guild.Id, streamRoleSettings);
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
await RescanUsers(guild);
|
||||
}
|
||||
|
||||
if (success) await RescanUsers(guild);
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets keyword on a guild and updates the cache.
|
||||
/// Sets keyword on a guild and updates the cache.
|
||||
/// </summary>
|
||||
/// <param name="guild">Guild Id</param>
|
||||
/// <param name="keyword">Keyword to set</param>
|
||||
@@ -143,7 +139,7 @@ public class StreamRoleService : INService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently set keyword on a guild.
|
||||
/// Gets the currently set keyword on a guild.
|
||||
/// </summary>
|
||||
/// <param name="guildId">Guild Id</param>
|
||||
/// <returns>The keyword set</returns>
|
||||
@@ -164,8 +160,8 @@ public class StreamRoleService : INService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the role to monitor, and a role to which to add to
|
||||
/// the user who starts streaming in the monitored role.
|
||||
/// Sets the role to monitor, and a role to which to add to
|
||||
/// the user who starts streaming in the monitored role.
|
||||
/// </summary>
|
||||
/// <param name="fromRole">Role to monitor</param>
|
||||
/// <param name="addRole">Role to add to the user</param>
|
||||
@@ -190,14 +186,12 @@ public class StreamRoleService : INService
|
||||
UpdateCache(fromRole.Guild.Id, setting);
|
||||
|
||||
foreach (var usr in await fromRole.GetMembersAsync())
|
||||
{
|
||||
if (usr is { } x)
|
||||
await RescanUser(x, setting, addRole);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the stream role feature on the specified guild.
|
||||
/// Stops the stream role feature on the specified guild.
|
||||
/// </summary>
|
||||
/// <param name="guildId">Guild's Id</param>
|
||||
public async Task StopStreamRole(IGuild guild, bool cleanup = false)
|
||||
@@ -219,13 +213,13 @@ public class StreamRoleService : INService
|
||||
{
|
||||
if (user.IsBot)
|
||||
return;
|
||||
|
||||
var g = (StreamingGame)user.Activities
|
||||
.FirstOrDefault(a => a is StreamingGame &&
|
||||
(string.IsNullOrWhiteSpace(setting.Keyword)
|
||||
|| a.Name.ToUpperInvariant().Contains(setting.Keyword.ToUpperInvariant())
|
||||
|| setting.Whitelist.Any(x => x.UserId == user.Id)));
|
||||
|
||||
|
||||
var g = (StreamingGame)user.Activities.FirstOrDefault(a
|
||||
=> a is StreamingGame
|
||||
&& (string.IsNullOrWhiteSpace(setting.Keyword)
|
||||
|| a.Name.ToUpperInvariant().Contains(setting.Keyword.ToUpperInvariant())
|
||||
|| setting.Whitelist.Any(x => x.UserId == user.Id)));
|
||||
|
||||
if (g is not null
|
||||
&& setting.Enabled
|
||||
&& setting.Blacklist.All(x => x.UserId != user.Id)
|
||||
@@ -245,11 +239,12 @@ public class StreamRoleService : INService
|
||||
if (!user.RoleIds.Contains(addRole.Id))
|
||||
{
|
||||
await user.AddRoleAsync(addRole);
|
||||
Log.Information("Added stream role to user {0} in {1} server", user.ToString(),
|
||||
Log.Information("Added stream role to user {0} in {1} server",
|
||||
user.ToString(),
|
||||
user.Guild.ToString());
|
||||
}
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
await StopStreamRole(user.Guild);
|
||||
Log.Warning(ex, "Error adding stream role(s). Forcibly disabling stream role feature");
|
||||
@@ -264,7 +259,6 @@ public class StreamRoleService : INService
|
||||
{
|
||||
//check if user is in the addrole
|
||||
if (user.RoleIds.Contains(setting.AddRoleId))
|
||||
{
|
||||
try
|
||||
{
|
||||
addRole ??= user.Guild.GetRole(setting.AddRoleId);
|
||||
@@ -272,15 +266,16 @@ public class StreamRoleService : INService
|
||||
throw new StreamRoleNotFoundException();
|
||||
|
||||
await user.RemoveRoleAsync(addRole);
|
||||
Log.Information("Removed stream role from the user {0} in {1} server", user.ToString(), user.Guild.ToString());
|
||||
Log.Information("Removed stream role from the user {0} in {1} server",
|
||||
user.ToString(),
|
||||
user.Guild.ToString());
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
await StopStreamRole(user.Guild);
|
||||
Log.Warning(ex, "Error removing stream role(s). Forcibly disabling stream role feature");
|
||||
throw new StreamRolePermissionException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,14 +291,13 @@ public class StreamRoleService : INService
|
||||
if (setting.Enabled)
|
||||
{
|
||||
var users = await guild.GetUsersAsync(CacheMode.CacheOnly);
|
||||
foreach (var usr in users.Where(x => x.RoleIds.Contains(setting.FromRoleId) || x.RoleIds.Contains(addRole.Id)))
|
||||
{
|
||||
foreach (var usr in users.Where(x
|
||||
=> x.RoleIds.Contains(setting.FromRoleId) || x.RoleIds.Contains(addRole.Id)))
|
||||
if (usr is { } x)
|
||||
await RescanUser(x, setting, addRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCache(ulong guildId, StreamRoleSettings setting)
|
||||
=> guildSettings.AddOrUpdate(guildId, key => setting, (key, old) => setting);
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Help.Services;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Help.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services;
|
||||
|
||||
@@ -11,7 +11,11 @@ public class VerboseErrorsService : INService
|
||||
private readonly CommandHandler _ch;
|
||||
private readonly HelpService _hs;
|
||||
|
||||
public VerboseErrorsService(Bot bot, DbService db, CommandHandler ch, HelpService hs)
|
||||
public VerboseErrorsService(
|
||||
Bot bot,
|
||||
DbService db,
|
||||
CommandHandler ch,
|
||||
HelpService hs)
|
||||
{
|
||||
_db = db;
|
||||
_ch = ch;
|
||||
@@ -19,10 +23,7 @@ public class VerboseErrorsService : INService
|
||||
|
||||
_ch.CommandErrored += LogVerboseError;
|
||||
|
||||
guildsEnabled = new(bot
|
||||
.AllGuildConfigs
|
||||
.Where(x => x.VerboseErrors)
|
||||
.Select(x => x.GuildId));
|
||||
guildsEnabled = new(bot.AllGuildConfigs.Where(x => x.VerboseErrors).Select(x => x.GuildId));
|
||||
}
|
||||
|
||||
private async Task LogVerboseError(CommandInfo cmd, ITextChannel channel, string reason)
|
||||
@@ -33,9 +34,9 @@ public class VerboseErrorsService : INService
|
||||
try
|
||||
{
|
||||
var embed = _hs.GetCommandHelp(cmd, channel.Guild)
|
||||
.WithTitle("Command Error")
|
||||
.WithDescription(reason)
|
||||
.WithErrorColor();
|
||||
.WithTitle("Command Error")
|
||||
.WithDescription(reason)
|
||||
.WithErrorColor();
|
||||
|
||||
await channel.EmbedAsync(embed);
|
||||
}
|
||||
@@ -45,13 +46,14 @@ public class VerboseErrorsService : INService
|
||||
}
|
||||
}
|
||||
|
||||
public bool ToggleVerboseErrors(ulong guildId, bool? enabled=null)
|
||||
public bool ToggleVerboseErrors(ulong guildId, bool? enabled = null)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set);
|
||||
|
||||
if (enabled==null) enabled = gc.VerboseErrors = !gc.VerboseErrors; // Old behaviour, now behind a condition
|
||||
if (enabled == null)
|
||||
enabled = gc.VerboseErrors = !gc.VerboseErrors; // Old behaviour, now behind a condition
|
||||
else gc.VerboseErrors = (bool)enabled; // New behaviour, just set it.
|
||||
|
||||
uow.SaveChanges();
|
||||
@@ -62,6 +64,6 @@ public class VerboseErrorsService : INService
|
||||
else
|
||||
guildsEnabled.TryRemove(guildId);
|
||||
|
||||
return (bool)enabled;
|
||||
return (bool)enabled;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Modules.Utility.Common;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Utility;
|
||||
|
||||
@@ -9,79 +8,91 @@ public partial class Utility
|
||||
{
|
||||
public class StreamRoleCommands : NadekoSubmodule<StreamRoleService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamRole(IRole fromRole, IRole addRole)
|
||||
{
|
||||
await this._service.SetStreamRole(fromRole, addRole);
|
||||
await _service.SetStreamRole(fromRole, addRole);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_enabled(Format.Bold(fromRole.ToString()), Format.Bold(addRole.ToString())));
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_enabled(Format.Bold(fromRole.ToString()),
|
||||
Format.Bold(addRole.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamRole()
|
||||
{
|
||||
await this._service.StopStreamRole(ctx.Guild);
|
||||
await _service.StopStreamRole(ctx.Guild);
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_disabled);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamRoleKeyword([Leftover]string keyword = null)
|
||||
public async Task StreamRoleKeyword([Leftover] string keyword = null)
|
||||
{
|
||||
var kw = await this._service.SetKeyword(ctx.Guild, keyword);
|
||||
|
||||
if(string.IsNullOrWhiteSpace(keyword))
|
||||
var kw = await _service.SetKeyword(ctx.Guild, keyword);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(keyword))
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_kw_reset);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_kw_set(Format.Bold(kw)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamRoleBlacklist(AddRemove action, [Leftover] IGuildUser user)
|
||||
{
|
||||
var success = await this._service.ApplyListAction(StreamRoleListType.Blacklist, ctx.Guild, action, user.Id, user.ToString());
|
||||
var success = await _service.ApplyListAction(StreamRoleListType.Blacklist,
|
||||
ctx.Guild,
|
||||
action,
|
||||
user.Id,
|
||||
user.ToString());
|
||||
|
||||
if(action == AddRemove.Add)
|
||||
if(success)
|
||||
if (action == AddRemove.Add)
|
||||
if (success)
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_bl_add(Format.Bold(user.ToString())));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_bl_add_fail(Format.Bold(user.ToString())));
|
||||
else
|
||||
if (success)
|
||||
else if (success)
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_bl_rem(Format.Bold(user.ToString())));
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.stream_role_bl_rem_fail(Format.Bold(user.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamRoleWhitelist(AddRemove action, [Leftover] IGuildUser user)
|
||||
{
|
||||
var success = await this._service.ApplyListAction(StreamRoleListType.Whitelist, ctx.Guild, action, user.Id, user.ToString());
|
||||
var success = await _service.ApplyListAction(StreamRoleListType.Whitelist,
|
||||
ctx.Guild,
|
||||
action,
|
||||
user.Id,
|
||||
user.ToString());
|
||||
|
||||
if (action == AddRemove.Add)
|
||||
if(success)
|
||||
if (success)
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_wl_add(Format.Bold(user.ToString())));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_wl_add_fail(Format.Bold(user.ToString())));
|
||||
else
|
||||
if (success)
|
||||
else if (success)
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_role_wl_rem(Format.Bold(user.ToString())));
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.stream_role_wl_rem_fail(Format.Bold(user.ToString())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,43 +8,49 @@ public partial class Utility
|
||||
[Group]
|
||||
public class UnitConverterCommands : NadekoSubmodule<ConverterService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task ConvertList()
|
||||
{
|
||||
var units = _service.Units;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.convertlist))
|
||||
.WithOkColor();
|
||||
var embed = _eb.Create().WithTitle(GetText(strs.convertlist)).WithOkColor();
|
||||
|
||||
|
||||
foreach (var g in units.GroupBy(x => x.UnitType))
|
||||
{
|
||||
embed.AddField(g.Key.ToTitleCase(),
|
||||
String.Join(", ", g.Select(x => x.Triggers.FirstOrDefault()).OrderBy(x => x)));
|
||||
}
|
||||
|
||||
string.Join(", ", g.Select(x => x.Triggers.FirstOrDefault()).OrderBy(x => x)));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Convert(string origin, string target, decimal value)
|
||||
{
|
||||
var originUnit = _service.Units.FirstOrDefault(x => x.Triggers.Select(y => y.ToUpperInvariant()).Contains(origin.ToUpperInvariant()));
|
||||
var targetUnit = _service.Units.FirstOrDefault(x => x.Triggers.Select(y => y.ToUpperInvariant()).Contains(target.ToUpperInvariant()));
|
||||
var originUnit = _service.Units.FirstOrDefault(x
|
||||
=> x.Triggers.Select(y => y.ToUpperInvariant()).Contains(origin.ToUpperInvariant()));
|
||||
var targetUnit = _service.Units.FirstOrDefault(x
|
||||
=> x.Triggers.Select(y => y.ToUpperInvariant()).Contains(target.ToUpperInvariant()));
|
||||
if (originUnit is null || targetUnit is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.convert_not_found(Format.Bold(origin), Format.Bold(target)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (originUnit.UnitType != targetUnit.UnitType)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.convert_type_error(Format.Bold(originUnit.Triggers.First()), Format.Bold(targetUnit.Triggers.First())));
|
||||
await ReplyErrorLocalizedAsync(strs.convert_type_error(Format.Bold(originUnit.Triggers.First()),
|
||||
Format.Bold(targetUnit.Triggers.First())));
|
||||
return;
|
||||
}
|
||||
|
||||
decimal res;
|
||||
if (originUnit.Triggers == targetUnit.Triggers) res = value;
|
||||
if (originUnit.Triggers == targetUnit.Triggers)
|
||||
{
|
||||
res = value;
|
||||
}
|
||||
else if (originUnit.UnitType == "temperature")
|
||||
{
|
||||
//don't really care too much about efficiency, so just convert to Kelvin, then to target
|
||||
@@ -60,6 +66,7 @@ public partial class Utility
|
||||
res = value;
|
||||
break;
|
||||
}
|
||||
|
||||
//from Kelvin to target
|
||||
switch (targetUnit.Triggers.First().ToUpperInvariant())
|
||||
{
|
||||
@@ -74,15 +81,17 @@ public partial class Utility
|
||||
else
|
||||
{
|
||||
if (originUnit.UnitType == "currency")
|
||||
{
|
||||
res = value * targetUnit.Modifier / originUnit.Modifier;
|
||||
}
|
||||
else
|
||||
res = value * originUnit.Modifier / targetUnit.Modifier;
|
||||
}
|
||||
|
||||
res = Math.Round(res, 4);
|
||||
|
||||
await SendConfirmAsync(GetText(strs.convert(value, originUnit.Triggers.Last(), res, targetUnit.Triggers.Last())));
|
||||
await SendConfirmAsync(GetText(strs.convert(value,
|
||||
originUnit.Triggers.Last(),
|
||||
res,
|
||||
targetUnit.Triggers.Last())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,6 +10,22 @@ namespace NadekoBot.Modules.Utility;
|
||||
|
||||
public partial class Utility : NadekoModule
|
||||
{
|
||||
public enum CreateInviteType
|
||||
{
|
||||
Any,
|
||||
New
|
||||
}
|
||||
|
||||
public enum MeOrBot { Me, Bot }
|
||||
|
||||
private static readonly JsonSerializerOptions _showEmbedSerializerOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = LowerCaseNamingPolicy.Default
|
||||
};
|
||||
|
||||
private static SemaphoreSlim sem = new(1, 1);
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ICoordinator _coord;
|
||||
private readonly IStatsService _stats;
|
||||
@@ -17,8 +33,12 @@ public partial class Utility : NadekoModule
|
||||
private readonly DownloadTracker _tracker;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public Utility(DiscordSocketClient client, ICoordinator coord,
|
||||
IStatsService stats, IBotCredentials creds, DownloadTracker tracker,
|
||||
public Utility(
|
||||
DiscordSocketClient client,
|
||||
ICoordinator coord,
|
||||
IStatsService stats,
|
||||
IBotCredentials creds,
|
||||
DownloadTracker tracker,
|
||||
IHttpClientFactory httpFactory)
|
||||
{
|
||||
_client = client;
|
||||
@@ -29,29 +49,32 @@ public partial class Utility : NadekoModule
|
||||
_httpFactory = httpFactory;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(1)]
|
||||
public async Task Say(ITextChannel channel, [Leftover] SmartText message)
|
||||
{
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(ctx.User, channel, (SocketGuild)ctx.Guild, (DiscordSocketClient)ctx.Client)
|
||||
.Build();
|
||||
.WithDefault(ctx.User, channel, (SocketGuild)ctx.Guild, (DiscordSocketClient)ctx.Client)
|
||||
.Build();
|
||||
|
||||
message = rep.Replace(message);
|
||||
|
||||
|
||||
await channel.SendAsync(message, !((IGuildUser)ctx.User).GuildPermissions.MentionEveryone);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(0)]
|
||||
public Task Say([Leftover] SmartText message)
|
||||
=> Say((ITextChannel)ctx.Channel, message);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WhosPlaying([Leftover] string game)
|
||||
{
|
||||
@@ -64,32 +87,36 @@ public partial class Utility : NadekoModule
|
||||
Log.Warning("Can't cast guild to socket guild.");
|
||||
return;
|
||||
}
|
||||
|
||||
var rng = new NadekoRandom();
|
||||
var arr = await Task.Run(() => socketGuild.Users
|
||||
.Where(u => u.Activities.FirstOrDefault()?.Name?.ToUpperInvariant() == game)
|
||||
.Select(u => u.Username)
|
||||
.OrderBy(x => rng.Next())
|
||||
.Take(60)
|
||||
.ToArray());
|
||||
.Where(u => u.Activities.FirstOrDefault()?.Name?.ToUpperInvariant()
|
||||
== game)
|
||||
.Select(u => u.Username)
|
||||
.OrderBy(x => rng.Next())
|
||||
.Take(60)
|
||||
.ToArray());
|
||||
|
||||
var i = 0;
|
||||
if (arr.Length == 0)
|
||||
await ReplyErrorLocalizedAsync(strs.nobody_playing_game);
|
||||
else
|
||||
{
|
||||
await SendConfirmAsync("```css\n" + string.Join("\n", arr.GroupBy(item => i++ / 2)
|
||||
.Select(ig => string.Concat(ig.Select(el => $"• {el,-27}")))) + "\n```");
|
||||
}
|
||||
await SendConfirmAsync("```css\n"
|
||||
+ string.Join("\n",
|
||||
arr.GroupBy(item => i++ / 2)
|
||||
.Select(ig => string.Concat(ig.Select(el => $"• {el,-27}"))))
|
||||
+ "\n```");
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task InRole(int page, [Leftover] IRole role = null)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
|
||||
|
||||
@@ -98,75 +125,80 @@ public partial class Utility : NadekoModule
|
||||
CacheMode.CacheOnly
|
||||
#endif
|
||||
);
|
||||
var roleUsers = users
|
||||
.Where(u => role is null ? u.RoleIds.Count == 1 : u.RoleIds.Contains(role.Id))
|
||||
.Select(u => $"`{u.Id, 18}` {u}")
|
||||
.ToArray();
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, cur =>
|
||||
{
|
||||
var pageUsers = roleUsers.Skip(cur * 20)
|
||||
.Take(20)
|
||||
.ToList();
|
||||
var roleUsers = users.Where(u => role is null ? u.RoleIds.Count == 1 : u.RoleIds.Contains(role.Id))
|
||||
.Select(u => $"`{u.Id,18}` {u}")
|
||||
.ToArray();
|
||||
|
||||
if (pageUsers.Count == 0)
|
||||
return _eb.Create().WithOkColor().WithDescription(GetText(strs.no_user_on_this_page));
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
cur =>
|
||||
{
|
||||
var pageUsers = roleUsers.Skip(cur * 20).Take(20).ToList();
|
||||
|
||||
return _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.inrole_list(Format.Bold(role?.Name ?? "No Role"), roleUsers.Length)))
|
||||
.WithDescription(string.Join("\n", pageUsers));
|
||||
}, roleUsers.Length, 20);
|
||||
if (pageUsers.Count == 0)
|
||||
return _eb.Create().WithOkColor().WithDescription(GetText(strs.no_user_on_this_page));
|
||||
|
||||
return _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.inrole_list(Format.Bold(role?.Name ?? "No Role"), roleUsers.Length)))
|
||||
.WithDescription(string.Join("\n", pageUsers));
|
||||
},
|
||||
roleUsers.Length,
|
||||
20);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public Task InRole([Leftover] IRole role = null)
|
||||
=> InRole(1, role);
|
||||
|
||||
public enum MeOrBot { Me, Bot }
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task CheckPerms(MeOrBot who = MeOrBot.Me)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var user = who == MeOrBot.Me
|
||||
? (IGuildUser)ctx.User
|
||||
: ((SocketGuild)ctx.Guild).CurrentUser;
|
||||
var user = who == MeOrBot.Me ? (IGuildUser)ctx.User : ((SocketGuild)ctx.Guild).CurrentUser;
|
||||
var perms = user.GetPermissions((ITextChannel)ctx.Channel);
|
||||
foreach (var p in perms.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any()))
|
||||
{
|
||||
builder.AppendLine($"{p.Name} : {p.GetValue(perms, null)}");
|
||||
}
|
||||
await SendConfirmAsync(builder.ToString());
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task UserId([Leftover] IGuildUser target = null)
|
||||
{
|
||||
var usr = target ?? ctx.User;
|
||||
await ReplyConfirmLocalizedAsync(strs.userid("🆔", Format.Bold(usr.ToString()),
|
||||
await ReplyConfirmLocalizedAsync(strs.userid("🆔",
|
||||
Format.Bold(usr.ToString()),
|
||||
Format.Code(usr.Id.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RoleId([Leftover] IRole role)
|
||||
=> await ReplyConfirmLocalizedAsync(strs.roleid("🆔", Format.Bold(role.ToString()),
|
||||
=> await ReplyConfirmLocalizedAsync(strs.roleid("🆔",
|
||||
Format.Bold(role.ToString()),
|
||||
Format.Code(role.Id.ToString())));
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task ChannelId()
|
||||
=> await ReplyConfirmLocalizedAsync(strs.channelid("🆔", Format.Code(ctx.Channel.Id.ToString())));
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ServerId()
|
||||
=> await ReplyConfirmLocalizedAsync(strs.serverid("🆔", Format.Code(ctx.Guild.Id.ToString())));
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Roles(IGuildUser target, int page = 1)
|
||||
{
|
||||
@@ -179,41 +211,43 @@ public partial class Utility : NadekoModule
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
var roles = target.GetRoles().Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * rolesPerPage).Take(rolesPerPage).ToArray();
|
||||
var roles = target.GetRoles()
|
||||
.Except(new[] { guild.EveryoneRole })
|
||||
.OrderBy(r => -r.Position)
|
||||
.Skip((page - 1) * rolesPerPage)
|
||||
.Take(rolesPerPage)
|
||||
.ToArray();
|
||||
if (!roles.Any())
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_roles_on_page);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
await SendConfirmAsync(GetText(strs.roles_page(page, Format.Bold(target.ToString()))),
|
||||
"\n• " + string.Join("\n• ", (IEnumerable<IRole>)roles).SanitizeMentions(true));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var roles = guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * rolesPerPage).Take(rolesPerPage).ToArray();
|
||||
var roles = guild.Roles.Except(new[] { guild.EveryoneRole })
|
||||
.OrderBy(r => -r.Position)
|
||||
.Skip((page - 1) * rolesPerPage)
|
||||
.Take(rolesPerPage)
|
||||
.ToArray();
|
||||
if (!roles.Any())
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_roles_on_page);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SendConfirmAsync(GetText(strs.roles_all_page(page)),
|
||||
"\n• " + string.Join("\n• ", (IEnumerable<IRole>)roles).SanitizeMentions(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Roles(int page = 1) =>
|
||||
Roles(null, page);
|
||||
public Task Roles(int page = 1)
|
||||
=> Roles(null, page);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ChannelTopic([Leftover]ITextChannel channel = null)
|
||||
public async Task ChannelTopic([Leftover] ITextChannel channel = null)
|
||||
{
|
||||
if (channel is null)
|
||||
channel = (ITextChannel)ctx.Channel;
|
||||
@@ -225,37 +259,44 @@ public partial class Utility : NadekoModule
|
||||
await SendConfirmAsync(GetText(strs.channel_topic), topic);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Stats()
|
||||
{
|
||||
var ownerIds = string.Join("\n", _creds.OwnerIds);
|
||||
if (string.IsNullOrWhiteSpace(ownerIds))
|
||||
ownerIds = "-";
|
||||
|
||||
await ctx.Channel.EmbedAsync(
|
||||
_eb.Create().WithOkColor()
|
||||
.WithAuthor($"NadekoBot v{StatsService.BotVersion}",
|
||||
"https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/avatar.png",
|
||||
"https://nadekobot.readthedocs.io/en/latest/")
|
||||
.AddField(GetText(strs.author), _stats.Author, true)
|
||||
.AddField(GetText(strs.botid), _client.CurrentUser.Id.ToString(), true)
|
||||
.AddField(GetText(strs.shard), $"#{_client.ShardId} / {_creds.TotalShards}", true)
|
||||
.AddField(GetText(strs.commands_ran), _stats.CommandsRan.ToString(), true)
|
||||
.AddField(GetText(strs.messages), $"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)",
|
||||
true)
|
||||
.AddField(GetText(strs.memory), FormattableString.Invariant($"{_stats.GetPrivateMemory():F2} MB"), true)
|
||||
.AddField(GetText(strs.owner_ids), ownerIds, true)
|
||||
.AddField(GetText(strs.uptime), _stats.GetUptimeString("\n"), true)
|
||||
.AddField(GetText(strs.presence),
|
||||
GetText(strs.presence_txt(
|
||||
_coord.GetGuildCount(),
|
||||
_stats.TextChannels,
|
||||
_stats.VoiceChannels)),
|
||||
true));
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithAuthor($"NadekoBot v{StatsService.BotVersion}",
|
||||
"https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/avatar.png",
|
||||
"https://nadekobot.readthedocs.io/en/latest/")
|
||||
.AddField(GetText(strs.author), _stats.Author, true)
|
||||
.AddField(GetText(strs.botid), _client.CurrentUser.Id.ToString(), true)
|
||||
.AddField(GetText(strs.shard),
|
||||
$"#{_client.ShardId} / {_creds.TotalShards}",
|
||||
true)
|
||||
.AddField(GetText(strs.commands_ran), _stats.CommandsRan.ToString(), true)
|
||||
.AddField(GetText(strs.messages),
|
||||
$"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)",
|
||||
true)
|
||||
.AddField(GetText(strs.memory),
|
||||
FormattableString.Invariant($"{_stats.GetPrivateMemory():F2} MB"),
|
||||
true)
|
||||
.AddField(GetText(strs.owner_ids), ownerIds, true)
|
||||
.AddField(GetText(strs.uptime), _stats.GetUptimeString("\n"), true)
|
||||
.AddField(GetText(strs.presence),
|
||||
GetText(strs.presence_txt(_coord.GetGuildCount(),
|
||||
_stats.TextChannels,
|
||||
_stats.VoiceChannels)),
|
||||
true));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public async Task Showemojis([Leftover] string _) // need to have the parameter so that the message.tags gets populated
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task
|
||||
Showemojis([Leftover] string _) // need to have the parameter so that the message.tags gets populated
|
||||
{
|
||||
var tags = ctx.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emote)t.Value);
|
||||
|
||||
@@ -267,23 +308,26 @@ public partial class Utility : NadekoModule
|
||||
await ctx.Channel.SendMessageAsync(result.TrimTo(2000));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageEmojisAndStickers)]
|
||||
[UserPerm(GuildPerm.ManageEmojisAndStickers)]
|
||||
[Priority(2)]
|
||||
public Task EmojiAdd(string name, Emote emote)
|
||||
=> EmojiAdd(name, emote.Url);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageEmojisAndStickers)]
|
||||
[UserPerm(GuildPerm.ManageEmojisAndStickers)]
|
||||
[Priority(1)]
|
||||
public Task EmojiAdd(Emote emote)
|
||||
=> EmojiAdd(emote.Name, emote.Url);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[BotPerm(GuildPerm.ManageEmojisAndStickers)]
|
||||
[UserPerm(GuildPerm.ManageEmojisAndStickers)]
|
||||
@@ -296,7 +340,7 @@ public partial class Utility : NadekoModule
|
||||
|
||||
if (url is null)
|
||||
return;
|
||||
|
||||
|
||||
using var http = _httpFactory.CreateClient();
|
||||
var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||
if (!res.IsImage() || res.GetImageSize() is null or > 262_144)
|
||||
@@ -318,10 +362,12 @@ public partial class Utility : NadekoModule
|
||||
await ReplyErrorLocalizedAsync(strs.emoji_add_error);
|
||||
return;
|
||||
}
|
||||
|
||||
await ConfirmLocalizedAsync(strs.emoji_added(em.ToString()));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task ListServers(int page = 1)
|
||||
{
|
||||
@@ -338,29 +384,21 @@ public partial class Utility : NadekoModule
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor();
|
||||
var embed = _eb.Create().WithOkColor();
|
||||
foreach (var guild in guilds)
|
||||
embed.AddField(guild.Name,
|
||||
GetText(strs.listservers(guild.Id, guild.MemberCount, guild.OwnerId)),
|
||||
false);
|
||||
embed.AddField(guild.Name, GetText(strs.listservers(guild.Id, guild.MemberCount, guild.OwnerId)));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task ShowEmbed(ulong messageId)
|
||||
=> ShowEmbed((ITextChannel)ctx.Channel, messageId);
|
||||
|
||||
private static readonly JsonSerializerOptions _showEmbedSerializerOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = LowerCaseNamingPolicy.Default
|
||||
};
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ShowEmbed(ITextChannel ch, ulong messageId)
|
||||
{
|
||||
@@ -390,7 +428,8 @@ public partial class Utility : NadekoModule
|
||||
await SendConfirmAsync(Format.Sanitize(json).Replace("](", "]\\("));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task SaveChat(int cnt)
|
||||
@@ -400,36 +439,38 @@ public partial class Utility : NadekoModule
|
||||
|
||||
var title = $"Chatlog-{ctx.Guild.Name}/#{ctx.Channel.Name}-{DateTime.Now}.txt";
|
||||
var grouping = msgs.GroupBy(x => $"{x.CreatedAt.Date:dd.MM.yyyy}")
|
||||
.Select(g => new
|
||||
{
|
||||
date = g.Key,
|
||||
messages = g.OrderBy(x => x.CreatedAt).Select(s =>
|
||||
{
|
||||
var msg = $"【{s.Timestamp:HH:mm:ss}】{s.Author}:";
|
||||
if (string.IsNullOrWhiteSpace(s.ToString()))
|
||||
{
|
||||
if (s.Attachments.Any())
|
||||
{
|
||||
msg += "FILES_UPLOADED: " + string.Join("\n", s.Attachments.Select(x => x.Url));
|
||||
}
|
||||
else if (s.Embeds.Any())
|
||||
{
|
||||
msg += "EMBEDS: " + string.Join("\n--------\n", s.Embeds.Select(x => $"Description: {x.Description}"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg += s.ToString();
|
||||
}
|
||||
return msg;
|
||||
})
|
||||
});
|
||||
await using var stream = await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream();
|
||||
await ctx.User.SendFileAsync(stream, title, title, false);
|
||||
}
|
||||
private static SemaphoreSlim sem = new(1, 1);
|
||||
.Select(g => new
|
||||
{
|
||||
date = g.Key,
|
||||
messages = g.OrderBy(x => x.CreatedAt)
|
||||
.Select(s =>
|
||||
{
|
||||
var msg = $"【{s.Timestamp:HH:mm:ss}】{s.Author}:";
|
||||
if (string.IsNullOrWhiteSpace(s.ToString()))
|
||||
{
|
||||
if (s.Attachments.Any())
|
||||
msg += "FILES_UPLOADED: "
|
||||
+ string.Join("\n", s.Attachments.Select(x => x.Url));
|
||||
else if (s.Embeds.Any())
|
||||
msg += "EMBEDS: "
|
||||
+ string.Join("\n--------\n",
|
||||
s.Embeds.Select(x
|
||||
=> $"Description: {x.Description}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
msg += s.ToString();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
return msg;
|
||||
})
|
||||
});
|
||||
await using var stream = await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream();
|
||||
await ctx.User.SendFileAsync(stream, title, title);
|
||||
}
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
#if GLOBAL_NADEKO
|
||||
[Ratelimit(30)]
|
||||
#endif
|
||||
@@ -451,14 +492,7 @@ public partial class Utility : NadekoModule
|
||||
}
|
||||
}
|
||||
|
||||
public enum CreateInviteType
|
||||
{
|
||||
Any,
|
||||
New
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// [NadekoCommand, Usage, Description, Aliases]
|
||||
// [RequireContext(ContextType.Guild)]
|
||||
// public async Task CreateMyInvite(CreateInviteType type = CreateInviteType.Any)
|
||||
@@ -497,4 +531,4 @@ public partial class Utility : NadekoModule
|
||||
// return embed;
|
||||
// }, inviteUsers.Count, 9);
|
||||
// }
|
||||
}
|
||||
}
|
@@ -8,7 +8,8 @@ public partial class Utility
|
||||
[Group]
|
||||
public class VerboseErrorCommands : NadekoSubmodule<VerboseErrorsService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task VerboseError(bool? newstate = null)
|
||||
@@ -21,4 +22,4 @@ public partial class Utility
|
||||
await ReplyConfirmLocalizedAsync(strs.verbose_errors_disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user