mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2026-03-03 14:49:38 -05:00
Applied codestyle to all .cs files
This commit is contained in:
@@ -6,10 +6,13 @@ namespace NadekoBot.Modules.Administration;
|
||||
|
||||
public partial class Administration : NadekoModule<AdministrationService>
|
||||
{
|
||||
private readonly ImageOnlyChannelService _imageOnly;
|
||||
|
||||
public Administration(ImageOnlyChannelService imageOnly)
|
||||
=> _imageOnly = imageOnly;
|
||||
public enum Channel
|
||||
{
|
||||
Channel,
|
||||
Ch,
|
||||
Chnl,
|
||||
Chan
|
||||
}
|
||||
|
||||
public enum List
|
||||
{
|
||||
@@ -17,7 +20,25 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
Ls = 0
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public enum Server
|
||||
{
|
||||
Server
|
||||
}
|
||||
|
||||
public enum State
|
||||
{
|
||||
Enable,
|
||||
Disable,
|
||||
Inherit
|
||||
}
|
||||
|
||||
private readonly ImageOnlyChannelService _imageOnly;
|
||||
|
||||
public Administration(ImageOnlyChannelService imageOnly)
|
||||
=> _imageOnly = imageOnly;
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.Administrator)]
|
||||
@@ -30,7 +51,8 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await ReplyPendingLocalizedAsync(strs.imageonly_disable);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageChannels)]
|
||||
[BotPerm(ChannelPerm.ManageChannels)]
|
||||
@@ -39,9 +61,9 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
var seconds = (int?)time?.Time.TotalSeconds ?? 0;
|
||||
if (time is not null && (time.Time < TimeSpan.FromSeconds(0) || time.Time > TimeSpan.FromHours(6)))
|
||||
return;
|
||||
|
||||
|
||||
await ((ITextChannel) ctx.Channel).ModifyAsync(tcp =>
|
||||
|
||||
await ((ITextChannel)ctx.Channel).ModifyAsync(tcp =>
|
||||
{
|
||||
tcp.SlowModeInterval = seconds;
|
||||
});
|
||||
@@ -49,26 +71,26 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(2)]
|
||||
public async Task Delmsgoncmd(List _)
|
||||
{
|
||||
var guild = (SocketGuild) ctx.Guild;
|
||||
var guild = (SocketGuild)ctx.Guild;
|
||||
var (enabled, channels) = _service.GetDelMsgOnCmdData(ctx.Guild.Id);
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.server_delmsgoncmd))
|
||||
.WithDescription(enabled ? "✅" : "❌");
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.server_delmsgoncmd))
|
||||
.WithDescription(enabled ? "✅" : "❌");
|
||||
|
||||
var str = string.Join("\n", channels
|
||||
.Select(x =>
|
||||
var str = string.Join("\n",
|
||||
channels.Select(x =>
|
||||
{
|
||||
var ch = guild.GetChannel(x.ChannelId)?.ToString()
|
||||
?? x.ChannelId.ToString();
|
||||
var ch = guild.GetChannel(x.ChannelId)?.ToString() ?? x.ChannelId.ToString();
|
||||
var prefix = x.State ? "✅ " : "❌ ";
|
||||
return prefix + ch;
|
||||
}));
|
||||
@@ -81,12 +103,8 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
public enum Server
|
||||
{
|
||||
Server
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageMessages)]
|
||||
@@ -105,22 +123,8 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
}
|
||||
}
|
||||
|
||||
public enum Channel
|
||||
{
|
||||
Channel,
|
||||
Ch,
|
||||
Chnl,
|
||||
Chan
|
||||
}
|
||||
|
||||
public enum State
|
||||
{
|
||||
Enable,
|
||||
Disable,
|
||||
Inherit
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageMessages)]
|
||||
@@ -128,7 +132,8 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
public Task Delmsgoncmd(Channel _, State s, ITextChannel ch)
|
||||
=> Delmsgoncmd(_, s, ch.Id);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageMessages)]
|
||||
@@ -139,20 +144,15 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await _service.SetDelMsgOnCmdState(ctx.Guild.Id, actualChId, s);
|
||||
|
||||
if (s == State.Disable)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.delmsg_channel_off);
|
||||
}
|
||||
else if (s == State.Enable)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.delmsg_channel_on);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.delmsg_channel_inherit);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.DeafenMembers)]
|
||||
[BotPerm(GuildPerm.DeafenMembers)]
|
||||
@@ -162,7 +162,8 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await ReplyConfirmLocalizedAsync(strs.deafen);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.DeafenMembers)]
|
||||
[BotPerm(GuildPerm.DeafenMembers)]
|
||||
@@ -172,7 +173,8 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await ReplyConfirmLocalizedAsync(strs.undeafen);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageChannels)]
|
||||
[BotPerm(GuildPerm.ManageChannels)]
|
||||
@@ -182,7 +184,8 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await ReplyConfirmLocalizedAsync(strs.delvoich(Format.Bold(voiceChannel.Name)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageChannels)]
|
||||
[BotPerm(GuildPerm.ManageChannels)]
|
||||
@@ -192,7 +195,8 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await ReplyConfirmLocalizedAsync(strs.createvoich(Format.Bold(ch.Name)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageChannels)]
|
||||
[BotPerm(GuildPerm.ManageChannels)]
|
||||
@@ -202,7 +206,8 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await ReplyConfirmLocalizedAsync(strs.deltextchan(Format.Bold(toDelete.Name)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageChannels)]
|
||||
[BotPerm(GuildPerm.ManageChannels)]
|
||||
@@ -212,36 +217,39 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await ReplyConfirmLocalizedAsync(strs.createtextchan(Format.Bold(txtCh.Name)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageChannels)]
|
||||
[BotPerm(GuildPerm.ManageChannels)]
|
||||
public async Task SetTopic([Leftover] string topic = null)
|
||||
{
|
||||
var channel = (ITextChannel) ctx.Channel;
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
topic ??= "";
|
||||
await channel.ModifyAsync(c => c.Topic = topic);
|
||||
await ReplyConfirmLocalizedAsync(strs.set_topic);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageChannels)]
|
||||
[BotPerm(GuildPerm.ManageChannels)]
|
||||
public async Task SetChanlName([Leftover] string name)
|
||||
{
|
||||
var channel = (ITextChannel) ctx.Channel;
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
await channel.ModifyAsync(c => c.Name = name);
|
||||
await ReplyConfirmLocalizedAsync(strs.set_channel_name);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageChannels)]
|
||||
[BotPerm(GuildPerm.ManageChannels)]
|
||||
public async Task NsfwToggle()
|
||||
{
|
||||
var channel = (ITextChannel) ctx.Channel;
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
var isEnabled = channel.IsNsfw;
|
||||
|
||||
await channel.ModifyAsync(c => c.IsNsfw = !isEnabled);
|
||||
@@ -252,20 +260,22 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await ReplyConfirmLocalizedAsync(strs.nsfw_set_true);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
[Priority(0)]
|
||||
public Task Edit(ulong messageId, [Leftover] string text)
|
||||
=> Edit((ITextChannel) ctx.Channel, messageId, text);
|
||||
=> Edit((ITextChannel)ctx.Channel, messageId, text);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task Edit(ITextChannel channel, ulong messageId, [Leftover] string text)
|
||||
{
|
||||
var userPerms = ((SocketGuildUser) ctx.User).GetPermissions(channel);
|
||||
var botPerms = ((SocketGuild) ctx.Guild).CurrentUser.GetPermissions(channel);
|
||||
var userPerms = ((SocketGuildUser)ctx.User).GetPermissions(channel);
|
||||
var botPerms = ((SocketGuild)ctx.Guild).CurrentUser.GetPermissions(channel);
|
||||
if (!userPerms.Has(ChannelPermission.ManageMessages))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.insuf_perms_u);
|
||||
@@ -281,23 +291,28 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await _service.EditMessage(ctx, channel, messageId, text);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
public Task Delete(ulong messageId, StoopidTime time = null)
|
||||
=> Delete((ITextChannel) ctx.Channel, messageId, time);
|
||||
=> Delete((ITextChannel)ctx.Channel, messageId, time);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Delete(ITextChannel channel, ulong messageId, StoopidTime time = null)
|
||||
=> await InternalMessageAction(channel, messageId, time, msg => msg.DeleteAsync());
|
||||
|
||||
private async Task InternalMessageAction(ITextChannel channel, ulong messageId, StoopidTime time,
|
||||
private async Task InternalMessageAction(
|
||||
ITextChannel channel,
|
||||
ulong messageId,
|
||||
StoopidTime time,
|
||||
Func<IMessage, Task> func)
|
||||
{
|
||||
var userPerms = ((SocketGuildUser) ctx.User).GetPermissions(channel);
|
||||
var botPerms = ((SocketGuild) ctx.Guild).CurrentUser.GetPermissions(channel);
|
||||
var userPerms = ((SocketGuildUser)ctx.User).GetPermissions(channel);
|
||||
var botPerms = ((SocketGuild)ctx.Guild).CurrentUser.GetPermissions(channel);
|
||||
if (!userPerms.Has(ChannelPermission.ManageMessages))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.insuf_perms_u);
|
||||
@@ -338,4 +353,4 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,14 @@ public partial class Administration
|
||||
[Group]
|
||||
public class AutoAssignRoleCommands : NadekoSubmodule<AutoAssignRoleService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task AutoAssignRole([Leftover] IRole role)
|
||||
{
|
||||
var guser = (IGuildUser) ctx.User;
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
if (role.Id == ctx.Guild.EveryoneRole.Id)
|
||||
return;
|
||||
|
||||
@@ -27,20 +28,15 @@ public partial class Administration
|
||||
|
||||
var roles = await _service.ToggleAarAsync(ctx.Guild.Id, role.Id);
|
||||
if (roles.Count == 0)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.aar_disabled);
|
||||
}
|
||||
else if (roles.Contains(role.Id))
|
||||
{
|
||||
await AutoAssignRole();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.aar_role_removed(Format.Bold(role.ToString())));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
@@ -51,18 +47,14 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.aar_none);
|
||||
return;
|
||||
}
|
||||
|
||||
var existing = roles.Select(rid => ctx.Guild.GetRole(rid)).Where(r => r is not null)
|
||||
.ToList();
|
||||
|
||||
var existing = roles.Select(rid => ctx.Guild.GetRole(rid)).Where(r => r is not null).ToList();
|
||||
|
||||
if (existing.Count != roles.Count)
|
||||
{
|
||||
await _service.SetAarRolesAsync(ctx.Guild.Id, existing.Select(x => x.Id));
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.aar_roles(
|
||||
'\n' + existing.Select(x => Format.Bold(x.ToString()))
|
||||
.Join(",\n")));
|
||||
'\n' + existing.Select(x => Format.Bold(x.ToString())).Join(",\n")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,4 @@ public enum LogType
|
||||
VoicePresence,
|
||||
VoicePresenceTTS,
|
||||
UserMuted
|
||||
}
|
||||
}
|
||||
@@ -25,18 +25,28 @@ public class AntiSpamStats
|
||||
|
||||
public class AntiAltStats
|
||||
{
|
||||
public PunishmentAction Action
|
||||
=> _setting.Action;
|
||||
|
||||
public int ActionDurationMinutes
|
||||
=> _setting.ActionDurationMinutes;
|
||||
|
||||
public ulong? RoleId
|
||||
=> _setting.RoleId;
|
||||
|
||||
public TimeSpan MinAge
|
||||
=> _setting.MinAge;
|
||||
|
||||
public int Counter
|
||||
=> _counter;
|
||||
|
||||
private readonly AntiAltSetting _setting;
|
||||
public PunishmentAction Action => _setting.Action;
|
||||
public int ActionDurationMinutes => _setting.ActionDurationMinutes;
|
||||
public ulong? RoleId => _setting.RoleId;
|
||||
public TimeSpan MinAge => _setting.MinAge;
|
||||
|
||||
private int _counter = 0;
|
||||
public int Counter => _counter;
|
||||
|
||||
private int _counter;
|
||||
|
||||
public AntiAltStats(AntiAltSetting setting)
|
||||
=> _setting = setting;
|
||||
|
||||
public void Increment() => Interlocked.Increment(ref _counter);
|
||||
|
||||
}
|
||||
public void Increment()
|
||||
=> Interlocked.Increment(ref _counter);
|
||||
}
|
||||
@@ -10,4 +10,4 @@ public class PunishQueueItem
|
||||
public int MuteTime { get; set; }
|
||||
public ulong? RoleId { get; set; }
|
||||
public IGuildUser User { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,15 @@ namespace NadekoBot.Modules.Administration.Common;
|
||||
|
||||
public sealed class UserSpamStats : IDisposable
|
||||
{
|
||||
public int Count => timers.Count;
|
||||
public int Count
|
||||
=> timers.Count;
|
||||
|
||||
public string LastMessage { get; set; }
|
||||
|
||||
private ConcurrentQueue<Timer> timers { get; }
|
||||
|
||||
private readonly object applyLock = new();
|
||||
|
||||
public UserSpamStats(IUserMessage msg)
|
||||
{
|
||||
LastMessage = msg.Content.ToUpperInvariant();
|
||||
@@ -16,7 +20,6 @@ public sealed class UserSpamStats : IDisposable
|
||||
ApplyNextMessage(msg);
|
||||
}
|
||||
|
||||
private readonly object applyLock = new();
|
||||
public void ApplyNextMessage(IUserMessage message)
|
||||
{
|
||||
lock (applyLock)
|
||||
@@ -28,10 +31,15 @@ public sealed class UserSpamStats : IDisposable
|
||||
while (timers.TryDequeue(out var old))
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
var t = new Timer(_ => {
|
||||
if (timers.TryDequeue(out var old))
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(30));
|
||||
|
||||
var t = new Timer(_ =>
|
||||
{
|
||||
if (timers.TryDequeue(out var old))
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromMinutes(30),
|
||||
TimeSpan.FromMinutes(30));
|
||||
timers.Enqueue(t);
|
||||
}
|
||||
}
|
||||
@@ -41,4 +49,4 @@ public sealed class UserSpamStats : IDisposable
|
||||
while (timers.TryDequeue(out var old))
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,20 +10,17 @@ namespace NadekoBot.Modules.Administration
|
||||
[OwnerOnly]
|
||||
public class DangerousCommands : NadekoSubmodule<DangerousCommandsService>
|
||||
{
|
||||
|
||||
private async Task InternalExecSql(string sql, params object[] reps)
|
||||
{
|
||||
sql = string.Format(sql, reps);
|
||||
try
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.sql_confirm_exec))
|
||||
.WithDescription(Format.Code(sql));
|
||||
.WithTitle(GetText(strs.sql_confirm_exec))
|
||||
.WithDescription(Format.Code(sql));
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var res = await _service.ExecuteSql(sql);
|
||||
await SendConfirmAsync(res.ToString());
|
||||
@@ -34,87 +31,91 @@ namespace NadekoBot.Modules.Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public Task SqlSelect([Leftover]string sql)
|
||||
public Task SqlSelect([Leftover] string sql)
|
||||
{
|
||||
var result = _service.SelectSql(sql);
|
||||
|
||||
return ctx.SendPaginatedConfirmAsync(0, cur =>
|
||||
{
|
||||
var items = result.Results.Skip(cur * 20).Take(20);
|
||||
|
||||
if (!items.Any())
|
||||
return ctx.SendPaginatedConfirmAsync(0,
|
||||
cur =>
|
||||
{
|
||||
var items = result.Results.Skip(cur * 20).Take(20).ToList();
|
||||
|
||||
if (!items.Any())
|
||||
return _eb.Create().WithErrorColor().WithFooter(sql).WithDescription("-");
|
||||
|
||||
return _eb.Create()
|
||||
.WithErrorColor()
|
||||
.WithFooter(sql)
|
||||
.WithDescription("-");
|
||||
}
|
||||
|
||||
return _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithFooter(sql)
|
||||
.WithTitle(string.Join(" ║ ", result.ColumnNames))
|
||||
.WithDescription(string.Join('\n', items.Select(x => string.Join(" ║ ", x))));
|
||||
|
||||
}, result.Results.Count, 20);
|
||||
.WithOkColor()
|
||||
.WithFooter(sql)
|
||||
.WithTitle(string.Join(" ║ ", result.ColumnNames))
|
||||
.WithDescription(string.Join('\n', items.Select(x => string.Join(" ║ ", x))));
|
||||
},
|
||||
result.Results.Count,
|
||||
20);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public Task SqlExec([Leftover]string sql) =>
|
||||
InternalExecSql(sql);
|
||||
public Task SqlExec([Leftover] string sql)
|
||||
=> InternalExecSql(sql);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public Task DeleteWaifus() =>
|
||||
SqlExec(DangerousCommandsService.WaifusDeleteSql);
|
||||
public Task DeleteWaifus()
|
||||
=> SqlExec(DangerousCommandsService.WaifusDeleteSql);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public Task DeleteWaifu(IUser user) =>
|
||||
DeleteWaifu(user.Id);
|
||||
public Task DeleteWaifu(IUser user)
|
||||
=> DeleteWaifu(user.Id);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public Task DeleteWaifu(ulong userId) =>
|
||||
InternalExecSql(DangerousCommandsService.WaifuDeleteSql, userId);
|
||||
public Task DeleteWaifu(ulong userId)
|
||||
=> InternalExecSql(DangerousCommandsService.WaifuDeleteSql, userId);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public Task DeleteCurrency() =>
|
||||
SqlExec(DangerousCommandsService.CurrencyDeleteSql);
|
||||
public Task DeleteCurrency()
|
||||
=> SqlExec(DangerousCommandsService.CurrencyDeleteSql);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public Task DeletePlaylists() =>
|
||||
SqlExec(DangerousCommandsService.MusicPlaylistDeleteSql);
|
||||
public Task DeletePlaylists()
|
||||
=> SqlExec(DangerousCommandsService.MusicPlaylistDeleteSql);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public Task DeleteXp() =>
|
||||
SqlExec(DangerousCommandsService.XpDeleteSql);
|
||||
public Task DeleteXp()
|
||||
=> SqlExec(DangerousCommandsService.XpDeleteSql);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task PurgeUser(ulong userId)
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithDescription(GetText(strs.purge_user_confirm(Format.Bold(userId.ToString()))));
|
||||
.WithDescription(GetText(strs.purge_user_confirm(Format.Bold(userId.ToString()))));
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed)) return;
|
||||
|
||||
if (!await PromptUserConfirmAsync(embed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _service.PurgeUserAsync(userId);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public Task PurgeUser([Leftover]IUser user)
|
||||
public Task PurgeUser([Leftover] IUser user)
|
||||
=> PurgeUser(user.Id);
|
||||
//[NadekoCommand, Usage, Description, Aliases]
|
||||
//[OwnerOnly]
|
||||
@@ -123,4 +124,4 @@ namespace NadekoBot.Modules.Administration
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -11,7 +11,8 @@ public partial class Administration
|
||||
{
|
||||
// override stats, it should require that the user has managessages guild permission
|
||||
// .po 'stats' add user guild managemessages
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task DiscordPermOverride(CommandOrCrInfo cmd, params GuildPerm[] perms)
|
||||
@@ -26,20 +27,20 @@ public partial class Administration
|
||||
var aggregatePerms = perms.Aggregate((acc, seed) => seed | acc);
|
||||
await _service.AddOverride(ctx.Guild.Id, cmd.Name, aggregatePerms);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.perm_override(
|
||||
Format.Bold(aggregatePerms.ToString()),
|
||||
await ReplyConfirmLocalizedAsync(strs.perm_override(Format.Bold(aggregatePerms.ToString()),
|
||||
Format.Code(cmd.Name)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task DiscordPermOverrideReset()
|
||||
{
|
||||
var result = await PromptUserConfirmAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(strs.perm_override_all_confirm)));
|
||||
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(strs.perm_override_all_confirm)));
|
||||
|
||||
if (!result)
|
||||
return;
|
||||
await _service.ClearAllOverrides(ctx.Guild.Id);
|
||||
@@ -47,35 +48,34 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.perm_override_all);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task DiscordPermOverrideList(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
|
||||
var overrides = await _service.GetAllOverrides(ctx.Guild.Id);
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
var eb = _eb.Create()
|
||||
.WithTitle(GetText(strs.perm_overrides))
|
||||
.WithOkColor();
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
var eb = _eb.Create().WithTitle(GetText(strs.perm_overrides)).WithOkColor();
|
||||
|
||||
var thisPageOverrides = overrides
|
||||
.Skip(9 * curPage)
|
||||
.Take(9)
|
||||
.ToList();
|
||||
var thisPageOverrides = overrides.Skip(9 * curPage).Take(9).ToList();
|
||||
|
||||
if (thisPageOverrides.Count == 0)
|
||||
eb.WithDescription(GetText(strs.perm_override_page_none));
|
||||
else
|
||||
eb.WithDescription(string.Join("\n",
|
||||
thisPageOverrides.Select(ov => $"{ov.Command} => {ov.Perm.ToString()}")));
|
||||
if (thisPageOverrides.Count == 0)
|
||||
eb.WithDescription(GetText(strs.perm_override_page_none));
|
||||
else
|
||||
eb.WithDescription(string.Join("\n",
|
||||
thisPageOverrides.Select(ov => $"{ov.Command} => {ov.Perm.ToString()}")));
|
||||
|
||||
return eb;
|
||||
}, overrides.Count, 9, true);
|
||||
return eb;
|
||||
},
|
||||
overrides.Count,
|
||||
9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@ public partial class Administration
|
||||
[Group]
|
||||
public class GameChannelCommands : NadekoSubmodule<GameVoiceChannelService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.MoveMembers)]
|
||||
@@ -21,6 +22,7 @@ public partial class Administration
|
||||
await ReplyErrorLocalizedAsync(strs.not_in_voice);
|
||||
return;
|
||||
}
|
||||
|
||||
var id = _service.ToggleGameVoiceChannel(ctx.Guild.Id, vch.Id);
|
||||
|
||||
if (id is null)
|
||||
@@ -34,4 +36,4 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public class GreetGrouper<T>
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a group, if group already exists, adds the specified user
|
||||
/// Creates a group, if group already exists, adds the specified user
|
||||
/// </summary>
|
||||
/// <param name="guildId">Id of the server for which to create group for</param>
|
||||
/// <param name="toAddIfExists">User to add if group already exists</param>
|
||||
@@ -31,7 +31,7 @@ public class GreetGrouper<T>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the specified amount of items from the group. If all items are removed, group will be removed.
|
||||
/// Remove the specified amount of items from the group. If all items are removed, group will be removed.
|
||||
/// </summary>
|
||||
/// <param name="guildId">Id of the group</param>
|
||||
/// <param name="count">Maximum number of items to retrieve</param>
|
||||
@@ -54,8 +54,7 @@ public class GreetGrouper<T>
|
||||
// if there are more in the group than what's needed
|
||||
// take the requested number, remove them from the set
|
||||
// and return them
|
||||
var toReturn = set.TakeWhile(_ => count-- != 0)
|
||||
.ToList();
|
||||
var toReturn = set.TakeWhile(_ => count-- != 0).ToList();
|
||||
foreach (var item in toReturn)
|
||||
set.Remove(item);
|
||||
|
||||
@@ -69,4 +68,4 @@ public class GreetGrouper<T>
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
@@ -43,4 +42,4 @@ public class GreetSettings
|
||||
BoostMessageDeleteAfter = g.BoostMessageDeleteAfter,
|
||||
BoostMessageChannelId = g.BoostMessageChannelId
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public class GreetSettingsService : INService
|
||||
{
|
||||
public bool GroupGreets
|
||||
=> _bss.Data.GroupGreets;
|
||||
|
||||
private readonly DbService _db;
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, GreetSettings> _guildConfigsCache;
|
||||
@@ -14,9 +17,6 @@ public class GreetSettingsService : INService
|
||||
private readonly GreetGrouper<IUser> _byes = new();
|
||||
private readonly BotConfigService _bss;
|
||||
|
||||
public bool GroupGreets
|
||||
=> _bss.Data.GroupGreets;
|
||||
|
||||
public GreetSettingsService(
|
||||
DiscordSocketClient client,
|
||||
Bot bot,
|
||||
@@ -42,8 +42,10 @@ public class GreetSettingsService : INService
|
||||
{
|
||||
// if user is a new booster
|
||||
// or boosted again the same server
|
||||
if ((optOldUser.Value is { PremiumSince: null } && newUser is { PremiumSince: not null }) ||
|
||||
(optOldUser.Value?.PremiumSince is { } oldDate && newUser.PremiumSince is { } newDate && newDate > oldDate))
|
||||
if ((optOldUser.Value is { PremiumSince: null } && newUser is { PremiumSince: not null })
|
||||
|| (optOldUser.Value?.PremiumSince is { } oldDate
|
||||
&& newUser.PremiumSince is { } newDate
|
||||
&& newDate > oldDate))
|
||||
{
|
||||
var conf = GetOrAddSettingsForGuild(newUser.Guild.Id);
|
||||
if (!conf.SendBoostMessage) return Task.CompletedTask;
|
||||
@@ -65,20 +67,12 @@ public class GreetSettingsService : INService
|
||||
return;
|
||||
|
||||
var toSend = SmartText.CreateFrom(conf.BoostMessage);
|
||||
var rep = new ReplacementBuilder().WithDefault(user,
|
||||
channel,
|
||||
user.Guild,
|
||||
_client
|
||||
)
|
||||
.Build();
|
||||
var rep = new ReplacementBuilder().WithDefault(user, channel, user.Guild, _client).Build();
|
||||
|
||||
try
|
||||
{
|
||||
var toDelete = await channel.SendAsync(rep.Replace(toSend));
|
||||
if (conf.BoostMessageDeleteAfter > 0)
|
||||
{
|
||||
toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
|
||||
}
|
||||
if (conf.BoostMessageDeleteAfter > 0) toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -96,76 +90,71 @@ public class GreetSettingsService : INService
|
||||
{
|
||||
_guildConfigsCache.AddOrUpdate(gc.GuildId,
|
||||
GreetSettings.Create(gc),
|
||||
delegate { return GreetSettings.Create(gc); }
|
||||
);
|
||||
delegate { return GreetSettings.Create(gc); });
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task UserLeft(SocketGuild guild, SocketUser user)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
var conf = GetOrAddSettingsForGuild(guild.Id);
|
||||
|
||||
if (!conf.SendChannelByeMessage) return;
|
||||
var channel = guild.TextChannels.FirstOrDefault(c => c.Id == conf.ByeMessageChannelId);
|
||||
|
||||
if (channel is null) //maybe warn the server owner that the channel is missing
|
||||
return;
|
||||
|
||||
if (GroupGreets)
|
||||
{
|
||||
var conf = GetOrAddSettingsForGuild(guild.Id);
|
||||
|
||||
if (!conf.SendChannelByeMessage) return;
|
||||
var channel = guild.TextChannels.FirstOrDefault(c => c.Id == conf.ByeMessageChannelId);
|
||||
|
||||
if (channel is null) //maybe warn the server owner that the channel is missing
|
||||
return;
|
||||
|
||||
if (GroupGreets)
|
||||
// if group is newly created, greet that user right away,
|
||||
// but any user which joins in the next 5 seconds will
|
||||
// be greeted in a group greet
|
||||
if (_byes.CreateOrAdd(guild.Id, user))
|
||||
{
|
||||
// if group is newly created, greet that user right away,
|
||||
// but any user which joins in the next 5 seconds will
|
||||
// be greeted in a group greet
|
||||
if (_byes.CreateOrAdd(guild.Id, user))
|
||||
// greet single user
|
||||
await ByeUsers(conf, channel, new[] { user });
|
||||
var groupClear = false;
|
||||
while (!groupClear)
|
||||
{
|
||||
// greet single user
|
||||
await ByeUsers(conf, channel, new[] { user });
|
||||
var groupClear = false;
|
||||
while (!groupClear)
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
groupClear = _byes.ClearGroup(guild.Id, 5, out var toBye);
|
||||
await ByeUsers(conf, channel, toBye);
|
||||
}
|
||||
await Task.Delay(5000);
|
||||
groupClear = _byes.ClearGroup(guild.Id, 5, out var toBye);
|
||||
await ByeUsers(conf, channel, toBye);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ByeUsers(conf, channel, new[] { user });
|
||||
}
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
// ignored
|
||||
await ByeUsers(conf, channel, new[] { user });
|
||||
}
|
||||
}
|
||||
);
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string? GetDmGreetMsg(ulong id)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.GuildConfigsForId(id, set => set)
|
||||
?.DmGreetMessageText;
|
||||
return uow.GuildConfigsForId(id, set => set)?.DmGreetMessageText;
|
||||
}
|
||||
|
||||
public string? GetGreetMsg(ulong gid)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.GuildConfigsForId(gid, set => set)
|
||||
.ChannelGreetMessageText;
|
||||
return uow.GuildConfigsForId(gid, set => set).ChannelGreetMessageText;
|
||||
}
|
||||
|
||||
public string? GetBoostMessage(ulong gid)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.GuildConfigsForId(gid, set => set)
|
||||
.BoostMessage;
|
||||
return uow.GuildConfigsForId(gid, set => set).BoostMessage;
|
||||
}
|
||||
|
||||
private Task ByeUsers(GreetSettings conf, ITextChannel channel, IUser user)
|
||||
@@ -177,20 +166,17 @@ public class GreetSettingsService : INService
|
||||
return;
|
||||
|
||||
var rep = new ReplacementBuilder().WithChannel(channel)
|
||||
.WithClient(_client)
|
||||
.WithServer(_client, (SocketGuild)channel.Guild)
|
||||
.WithManyUsers(users)
|
||||
.Build();
|
||||
.WithClient(_client)
|
||||
.WithServer(_client, (SocketGuild)channel.Guild)
|
||||
.WithManyUsers(users)
|
||||
.Build();
|
||||
|
||||
var text = SmartText.CreateFrom(conf.ChannelByeMessageText);
|
||||
text = rep.Replace(text);
|
||||
try
|
||||
{
|
||||
var toDelete = await channel.SendAsync(text);
|
||||
if (conf.AutoDeleteByeMessagesTimer > 0)
|
||||
{
|
||||
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
|
||||
}
|
||||
if (conf.AutoDeleteByeMessagesTimer > 0) toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -207,20 +193,17 @@ public class GreetSettingsService : INService
|
||||
return;
|
||||
|
||||
var rep = new ReplacementBuilder().WithChannel(channel)
|
||||
.WithClient(_client)
|
||||
.WithServer(_client, (SocketGuild)channel.Guild)
|
||||
.WithManyUsers(users)
|
||||
.Build();
|
||||
.WithClient(_client)
|
||||
.WithServer(_client, (SocketGuild)channel.Guild)
|
||||
.WithManyUsers(users)
|
||||
.Build();
|
||||
|
||||
var text = SmartText.CreateFrom(conf.ChannelGreetMessageText);
|
||||
text = rep.Replace(text);
|
||||
try
|
||||
{
|
||||
var toDelete = await channel.SendAsync(text);
|
||||
if (conf.AutoDeleteGreetMessagesTimer > 0)
|
||||
{
|
||||
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
|
||||
}
|
||||
if (conf.AutoDeleteGreetMessagesTimer > 0) toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -230,12 +213,7 @@ public class GreetSettingsService : INService
|
||||
|
||||
private async Task<bool> GreetDmUser(GreetSettings conf, IDMChannel channel, IGuildUser user)
|
||||
{
|
||||
var rep = new ReplacementBuilder().WithDefault(user,
|
||||
channel,
|
||||
(SocketGuild)user.Guild,
|
||||
_client
|
||||
)
|
||||
.Build();
|
||||
var rep = new ReplacementBuilder().WithDefault(user, channel, (SocketGuild)user.Guild, _client).Build();
|
||||
|
||||
var text = SmartText.CreateFrom(conf.DmGreetMessageText);
|
||||
rep.Replace(text);
|
||||
@@ -254,65 +232,60 @@ public class GreetSettingsService : INService
|
||||
private Task UserJoined(IGuildUser user)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var conf = GetOrAddSettingsForGuild(user.GuildId);
|
||||
var conf = GetOrAddSettingsForGuild(user.GuildId);
|
||||
|
||||
if (conf.SendChannelGreetMessage)
|
||||
if (conf.SendChannelGreetMessage)
|
||||
{
|
||||
var channel = await user.Guild.GetTextChannelAsync(conf.GreetMessageChannelId);
|
||||
if (channel != null)
|
||||
{
|
||||
var channel = await user.Guild.GetTextChannelAsync(conf.GreetMessageChannelId);
|
||||
if (channel != null)
|
||||
if (GroupGreets)
|
||||
{
|
||||
if (GroupGreets)
|
||||
// if group is newly created, greet that user right away,
|
||||
// but any user which joins in the next 5 seconds will
|
||||
// be greeted in a group greet
|
||||
if (_greets.CreateOrAdd(user.GuildId, user))
|
||||
{
|
||||
// if group is newly created, greet that user right away,
|
||||
// but any user which joins in the next 5 seconds will
|
||||
// be greeted in a group greet
|
||||
if (_greets.CreateOrAdd(user.GuildId, user))
|
||||
// greet single user
|
||||
await GreetUsers(conf, channel, new[] { user });
|
||||
var groupClear = false;
|
||||
while (!groupClear)
|
||||
{
|
||||
// greet single user
|
||||
await GreetUsers(conf, channel, new[] { user });
|
||||
var groupClear = false;
|
||||
while (!groupClear)
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
groupClear = _greets.ClearGroup(user.GuildId, 5, out var toGreet);
|
||||
await GreetUsers(conf, channel, toGreet);
|
||||
}
|
||||
await Task.Delay(5000);
|
||||
groupClear = _greets.ClearGroup(user.GuildId, 5, out var toGreet);
|
||||
await GreetUsers(conf, channel, toGreet);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await GreetUsers(conf, channel, new[] { user });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (conf.SendDmGreetMessage)
|
||||
{
|
||||
var channel = await user.CreateDMChannelAsync();
|
||||
|
||||
if (channel is not null)
|
||||
else
|
||||
{
|
||||
await GreetDmUser(conf, channel, user);
|
||||
await GreetUsers(conf, channel, new[] { user });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
||||
if (conf.SendDmGreetMessage)
|
||||
{
|
||||
// ignored
|
||||
var channel = await user.CreateDMChannelAsync();
|
||||
|
||||
if (channel is not null) await GreetDmUser(conf, channel, user);
|
||||
}
|
||||
}
|
||||
);
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string? GetByeMessage(ulong gid)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.GuildConfigsForId(gid, set => set)
|
||||
.ChannelByeMessageText;
|
||||
return uow.GuildConfigsForId(gid, set => set).ChannelByeMessageText;
|
||||
}
|
||||
|
||||
public GreetSettings GetOrAddSettingsForGuild(ulong guildId)
|
||||
@@ -332,11 +305,9 @@ public class GreetSettingsService : INService
|
||||
|
||||
public async Task<bool> SetSettings(ulong guildId, GreetSettings settings)
|
||||
{
|
||||
if (settings.AutoDeleteByeMessagesTimer is > 600 or < 0 ||
|
||||
settings.AutoDeleteGreetMessagesTimer is > 600 or < 0)
|
||||
{
|
||||
if (settings.AutoDeleteByeMessagesTimer is > 600 or < 0
|
||||
|| settings.AutoDeleteGreetMessagesTimer is > 600 or < 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
@@ -411,53 +382,6 @@ public class GreetSettingsService : INService
|
||||
return enabled;
|
||||
}
|
||||
|
||||
#region Get Enabled Status
|
||||
|
||||
public bool GetGreetDmEnabled(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
return conf.SendDmGreetMessage;
|
||||
}
|
||||
|
||||
public bool GetGreetEnabled(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
return conf.SendChannelGreetMessage;
|
||||
}
|
||||
|
||||
public bool GetByeEnabled(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
return conf.SendChannelByeMessage;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Messages
|
||||
|
||||
public Task ByeTest(ITextChannel channel, IGuildUser user)
|
||||
{
|
||||
var conf = GetOrAddSettingsForGuild(user.GuildId);
|
||||
return ByeUsers(conf, channel, user);
|
||||
}
|
||||
|
||||
public Task GreetTest(ITextChannel channel, IGuildUser user)
|
||||
{
|
||||
var conf = GetOrAddSettingsForGuild(user.GuildId);
|
||||
return GreetUsers(conf, channel, user);
|
||||
}
|
||||
|
||||
public Task<bool> GreetDmTest(IDMChannel channel, IGuildUser user)
|
||||
{
|
||||
var conf = GetOrAddSettingsForGuild(user.GuildId);
|
||||
return GreetDmUser(conf, channel, user);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public bool SetGreetDmMessage(ulong guildId, ref string? message)
|
||||
{
|
||||
message = message?.SanitizeMentions();
|
||||
@@ -580,4 +504,51 @@ public class GreetSettingsService : INService
|
||||
_guildConfigsCache.AddOrUpdate(guildId, toAdd, (_, _) => toAdd);
|
||||
return conf.SendBoostMessage;
|
||||
}
|
||||
|
||||
#region Get Enabled Status
|
||||
|
||||
public bool GetGreetDmEnabled(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
return conf.SendDmGreetMessage;
|
||||
}
|
||||
|
||||
public bool GetGreetEnabled(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
return conf.SendChannelGreetMessage;
|
||||
}
|
||||
|
||||
public bool GetByeEnabled(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
return conf.SendChannelByeMessage;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Messages
|
||||
|
||||
public Task ByeTest(ITextChannel channel, IGuildUser user)
|
||||
{
|
||||
var conf = GetOrAddSettingsForGuild(user.GuildId);
|
||||
return ByeUsers(conf, channel, user);
|
||||
}
|
||||
|
||||
public Task GreetTest(ITextChannel channel, IGuildUser user)
|
||||
{
|
||||
var conf = GetOrAddSettingsForGuild(user.GuildId);
|
||||
return GreetUsers(conf, channel, user);
|
||||
}
|
||||
|
||||
public Task<bool> GreetDmTest(IDMChannel channel, IGuildUser user)
|
||||
{
|
||||
var conf = GetOrAddSettingsForGuild(user.GuildId);
|
||||
return GreetDmUser(conf, channel, user);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -5,7 +5,8 @@ public partial class Administration
|
||||
[Group]
|
||||
public class ServerGreetCommands : NadekoSubmodule<GreetSettingsService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task Boost()
|
||||
@@ -18,7 +19,8 @@ public partial class Administration
|
||||
await ReplyPendingLocalizedAsync(strs.boost_off);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task BoostDel(int timer = 30)
|
||||
@@ -34,7 +36,8 @@ public partial class Administration
|
||||
await ReplyPendingLocalizedAsync(strs.boostdel_off);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task BoostMsg([Leftover] string? text = null)
|
||||
@@ -53,7 +56,8 @@ public partial class Administration
|
||||
await ReplyPendingLocalizedAsync(strs.boostmsg_enable($"`{Prefix}boost`"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task GreetDel(int timer = 30)
|
||||
@@ -69,7 +73,8 @@ public partial class Administration
|
||||
await ReplyPendingLocalizedAsync(strs.greetdel_off);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task Greet()
|
||||
@@ -82,7 +87,8 @@ public partial class Administration
|
||||
await ReplyPendingLocalizedAsync(strs.greet_off);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task GreetMsg([Leftover] string? text = null)
|
||||
@@ -97,12 +103,13 @@ public partial class Administration
|
||||
var sendGreetEnabled = _service.SetGreetMessage(ctx.Guild.Id, ref text);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.greetmsg_new);
|
||||
|
||||
|
||||
if (!sendGreetEnabled)
|
||||
await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task GreetDm()
|
||||
@@ -115,7 +122,8 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.greetdm_off);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task GreetDmMsg([Leftover] string? text = null)
|
||||
@@ -134,7 +142,8 @@ public partial class Administration
|
||||
await ReplyPendingLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task Bye()
|
||||
@@ -147,7 +156,8 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.bye_off);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task ByeMsg([Leftover] string? text = null)
|
||||
@@ -166,7 +176,8 @@ public partial class Administration
|
||||
await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
public async Task ByeDel(int timer = 30)
|
||||
@@ -180,7 +191,8 @@ public partial class Administration
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
[Ratelimit(5)]
|
||||
@@ -190,13 +202,11 @@ public partial class Administration
|
||||
|
||||
await _service.ByeTest((ITextChannel)ctx.Channel, user);
|
||||
var enabled = _service.GetByeEnabled(ctx.Guild.Id);
|
||||
if (!enabled)
|
||||
{
|
||||
await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
|
||||
}
|
||||
if (!enabled) await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
[Ratelimit(5)]
|
||||
@@ -206,13 +216,11 @@ public partial class Administration
|
||||
|
||||
await _service.GreetTest((ITextChannel)ctx.Channel, user);
|
||||
var enabled = _service.GetGreetEnabled(ctx.Guild.Id);
|
||||
if (!enabled)
|
||||
{
|
||||
await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
|
||||
}
|
||||
if (!enabled) await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageGuild)]
|
||||
[Ratelimit(5)]
|
||||
@@ -231,4 +239,4 @@ public partial class Administration
|
||||
await ReplyPendingLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,46 +8,46 @@ public partial class Administration
|
||||
[Group]
|
||||
public class LocalizationCommands : NadekoSubmodule
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, string> supportedLocales =
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{"ar", "العربية"},
|
||||
{"zh-TW", "繁體中文, 台灣"},
|
||||
{"zh-CN", "简体中文, 中华人民共和国"},
|
||||
{"nl-NL", "Nederlands, Nederland"},
|
||||
{"en-US", "English, United States"},
|
||||
{"fr-FR", "Français, France"},
|
||||
{"cs-CZ", "Čeština, Česká republika"},
|
||||
{"da-DK", "Dansk, Danmark"},
|
||||
{"de-DE", "Deutsch, Deutschland"},
|
||||
{"he-IL", "עברית, ישראל"},
|
||||
{"hu-HU", "Magyar, Magyarország"},
|
||||
{"id-ID", "Bahasa Indonesia, Indonesia"},
|
||||
{"it-IT", "Italiano, Italia"},
|
||||
{"ja-JP", "日本語, 日本"},
|
||||
{"ko-KR", "한국어, 대한민국"},
|
||||
{"nb-NO", "Norsk, Norge"},
|
||||
{"pl-PL", "Polski, Polska"},
|
||||
{"pt-BR", "Português Brasileiro, Brasil"},
|
||||
{"ro-RO", "Română, România"},
|
||||
{"ru-RU", "Русский, Россия"},
|
||||
{"sr-Cyrl-RS", "Српски, Србија"},
|
||||
{"es-ES", "Español, España"},
|
||||
{"sv-SE", "Svenska, Sverige"},
|
||||
{"tr-TR", "Türkçe, Türkiye"},
|
||||
{"ts-TS", "Tsundere, You Baka"},
|
||||
{"uk-UA", "Українська, Україна"}
|
||||
};
|
||||
private static readonly IReadOnlyDictionary<string, string> supportedLocales = new Dictionary<string, string>
|
||||
{
|
||||
{ "ar", "العربية" },
|
||||
{ "zh-TW", "繁體中文, 台灣" },
|
||||
{ "zh-CN", "简体中文, 中华人民共和国" },
|
||||
{ "nl-NL", "Nederlands, Nederland" },
|
||||
{ "en-US", "English, United States" },
|
||||
{ "fr-FR", "Français, France" },
|
||||
{ "cs-CZ", "Čeština, Česká republika" },
|
||||
{ "da-DK", "Dansk, Danmark" },
|
||||
{ "de-DE", "Deutsch, Deutschland" },
|
||||
{ "he-IL", "עברית, ישראל" },
|
||||
{ "hu-HU", "Magyar, Magyarország" },
|
||||
{ "id-ID", "Bahasa Indonesia, Indonesia" },
|
||||
{ "it-IT", "Italiano, Italia" },
|
||||
{ "ja-JP", "日本語, 日本" },
|
||||
{ "ko-KR", "한국어, 대한민국" },
|
||||
{ "nb-NO", "Norsk, Norge" },
|
||||
{ "pl-PL", "Polski, Polska" },
|
||||
{ "pt-BR", "Português Brasileiro, Brasil" },
|
||||
{ "ro-RO", "Română, România" },
|
||||
{ "ru-RU", "Русский, Россия" },
|
||||
{ "sr-Cyrl-RS", "Српски, Србија" },
|
||||
{ "es-ES", "Español, España" },
|
||||
{ "sv-SE", "Svenska, Sverige" },
|
||||
{ "tr-TR", "Türkçe, Türkiye" },
|
||||
{ "ts-TS", "Tsundere, You Baka" },
|
||||
{ "uk-UA", "Українська, Україна" }
|
||||
};
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task LanguageSet()
|
||||
=> await ReplyConfirmLocalizedAsync(strs.lang_set_show(
|
||||
Format.Bold(Culture.ToString()),
|
||||
=> await ReplyConfirmLocalizedAsync(strs.lang_set_show(Format.Bold(Culture.ToString()),
|
||||
Format.Bold(Culture.NativeName)));
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(1)]
|
||||
@@ -67,8 +67,7 @@ public partial class Administration
|
||||
Localization.SetGuildCulture(ctx.Guild, ci);
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.lang_set(Format.Bold(ci.ToString()),
|
||||
Format.Bold(ci.NativeName)));
|
||||
await ReplyConfirmLocalizedAsync(strs.lang_set(Format.Bold(ci.ToString()), Format.Bold(ci.NativeName)));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -76,14 +75,16 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task LanguageSetDefault()
|
||||
{
|
||||
var cul = Localization.DefaultCultureInfo;
|
||||
await ReplyErrorLocalizedAsync(strs.lang_set_bot_show(cul, cul.NativeName));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task LanguageSetDefault(string name)
|
||||
{
|
||||
@@ -110,12 +111,15 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task LanguagesList()
|
||||
=> await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.lang_list))
|
||||
.WithDescription(string.Join("\n",
|
||||
supportedLocales.Select(x => $"{Format.Code(x.Key),-10} => {x.Value}"))));
|
||||
=> await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.lang_list))
|
||||
.WithDescription(string.Join("\n",
|
||||
supportedLocales.Select(
|
||||
x => $"{Format.Code(x.Key),-10} => {x.Value}"))));
|
||||
}
|
||||
}
|
||||
/* list of language codes for reference.
|
||||
@@ -248,4 +252,4 @@ public partial class Administration
|
||||
{ "YE", "ar-YE" },
|
||||
{ "ZA", "af-ZA" },
|
||||
{ "ZW", "en-ZW" }
|
||||
*/
|
||||
*/
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.TypeReaders.Models;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
@@ -11,7 +11,8 @@ public partial class Administration
|
||||
[NoPublicBot]
|
||||
public class LogCommands : NadekoSubmodule<ILogCommandService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
@@ -24,7 +25,8 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.log_disabled);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
@@ -38,63 +40,74 @@ public partial class Administration
|
||||
?? new List<IgnoredLogItem>();
|
||||
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.AddField(GetText(strs.log_ignored_channels),
|
||||
chs.Count == 0 ? "-" : string.Join('\n', chs.Select(x => $"{x.LogItemId} | <#{x.LogItemId}>")))
|
||||
.AddField(GetText(strs.log_ignored_users),
|
||||
usrs.Count == 0 ? "-" : string.Join('\n', usrs.Select(x => $"{x.LogItemId} | <@{x.LogItemId}>")));
|
||||
.WithOkColor()
|
||||
.AddField(GetText(strs.log_ignored_channels),
|
||||
chs.Count == 0
|
||||
? "-"
|
||||
: string.Join('\n', chs.Select(x => $"{x.LogItemId} | <#{x.LogItemId}>")))
|
||||
.AddField(GetText(strs.log_ignored_users),
|
||||
usrs.Count == 0
|
||||
? "-"
|
||||
: string.Join('\n', usrs.Select(x => $"{x.LogItemId} | <@{x.LogItemId}>")));
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
public async Task LogIgnore([Leftover]ITextChannel target)
|
||||
public async Task LogIgnore([Leftover] ITextChannel target)
|
||||
{
|
||||
target ??= (ITextChannel)ctx.Channel;
|
||||
|
||||
var removed = _service.LogIgnore(ctx.Guild.Id, target.Id, IgnoredItemType.Channel);
|
||||
|
||||
if (!removed)
|
||||
await ReplyConfirmLocalizedAsync(strs.log_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")")));
|
||||
await ReplyConfirmLocalizedAsync(
|
||||
strs.log_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")")));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.log_not_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")")));
|
||||
await ReplyConfirmLocalizedAsync(
|
||||
strs.log_not_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")")));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
public async Task LogIgnore([Leftover]IUser target)
|
||||
public async Task LogIgnore([Leftover] IUser target)
|
||||
{
|
||||
var removed = _service.LogIgnore(ctx.Guild.Id, target.Id, IgnoredItemType.User);
|
||||
|
||||
if (!removed)
|
||||
await ReplyConfirmLocalizedAsync(strs.log_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")")));
|
||||
await ReplyConfirmLocalizedAsync(
|
||||
strs.log_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")")));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.log_not_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")")));
|
||||
await ReplyConfirmLocalizedAsync(
|
||||
strs.log_not_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")")));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
public async Task LogEvents()
|
||||
{
|
||||
var logSetting = _service.GetGuildLogSettings(ctx.Guild.Id);
|
||||
var str = string.Join("\n", Enum.GetNames(typeof(LogType))
|
||||
.Select(x =>
|
||||
{
|
||||
var val = logSetting is null ? null : GetLogProperty(logSetting, Enum.Parse<LogType>(x));
|
||||
if (val != null)
|
||||
return $"{Format.Bold(x)} <#{val}>";
|
||||
return Format.Bold(x);
|
||||
}));
|
||||
var str = string.Join("\n",
|
||||
Enum.GetNames(typeof(LogType))
|
||||
.Select(x =>
|
||||
{
|
||||
var val = logSetting is null ? null : GetLogProperty(logSetting, Enum.Parse<LogType>(x));
|
||||
if (val != null)
|
||||
return $"{Format.Bold(x)} <#{val}>";
|
||||
return Format.Bold(x);
|
||||
}));
|
||||
|
||||
await SendConfirmAsync(Format.Bold(GetText(strs.log_events)) + "\n" +
|
||||
str);
|
||||
await SendConfirmAsync(Format.Bold(GetText(strs.log_events)) + "\n" + str);
|
||||
}
|
||||
|
||||
private static ulong? GetLogProperty(LogSetting l, LogType type)
|
||||
@@ -136,7 +149,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
@@ -150,4 +164,4 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.log_stop(Format.Bold(type.ToString())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ public partial class Administration
|
||||
{
|
||||
var runnerUserRoles = runnerUser.GetRoles();
|
||||
var targetUserRoles = targetUser.GetRoles();
|
||||
if (runnerUser.Id != ctx.Guild.OwnerId &&
|
||||
runnerUserRoles.Max(x => x.Position) <= targetUserRoles.Max(x => x.Position))
|
||||
if (runnerUser.Id != ctx.Guild.OwnerId
|
||||
&& runnerUserRoles.Max(x => x.Position) <= targetUserRoles.Max(x => x.Position))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.mute_perms);
|
||||
return false;
|
||||
@@ -23,7 +23,8 @@ public partial class Administration
|
||||
return true;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async Task MuteRole([Leftover] IRole role = null)
|
||||
@@ -34,20 +35,21 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.mute_role(Format.Code(muteRole.Name)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.User.Id != ctx.Guild.OwnerId &&
|
||||
role.Position >= ((SocketGuildUser) ctx.User).Roles.Max(x => x.Position))
|
||||
|
||||
if (ctx.User.Id != ctx.Guild.OwnerId
|
||||
&& role.Position >= ((SocketGuildUser)ctx.User).Roles.Max(x => x.Position))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.insuf_perms_u);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await _service.SetMuteRoleAsync(ctx.Guild.Id, role.Name);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.mute_role_set);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
|
||||
[Priority(0)]
|
||||
@@ -68,7 +70,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
|
||||
[Priority(1)]
|
||||
@@ -82,7 +85,8 @@ public partial class Administration
|
||||
return;
|
||||
|
||||
await _service.TimedMute(user, ctx.User, time.Time, reason: reason);
|
||||
await ReplyConfirmLocalizedAsync(strs.user_muted_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
|
||||
await ReplyConfirmLocalizedAsync(strs.user_muted_time(Format.Bold(user.ToString()),
|
||||
(int)time.Time.TotalMinutes));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -91,7 +95,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
|
||||
public async Task Unmute(IGuildUser user, [Leftover] string reason = "")
|
||||
@@ -107,7 +112,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
@@ -118,7 +124,7 @@ public partial class Administration
|
||||
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
||||
return;
|
||||
|
||||
await _service.MuteUser(user, ctx.User, MuteType.Chat, reason: reason);
|
||||
await _service.MuteUser(user, ctx.User, MuteType.Chat, reason);
|
||||
await ReplyConfirmLocalizedAsync(strs.user_chat_mute(Format.Bold(user.ToString())));
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -128,7 +134,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(1)]
|
||||
@@ -141,8 +148,9 @@ public partial class Administration
|
||||
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
||||
return;
|
||||
|
||||
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Chat, reason: reason);
|
||||
await ReplyConfirmLocalizedAsync(strs.user_chat_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
|
||||
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Chat, reason);
|
||||
await ReplyConfirmLocalizedAsync(strs.user_chat_mute_time(Format.Bold(user.ToString()),
|
||||
(int)time.Time.TotalMinutes));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -151,14 +159,15 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async Task ChatUnmute(IGuildUser user, [Leftover] string reason = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
await _service.UnmuteUser(user.Guild.Id, user.Id, ctx.User, MuteType.Chat, reason: reason);
|
||||
await _service.UnmuteUser(user.Guild.Id, user.Id, ctx.User, MuteType.Chat, reason);
|
||||
await ReplyConfirmLocalizedAsync(strs.user_chat_unmute(Format.Bold(user.ToString())));
|
||||
}
|
||||
catch
|
||||
@@ -167,7 +176,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.MuteMembers)]
|
||||
[Priority(0)]
|
||||
@@ -178,7 +188,7 @@ public partial class Administration
|
||||
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
||||
return;
|
||||
|
||||
await _service.MuteUser(user, ctx.User, MuteType.Voice, reason: reason);
|
||||
await _service.MuteUser(user, ctx.User, MuteType.Voice, reason);
|
||||
await ReplyConfirmLocalizedAsync(strs.user_voice_mute(Format.Bold(user.ToString())));
|
||||
}
|
||||
catch
|
||||
@@ -187,11 +197,12 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.MuteMembers)]
|
||||
[Priority(1)]
|
||||
public async Task VoiceMute(StoopidTime time,IGuildUser user, [Leftover] string reason = "")
|
||||
public async Task VoiceMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
|
||||
{
|
||||
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
|
||||
return;
|
||||
@@ -200,8 +211,9 @@ public partial class Administration
|
||||
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
|
||||
return;
|
||||
|
||||
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Voice, reason: reason);
|
||||
await ReplyConfirmLocalizedAsync(strs.user_voice_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
|
||||
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Voice, reason);
|
||||
await ReplyConfirmLocalizedAsync(strs.user_voice_mute_time(Format.Bold(user.ToString()),
|
||||
(int)time.Time.TotalMinutes));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -209,14 +221,15 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.MuteMembers)]
|
||||
public async Task VoiceUnmute(IGuildUser user, [Leftover] string reason = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
await _service.UnmuteUser(user.GuildId, user.Id, ctx.User, MuteType.Voice, reason: reason);
|
||||
await _service.UnmuteUser(user.GuildId, user.Id, ctx.User, MuteType.Voice, reason);
|
||||
await ReplyConfirmLocalizedAsync(strs.user_voice_unmute(Format.Bold(user.ToString())));
|
||||
}
|
||||
catch
|
||||
@@ -225,4 +238,4 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@ public partial class Administration
|
||||
[Group]
|
||||
public class PlayingRotateCommands : NadekoSubmodule<PlayingRotateService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task RotatePlaying()
|
||||
{
|
||||
@@ -18,7 +19,8 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.ropl_disabled);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task AddPlaying(ActivityType t, [Leftover] string status)
|
||||
{
|
||||
@@ -27,7 +29,8 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.ropl_added);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task ListPlaying()
|
||||
{
|
||||
@@ -40,13 +43,13 @@ public partial class Administration
|
||||
else
|
||||
{
|
||||
var i = 1;
|
||||
await ReplyConfirmLocalizedAsync(strs.ropl_list(
|
||||
string.Join("\n\t", statuses.Select(rs => $"`{i++}.` *{rs.Type}* {rs.Status}"))));
|
||||
await ReplyConfirmLocalizedAsync(strs.ropl_list(string.Join("\n\t",
|
||||
statuses.Select(rs => $"`{i++}.` *{rs.Type}* {rs.Status}"))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task RemovePlaying(int index)
|
||||
{
|
||||
@@ -60,4 +63,4 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.reprm(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,41 +6,45 @@ public partial class Administration
|
||||
[Group]
|
||||
public class PrefixCommands : NadekoSubmodule
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task PrefixCommand()
|
||||
=> await ReplyConfirmLocalizedAsync(strs.prefix_current(Format.Code(CmdHandler.GetPrefix(ctx.Guild))));
|
||||
|
||||
public enum Set
|
||||
{
|
||||
Set
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(1)]
|
||||
public async Task PrefixCommand()
|
||||
=> await ReplyConfirmLocalizedAsync(strs.prefix_current(Format.Code(CmdHandler.GetPrefix(ctx.Guild))));
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(0)]
|
||||
public Task PrefixCommand(Set _, [Leftover] string prefix)
|
||||
=> PrefixCommand(prefix);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(0)]
|
||||
public async Task PrefixCommand([Leftover]string prefix)
|
||||
public async Task PrefixCommand([Leftover] string prefix)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prefix))
|
||||
return;
|
||||
|
||||
var oldPrefix = base.Prefix;
|
||||
var oldPrefix = Prefix;
|
||||
var newPrefix = CmdHandler.SetPrefix(ctx.Guild, prefix);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.prefix_new(Format.Code(oldPrefix), Format.Code(newPrefix)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task DefPrefix([Leftover]string prefix = null)
|
||||
public async Task DefPrefix([Leftover] string prefix = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prefix))
|
||||
{
|
||||
@@ -54,4 +58,4 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.defprefix_new(Format.Code(oldPrefix), Format.Code(newPrefix)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Common.TypeReaders.Models;
|
||||
using NadekoBot.Modules.Administration.Common;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Common.TypeReaders.Models;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
@@ -11,7 +11,8 @@ public partial class Administration
|
||||
[Group]
|
||||
public class ProtectionCommands : NadekoSubmodule<ProtectionService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AntiAlt()
|
||||
@@ -25,26 +26,31 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.protection_not_running("Anti-Alt"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover] StoopidTime punishTime = null)
|
||||
{
|
||||
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
|
||||
var punishTimeMinutes = (int?) punishTime?.Time.TotalMinutes ?? 0;
|
||||
var punishTimeMinutes = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
||||
|
||||
if (minAgeMinutes < 1 || punishTimeMinutes < 0)
|
||||
return;
|
||||
|
||||
await _service.StartAntiAltAsync(ctx.Guild.Id, minAgeMinutes, action, (int?)punishTime?.Time.TotalMinutes ?? 0);
|
||||
await _service.StartAntiAltAsync(ctx.Guild.Id,
|
||||
minAgeMinutes,
|
||||
action,
|
||||
(int?)punishTime?.Time.TotalMinutes ?? 0);
|
||||
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover]IRole role)
|
||||
public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover] IRole role)
|
||||
{
|
||||
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
|
||||
|
||||
@@ -55,46 +61,50 @@ public partial class Administration
|
||||
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public Task AntiRaid()
|
||||
{
|
||||
if (_service.TryStopAntiRaid(ctx.Guild.Id))
|
||||
{
|
||||
return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Raid"));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Raid"));
|
||||
}
|
||||
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Raid"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(1)]
|
||||
public Task AntiRaid(int userThreshold, int seconds,
|
||||
PunishmentAction action, [Leftover] StoopidTime punishTime)
|
||||
=> InternalAntiRaid(userThreshold, seconds, action, punishTime: punishTime);
|
||||
public Task AntiRaid(
|
||||
int userThreshold,
|
||||
int seconds,
|
||||
PunishmentAction action,
|
||||
[Leftover] StoopidTime punishTime)
|
||||
=> InternalAntiRaid(userThreshold, seconds, action, punishTime);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(2)]
|
||||
public Task AntiRaid(int userThreshold, int seconds, PunishmentAction action)
|
||||
=> InternalAntiRaid(userThreshold, seconds, action);
|
||||
|
||||
private async Task InternalAntiRaid(int userThreshold, int seconds = 10,
|
||||
PunishmentAction action = PunishmentAction.Mute, StoopidTime punishTime = null)
|
||||
|
||||
private async Task InternalAntiRaid(
|
||||
int userThreshold,
|
||||
int seconds = 10,
|
||||
PunishmentAction action = PunishmentAction.Mute,
|
||||
StoopidTime punishTime = null)
|
||||
{
|
||||
if (action == PunishmentAction.AddRole)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.punishment_unsupported(action));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (userThreshold is < 2 or > 30)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.raid_cnt(2, 30));
|
||||
@@ -106,47 +116,36 @@ public partial class Administration
|
||||
await ReplyErrorLocalizedAsync(strs.raid_time(2, 300));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (punishTime is not null)
|
||||
{
|
||||
if (!_service.IsDurationAllowed(action))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.prot_cant_use_time);
|
||||
}
|
||||
}
|
||||
|
||||
var time = (int?) punishTime?.Time.TotalMinutes ?? 0;
|
||||
|
||||
var time = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
||||
if (time is < 0 or > 60 * 24)
|
||||
return;
|
||||
|
||||
var stats = await _service.StartAntiRaidAsync(ctx.Guild.Id, userThreshold, seconds,
|
||||
action, time);
|
||||
var stats = await _service.StartAntiRaidAsync(ctx.Guild.Id, userThreshold, seconds, action, time);
|
||||
|
||||
if (stats is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (stats is null) return;
|
||||
|
||||
await SendConfirmAsync(GetText(strs.prot_enable("Anti-Raid")),
|
||||
$"{ctx.User.Mention} {GetAntiRaidString(stats)}");
|
||||
$"{ctx.User.Mention} {GetAntiRaidString(stats)}");
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public Task AntiSpam()
|
||||
{
|
||||
if (_service.TryStopAntiSpam(ctx.Guild.Id))
|
||||
{
|
||||
return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Spam"));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Spam"));
|
||||
}
|
||||
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Spam"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(0)]
|
||||
@@ -158,35 +157,36 @@ public partial class Administration
|
||||
return InternalAntiSpam(messageCount, action, null, role);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(1)]
|
||||
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] StoopidTime punishTime)
|
||||
=> InternalAntiSpam(messageCount, action, punishTime, null);
|
||||
=> InternalAntiSpam(messageCount, action, punishTime);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(2)]
|
||||
public Task AntiSpam(int messageCount, PunishmentAction action)
|
||||
=> InternalAntiSpam(messageCount, action);
|
||||
|
||||
public async Task InternalAntiSpam(int messageCount, PunishmentAction action,
|
||||
StoopidTime timeData = null, IRole role = null)
|
||||
public async Task InternalAntiSpam(
|
||||
int messageCount,
|
||||
PunishmentAction action,
|
||||
StoopidTime timeData = null,
|
||||
IRole role = null)
|
||||
{
|
||||
if (messageCount is < 2 or > 10)
|
||||
return;
|
||||
|
||||
if (timeData is not null)
|
||||
{
|
||||
if (!_service.IsDurationAllowed(action))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.prot_cant_use_time);
|
||||
}
|
||||
}
|
||||
|
||||
var time = (int?) timeData?.Time.TotalMinutes ?? 0;
|
||||
var time = (int?)timeData?.Time.TotalMinutes ?? 0;
|
||||
if (time is < 0 or > 60 * 24)
|
||||
return;
|
||||
|
||||
@@ -196,14 +196,15 @@ public partial class Administration
|
||||
$"{ctx.User.Mention} {GetAntiSpamString(stats)}");
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AntispamIgnore()
|
||||
{
|
||||
var added = await _service.AntiSpamIgnoreAsync(ctx.Guild.Id, ctx.Channel.Id);
|
||||
|
||||
if(added is null)
|
||||
if (added is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.protection_not_running("Anti-Spam"));
|
||||
return;
|
||||
@@ -215,7 +216,8 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.spam_not_ignore("Anti-Spam"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AntiList()
|
||||
{
|
||||
@@ -227,8 +229,7 @@ public partial class Administration
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.prot_active));
|
||||
var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.prot_active));
|
||||
|
||||
if (spam != null)
|
||||
embed.AddField("Anti-Spam", GetAntiSpamString(spam).TrimTo(1024), true);
|
||||
@@ -242,9 +243,8 @@ public partial class Administration
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
private string GetAntiAltString(AntiAltStats alt)
|
||||
=> GetText(strs.anti_alt_status(
|
||||
Format.Bold(alt.MinAge.ToString(@"dd\d\ hh\h\ mm\m\ ")),
|
||||
private string GetAntiAltString(AntiAltStats alt)
|
||||
=> GetText(strs.anti_alt_status(Format.Bold(alt.MinAge.ToString(@"dd\d\ hh\h\ mm\m\ ")),
|
||||
Format.Bold(alt.Action.ToString()),
|
||||
Format.Bold(alt.Counter.ToString())));
|
||||
|
||||
@@ -257,13 +257,9 @@ public partial class Administration
|
||||
ignoredString = "none";
|
||||
|
||||
var add = string.Empty;
|
||||
if (settings.MuteTime > 0)
|
||||
{
|
||||
add = $" ({TimeSpan.FromMinutes(settings.MuteTime):hh\\hmm\\m})";
|
||||
}
|
||||
if (settings.MuteTime > 0) add = $" ({TimeSpan.FromMinutes(settings.MuteTime):hh\\hmm\\m})";
|
||||
|
||||
return GetText(strs.spam_stats(
|
||||
Format.Bold(settings.MessageThreshold.ToString()),
|
||||
return GetText(strs.spam_stats(Format.Bold(settings.MessageThreshold.ToString()),
|
||||
Format.Bold(settings.Action + add),
|
||||
ignoredString));
|
||||
}
|
||||
@@ -273,14 +269,11 @@ public partial class Administration
|
||||
var actionString = Format.Bold(stats.AntiRaidSettings.Action.ToString());
|
||||
|
||||
if (stats.AntiRaidSettings.PunishDuration > 0)
|
||||
{
|
||||
actionString += $" **({TimeSpan.FromMinutes(stats.AntiRaidSettings.PunishDuration):hh\\hmm\\m})**";
|
||||
}
|
||||
|
||||
return GetText(strs.raid_stats(
|
||||
Format.Bold(stats.AntiRaidSettings.UserThreshold.ToString()),
|
||||
return GetText(strs.raid_stats(Format.Bold(stats.AntiRaidSettings.UserThreshold.ToString()),
|
||||
Format.Bold(stats.AntiRaidSettings.Seconds.ToString()),
|
||||
actionString));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using ITextChannel = Discord.ITextChannel;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
@@ -12,7 +11,8 @@ public partial class Administration
|
||||
private static readonly TimeSpan twoWeeks = TimeSpan.FromDays(14);
|
||||
|
||||
//delets her own messages, no perm required
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Prune(string parameter = null)
|
||||
{
|
||||
@@ -24,8 +24,10 @@ public partial class Administration
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, x => x.Author.Id == user.Id);
|
||||
ctx.Message.DeleteAfter(3);
|
||||
}
|
||||
|
||||
// prune x
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
@@ -45,7 +47,8 @@ public partial class Administration
|
||||
}
|
||||
|
||||
//prune @user [x]
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
@@ -54,7 +57,8 @@ public partial class Administration
|
||||
=> Prune(user.Id, count, parameter);
|
||||
|
||||
//prune userid [x]
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
@@ -71,9 +75,13 @@ public partial class Administration
|
||||
count = 1000;
|
||||
|
||||
if (parameter is "-s" or "--safe")
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, count, m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks && !m.IsPinned);
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel,
|
||||
count,
|
||||
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks && !m.IsPinned);
|
||||
else
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, count, m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks);
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel,
|
||||
count,
|
||||
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System.Net;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
@@ -10,9 +11,10 @@ public partial class Administration
|
||||
{
|
||||
public class RoleCommands : NadekoSubmodule<RoleCommandsService>
|
||||
{
|
||||
private IServiceProvider _services;
|
||||
public enum Exclude { Excl }
|
||||
|
||||
private IServiceProvider _services;
|
||||
|
||||
public RoleCommands(IServiceProvider services)
|
||||
=> _services = services;
|
||||
|
||||
@@ -20,34 +22,32 @@ public partial class Administration
|
||||
{
|
||||
var target = messageId is { } msgId
|
||||
? await ctx.Channel.GetMessageAsync(msgId)
|
||||
: (await ctx.Channel.GetMessagesAsync(2).FlattenAsync())
|
||||
.Skip(1)
|
||||
.FirstOrDefault();
|
||||
: (await ctx.Channel.GetMessagesAsync(2).FlattenAsync()).Skip(1).FirstOrDefault();
|
||||
|
||||
if (input.Length % 2 != 0)
|
||||
return;
|
||||
|
||||
var all = await input
|
||||
.Chunk(input.Length / 2)
|
||||
.Select(async x =>
|
||||
{
|
||||
var inputRoleStr = x.First();
|
||||
var roleReader = new RoleTypeReader<SocketRole>();
|
||||
var roleResult = await roleReader.ReadAsync(ctx, inputRoleStr, _services);
|
||||
if (!roleResult.IsSuccess)
|
||||
{
|
||||
Log.Warning("Role {0} not found.", inputRoleStr);
|
||||
return null;
|
||||
}
|
||||
var role = (IRole)roleResult.BestMatch;
|
||||
if (role.Position > ((IGuildUser)ctx.User).GetRoles().Select(r => r.Position).Max()
|
||||
&& ctx.User.Id != ctx.Guild.OwnerId)
|
||||
return null;
|
||||
var emote = x.Last().ToIEmote();
|
||||
return new { role, emote };
|
||||
})
|
||||
.Where(x => x != null)
|
||||
.WhenAll();
|
||||
var all = await input.Chunk(input.Length / 2)
|
||||
.Select(async x =>
|
||||
{
|
||||
var inputRoleStr = x.First();
|
||||
var roleReader = new RoleTypeReader<SocketRole>();
|
||||
var roleResult = await roleReader.ReadAsync(ctx, inputRoleStr, _services);
|
||||
if (!roleResult.IsSuccess)
|
||||
{
|
||||
Log.Warning("Role {0} not found.", inputRoleStr);
|
||||
return null;
|
||||
}
|
||||
|
||||
var role = (IRole)roleResult.BestMatch;
|
||||
if (role.Position > ((IGuildUser)ctx.User).GetRoles().Select(r => r.Position).Max()
|
||||
&& ctx.User.Id != ctx.Guild.OwnerId)
|
||||
return null;
|
||||
var emote = x.Last().ToIEmote();
|
||||
return new { role, emote };
|
||||
})
|
||||
.Where(x => x != null)
|
||||
.WhenAll();
|
||||
|
||||
if (!all.Any())
|
||||
return;
|
||||
@@ -56,12 +56,10 @@ public partial class Administration
|
||||
{
|
||||
try
|
||||
{
|
||||
await target.AddReactionAsync(x.emote, new()
|
||||
{
|
||||
RetryMode = RetryMode.Retry502 | RetryMode.RetryRatelimit
|
||||
});
|
||||
await target.AddReactionAsync(x.emote,
|
||||
new() { RetryMode = RetryMode.Retry502 | RetryMode.RetryRatelimit });
|
||||
}
|
||||
catch (Discord.Net.HttpException ex) when(ex.HttpCode == HttpStatusCode.BadRequest)
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.reaction_cant_access(Format.Code(x.emote.ToString())));
|
||||
return;
|
||||
@@ -70,75 +68,75 @@ public partial class Administration
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
if (_service.Add(ctx.Guild.Id, new()
|
||||
{
|
||||
Exclusive = exclusive,
|
||||
MessageId = target.Id,
|
||||
ChannelId = target.Channel.Id,
|
||||
ReactionRoles = all.Select(x =>
|
||||
if (_service.Add(ctx.Guild.Id,
|
||||
new()
|
||||
{
|
||||
return new ReactionRole()
|
||||
{
|
||||
EmoteName = x.emote.ToString(),
|
||||
RoleId = x.role.Id,
|
||||
};
|
||||
}).ToList(),
|
||||
}))
|
||||
{
|
||||
Exclusive = exclusive,
|
||||
MessageId = target.Id,
|
||||
ChannelId = target.Channel.Id,
|
||||
ReactionRoles = all.Select(x =>
|
||||
{
|
||||
return new ReactionRole
|
||||
{
|
||||
EmoteName = x.emote.ToString(), RoleId = x.role.Id
|
||||
};
|
||||
})
|
||||
.ToList()
|
||||
}))
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.reaction_roles_full);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
public Task ReactionRoles(ulong messageId, params string[] input) =>
|
||||
InternalReactionRoles(false, messageId, input);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public Task ReactionRoles(ulong messageId, params string[] input)
|
||||
=> InternalReactionRoles(false, messageId, input);
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(1)]
|
||||
public Task ReactionRoles(ulong messageId, Exclude _, params string[] input) =>
|
||||
InternalReactionRoles(true, messageId, input);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
public Task ReactionRoles(ulong messageId, Exclude _, params string[] input)
|
||||
=> InternalReactionRoles(true, messageId, input);
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
public Task ReactionRoles(params string[] input) =>
|
||||
InternalReactionRoles(false, null, input);
|
||||
public Task ReactionRoles(params string[] input)
|
||||
=> InternalReactionRoles(false, null, input);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(1)]
|
||||
public Task ReactionRoles(Exclude _, params string[] input) =>
|
||||
InternalReactionRoles(true, null, input);
|
||||
public Task ReactionRoles(Exclude _, params string[] input)
|
||||
=> InternalReactionRoles(true, null, input);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async Task ReactionRolesList()
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor();
|
||||
if (!_service.Get(ctx.Guild.Id, out var rrs) ||
|
||||
!rrs.Any())
|
||||
var embed = _eb.Create().WithOkColor();
|
||||
if (!_service.Get(ctx.Guild.Id, out var rrs) || !rrs.Any())
|
||||
{
|
||||
embed.WithDescription(GetText(strs.no_reaction_roles));
|
||||
}
|
||||
@@ -149,37 +147,33 @@ public partial class Administration
|
||||
{
|
||||
var ch = g.GetTextChannel(rr.ChannelId);
|
||||
IUserMessage msg = null;
|
||||
if (ch is not null)
|
||||
{
|
||||
msg = await ch.GetMessageAsync(rr.MessageId) as IUserMessage;
|
||||
}
|
||||
if (ch is not null) msg = await ch.GetMessageAsync(rr.MessageId) as IUserMessage;
|
||||
var content = msg?.Content.TrimTo(30) ?? "DELETED!";
|
||||
embed.AddField($"**{rr.Index + 1}.** {ch?.Name ?? "DELETED!"}",
|
||||
GetText(strs.reaction_roles_message(rr.ReactionRoles?.Count ?? 0, content)));
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async Task ReactionRolesRemove(int index)
|
||||
{
|
||||
if (index < 1 ||
|
||||
!_service.Get(ctx.Guild.Id, out var rrs) ||
|
||||
!rrs.Any() || rrs.Count < index)
|
||||
{
|
||||
if (index < 1 || !_service.Get(ctx.Guild.Id, out var rrs) || !rrs.Any() || rrs.Count < index)
|
||||
return;
|
||||
}
|
||||
index--;
|
||||
var rr = rrs[index];
|
||||
_service.Remove(ctx.Guild.Id, index);
|
||||
await ReplyConfirmLocalizedAsync(strs.reaction_role_removed(index + 1));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
@@ -193,9 +187,8 @@ public partial class Administration
|
||||
{
|
||||
await targetUser.AddRoleAsync(roleToAdd);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(
|
||||
strs.setrole(Format.Bold(roleToAdd.Name), Format.Bold(targetUser.ToString()))
|
||||
);
|
||||
await ReplyConfirmLocalizedAsync(strs.setrole(Format.Bold(roleToAdd.Name),
|
||||
Format.Bold(targetUser.ToString())));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -204,19 +197,22 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task RemoveRole(IGuildUser targetUser, [Leftover] IRole roleToRemove)
|
||||
{
|
||||
var runnerUser = (IGuildUser)ctx.User;
|
||||
if (ctx.User.Id != runnerUser.Guild.OwnerId && runnerUser.GetRoles().Max(x => x.Position) <= roleToRemove.Position)
|
||||
if (ctx.User.Id != runnerUser.Guild.OwnerId
|
||||
&& runnerUser.GetRoles().Max(x => x.Position) <= roleToRemove.Position)
|
||||
return;
|
||||
try
|
||||
{
|
||||
await targetUser.RemoveRoleAsync(roleToRemove);
|
||||
await ReplyConfirmLocalizedAsync(strs.remrole(Format.Bold(roleToRemove.Name), Format.Bold(targetUser.ToString())));
|
||||
await ReplyConfirmLocalizedAsync(strs.remrole(Format.Bold(roleToRemove.Name),
|
||||
Format.Bold(targetUser.ToString())));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -224,11 +220,12 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task RenameRole(IRole roleToEdit, [Leftover]string newname)
|
||||
public async Task RenameRole(IRole roleToEdit, [Leftover] string newname)
|
||||
{
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= roleToEdit.Position)
|
||||
@@ -240,6 +237,7 @@ public partial class Administration
|
||||
await ReplyErrorLocalizedAsync(strs.renrole_perms);
|
||||
return;
|
||||
}
|
||||
|
||||
await roleToEdit.ModifyAsync(g => g.Name = newname);
|
||||
await ReplyConfirmLocalizedAsync(strs.renrole);
|
||||
}
|
||||
@@ -249,7 +247,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
@@ -257,11 +256,11 @@ public partial class Administration
|
||||
{
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
|
||||
var userRoles = user.GetRoles()
|
||||
.Where(x => !x.IsManaged && x != x.Guild.EveryoneRole)
|
||||
.ToList();
|
||||
|
||||
if (user.Id == ctx.Guild.OwnerId || (ctx.User.Id != ctx.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position)))
|
||||
var userRoles = user.GetRoles().Where(x => !x.IsManaged && x != x.Guild.EveryoneRole).ToList();
|
||||
|
||||
if (user.Id == ctx.Guild.OwnerId
|
||||
|| (ctx.User.Id != ctx.Guild.OwnerId
|
||||
&& guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position)))
|
||||
return;
|
||||
try
|
||||
{
|
||||
@@ -274,7 +273,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
@@ -287,22 +287,23 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.cr(Format.Bold(r.Name)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task DeleteRole([Leftover] IRole role)
|
||||
{
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
if (ctx.User.Id != guser.Guild.OwnerId
|
||||
&& guser.GetRoles().Max(x => x.Position) <= role.Position)
|
||||
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
|
||||
return;
|
||||
|
||||
await role.DeleteAsync();
|
||||
await ReplyConfirmLocalizedAsync(strs.dr(Format.Bold(role.Name)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
@@ -311,32 +312,30 @@ public partial class Administration
|
||||
var newHoisted = !role.IsHoisted;
|
||||
await role.ModifyAsync(r => r.Hoist = newHoisted);
|
||||
if (newHoisted)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.rolehoist_enabled(Format.Bold(role.Name)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.rolehoist_disabled(Format.Bold(role.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task RoleColor([Leftover] IRole role)
|
||||
=> await SendConfirmAsync("Role Color", role.Color.RawValue.ToString("x6"));
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
public async Task RoleColor(SixLabors.ImageSharp.Color color, [Leftover]IRole role)
|
||||
public async Task RoleColor(Color color, [Leftover] IRole role)
|
||||
{
|
||||
try
|
||||
{
|
||||
var rgba32 = color.ToPixel<Rgba32>();
|
||||
await role.ModifyAsync(r => r.Color = new Color(rgba32.R, rgba32.G, rgba32.B));
|
||||
await role.ModifyAsync(r => r.Color = new Discord.Color(rgba32.R, rgba32.G, rgba32.B));
|
||||
await ReplyConfirmLocalizedAsync(strs.rc(Format.Bold(role.Name)));
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -345,4 +344,4 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#nullable disable
|
||||
using System.Text;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using System.Text;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
@@ -9,7 +9,8 @@ public partial class Administration
|
||||
[Group]
|
||||
public class SelfAssignedRolesCommands : NadekoSubmodule<SelfAssignedRolesService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[BotPerm(GuildPerm.ManageMessages)]
|
||||
@@ -18,24 +19,22 @@ public partial class Administration
|
||||
var newVal = _service.ToggleAdSarm(ctx.Guild.Id);
|
||||
|
||||
if (newVal)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.adsarm_enable(Prefix));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.adsarm_disable(Prefix));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(1)]
|
||||
public Task Asar([Leftover] IRole role) =>
|
||||
Asar(0, role);
|
||||
public Task Asar([Leftover] IRole role)
|
||||
=> Asar(0, role);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
@@ -49,16 +48,14 @@ public partial class Administration
|
||||
var succ = _service.AddNew(ctx.Guild.Id, role, group);
|
||||
|
||||
if (succ)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.role_added(Format.Bold(role.Name), Format.Bold(group.ToString())));
|
||||
}
|
||||
await ReplyConfirmLocalizedAsync(strs.role_added(Format.Bold(role.Name),
|
||||
Format.Bold(group.ToString())));
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.role_in_list(Format.Bold(role.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
@@ -70,16 +67,14 @@ public partial class Administration
|
||||
var set = await _service.SetNameAsync(ctx.Guild.Id, group, name);
|
||||
|
||||
if (set)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.group_name_added(Format.Bold(group.ToString()), Format.Bold(name.ToString())));
|
||||
}
|
||||
await ReplyConfirmLocalizedAsync(
|
||||
strs.group_name_added(Format.Bold(group.ToString()), Format.Bold(name)));
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.group_name_removed(Format.Bold(group.ToString())));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async Task Rsar([Leftover] IRole role)
|
||||
@@ -90,16 +85,13 @@ public partial class Administration
|
||||
|
||||
var success = _service.RemoveSar(role.Guild.Id, role.Id);
|
||||
if (!success)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.self_assign_not);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.self_assign_rem(Format.Bold(role.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Lsar(int page = 1)
|
||||
{
|
||||
@@ -108,57 +100,55 @@ public partial class Administration
|
||||
|
||||
var (exclusive, roles, groups) = _service.GetRoles(ctx.Guild);
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, cur =>
|
||||
{
|
||||
var rolesStr = new StringBuilder();
|
||||
var roleGroups = roles
|
||||
.OrderBy(x => x.Model.Group)
|
||||
.Skip(cur * 20)
|
||||
.Take(20)
|
||||
.GroupBy(x => x.Model.Group)
|
||||
.OrderBy(x => x.Key);
|
||||
|
||||
foreach (var kvp in roleGroups)
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
cur =>
|
||||
{
|
||||
var groupNameText = string.Empty;
|
||||
if (!groups.TryGetValue(kvp.Key, out var name))
|
||||
{
|
||||
groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key)));
|
||||
}
|
||||
else
|
||||
{
|
||||
groupNameText = Format.Bold($"{kvp.Key} - {name.TrimTo(25, true)}");
|
||||
}
|
||||
var rolesStr = new StringBuilder();
|
||||
var roleGroups = roles.OrderBy(x => x.Model.Group)
|
||||
.Skip(cur * 20)
|
||||
.Take(20)
|
||||
.GroupBy(x => x.Model.Group)
|
||||
.OrderBy(x => x.Key);
|
||||
|
||||
rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫");
|
||||
foreach (var (model, role) in kvp.AsEnumerable())
|
||||
foreach (var kvp in roleGroups)
|
||||
{
|
||||
if (role is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var groupNameText = string.Empty;
|
||||
if (!groups.TryGetValue(kvp.Key, out var name))
|
||||
groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key)));
|
||||
else
|
||||
{
|
||||
// first character is invisible space
|
||||
if (model.LevelRequirement == 0)
|
||||
rolesStr.AppendLine(" " + role.Name);
|
||||
else
|
||||
rolesStr.AppendLine(" " + role.Name + $" (lvl {model.LevelRequirement}+)");
|
||||
}
|
||||
}
|
||||
rolesStr.AppendLine();
|
||||
}
|
||||
groupNameText = Format.Bold($"{kvp.Key} - {name.TrimTo(25, true)}");
|
||||
|
||||
return _eb.Create().WithOkColor()
|
||||
.WithTitle(Format.Bold(GetText(strs.self_assign_list(roles.Count()))))
|
||||
.WithDescription(rolesStr.ToString())
|
||||
.WithFooter(exclusive
|
||||
? GetText(strs.self_assign_are_exclusive)
|
||||
: GetText(strs.self_assign_are_not_exclusive));
|
||||
}, roles.Count(), 20);
|
||||
rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫");
|
||||
foreach (var (model, role) in kvp.AsEnumerable())
|
||||
if (role is null)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
// first character is invisible space
|
||||
if (model.LevelRequirement == 0)
|
||||
rolesStr.AppendLine(" " + role.Name);
|
||||
else
|
||||
rolesStr.AppendLine(" " + role.Name + $" (lvl {model.LevelRequirement}+)");
|
||||
}
|
||||
|
||||
rolesStr.AppendLine();
|
||||
}
|
||||
|
||||
return _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(Format.Bold(GetText(strs.self_assign_list(roles.Count()))))
|
||||
.WithDescription(rolesStr.ToString())
|
||||
.WithFooter(exclusive
|
||||
? GetText(strs.self_assign_are_exclusive)
|
||||
: GetText(strs.self_assign_are_not_exclusive));
|
||||
},
|
||||
roles.Count(),
|
||||
20);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
@@ -171,7 +161,8 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.self_assign_no_excl);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
@@ -188,12 +179,12 @@ public partial class Administration
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.self_assign_level_req(
|
||||
Format.Bold(role.Name),
|
||||
await ReplyConfirmLocalizedAsync(strs.self_assign_level_req(Format.Bold(role.Name),
|
||||
Format.Bold(level.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Iam([Leftover] IRole role)
|
||||
{
|
||||
@@ -203,25 +194,15 @@ public partial class Administration
|
||||
|
||||
IUserMessage msg;
|
||||
if (result == SelfAssignedRolesService.AssignResult.Err_Not_Assignable)
|
||||
{
|
||||
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not);
|
||||
}
|
||||
else if (result == SelfAssignedRolesService.AssignResult.Err_Lvl_Req)
|
||||
{
|
||||
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not_level(Format.Bold(extra.ToString())));
|
||||
}
|
||||
else if (result == SelfAssignedRolesService.AssignResult.Err_Already_Have)
|
||||
{
|
||||
msg = await ReplyErrorLocalizedAsync(strs.self_assign_already(Format.Bold(role.Name)));
|
||||
}
|
||||
else if (result == SelfAssignedRolesService.AssignResult.Err_Not_Perms)
|
||||
{
|
||||
msg = await ReplyErrorLocalizedAsync(strs.self_assign_perms);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = await ReplyConfirmLocalizedAsync(strs.self_assign_success(Format.Bold(role.Name)));
|
||||
}
|
||||
|
||||
if (autoDelete)
|
||||
{
|
||||
@@ -230,7 +211,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Iamnot([Leftover] IRole role)
|
||||
{
|
||||
@@ -240,21 +222,13 @@ public partial class Administration
|
||||
|
||||
IUserMessage msg;
|
||||
if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Assignable)
|
||||
{
|
||||
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not);
|
||||
}
|
||||
else if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Have)
|
||||
{
|
||||
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not_have(Format.Bold(role.Name)));
|
||||
}
|
||||
else if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Perms)
|
||||
{
|
||||
msg = await ReplyErrorLocalizedAsync(strs.self_assign_perms);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = await ReplyConfirmLocalizedAsync(strs.self_assign_remove(Format.Bold(role.Name)));
|
||||
}
|
||||
|
||||
if (autoDelete)
|
||||
{
|
||||
@@ -263,4 +237,4 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
@@ -9,6 +9,14 @@ public partial class Administration
|
||||
[Group]
|
||||
public class SelfCommands : NadekoSubmodule<SelfService>
|
||||
{
|
||||
public enum SettableUserStatus
|
||||
{
|
||||
Online,
|
||||
Invisible,
|
||||
Idle,
|
||||
Dnd
|
||||
}
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly ICoordinator _coord;
|
||||
@@ -20,7 +28,8 @@ public partial class Administration
|
||||
_coord = coord;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
@@ -30,7 +39,7 @@ public partial class Administration
|
||||
return;
|
||||
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
var cmd = new AutoCommand()
|
||||
var cmd = new AutoCommand
|
||||
{
|
||||
CommandText = cmdText,
|
||||
ChannelId = ctx.Channel.Id,
|
||||
@@ -39,18 +48,22 @@ public partial class Administration
|
||||
GuildName = ctx.Guild?.Name,
|
||||
VoiceChannelId = guser.VoiceChannel?.Id,
|
||||
VoiceChannelName = guser.VoiceChannel?.Name,
|
||||
Interval = 0,
|
||||
Interval = 0
|
||||
};
|
||||
_service.AddNewAutoCommand(cmd);
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.scadd))
|
||||
.AddField(GetText(strs.server), cmd.GuildId is null ? $"-" : $"{cmd.GuildName}/{cmd.GuildId}", true)
|
||||
.AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true)
|
||||
.AddField(GetText(strs.command_text), cmdText, false));
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.scadd))
|
||||
.AddField(GetText(strs.server),
|
||||
cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}",
|
||||
true)
|
||||
.AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true)
|
||||
.AddField(GetText(strs.command_text), cmdText));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
@@ -63,7 +76,7 @@ public partial class Administration
|
||||
return;
|
||||
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
var cmd = new AutoCommand()
|
||||
var cmd = new AutoCommand
|
||||
{
|
||||
CommandText = cmdText,
|
||||
ChannelId = ctx.Channel.Id,
|
||||
@@ -72,14 +85,15 @@ public partial class Administration
|
||||
GuildName = ctx.Guild?.Name,
|
||||
VoiceChannelId = guser.VoiceChannel?.Id,
|
||||
VoiceChannelName = guser.VoiceChannel?.Name,
|
||||
Interval = interval,
|
||||
Interval = interval
|
||||
};
|
||||
_service.AddNewAutoCommand(cmd);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.autocmd_add(Format.Code(Format.Sanitize(cmdText)), cmd.Interval));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task StartupCommandsList(int page = 1)
|
||||
@@ -87,11 +101,8 @@ public partial class Administration
|
||||
if (page-- < 1)
|
||||
return;
|
||||
|
||||
var scmds = _service.GetStartupCommands()
|
||||
.Skip(page * 5)
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
var scmds = _service.GetStartupCommands().Skip(page * 5).Take(5).ToList();
|
||||
|
||||
if (scmds.Count == 0)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.startcmdlist_none);
|
||||
@@ -99,19 +110,19 @@ public partial class Administration
|
||||
else
|
||||
{
|
||||
var i = 0;
|
||||
await SendConfirmAsync(
|
||||
text: string.Join("\n", scmds
|
||||
.Select(x => $@"```css
|
||||
await SendConfirmAsync(text: string.Join("\n",
|
||||
scmds.Select(x => $@"```css
|
||||
#{++i + (page * 5)}
|
||||
[{GetText(strs.server)}]: {(x.GuildId.HasValue ? $"{x.GuildName} #{x.GuildId}" : "-")}
|
||||
[{GetText(strs.channel)}]: {x.ChannelName} #{x.ChannelId}
|
||||
[{GetText(strs.command_text)}]: {x.CommandText}```")),
|
||||
title: string.Empty,
|
||||
footer: GetText(strs.page(page + 1)));
|
||||
title: string.Empty,
|
||||
footer: GetText(strs.page(page + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task AutoCommandsList(int page = 1)
|
||||
@@ -119,10 +130,7 @@ public partial class Administration
|
||||
if (page-- < 1)
|
||||
return;
|
||||
|
||||
var scmds = _service.GetAutoCommands()
|
||||
.Skip(page * 5)
|
||||
.Take(5)
|
||||
.ToList();
|
||||
var scmds = _service.GetAutoCommands().Skip(page * 5).Take(5).ToList();
|
||||
if (!scmds.Any())
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.autocmdlist_none);
|
||||
@@ -130,23 +138,23 @@ public partial class Administration
|
||||
else
|
||||
{
|
||||
var i = 0;
|
||||
await SendConfirmAsync(
|
||||
text: string.Join("\n", scmds
|
||||
.Select(x => $@"```css
|
||||
await SendConfirmAsync(text: string.Join("\n",
|
||||
scmds.Select(x => $@"```css
|
||||
#{++i + (page * 5)}
|
||||
[{GetText(strs.server)}]: {(x.GuildId.HasValue ? $"{x.GuildName} #{x.GuildId}" : "-")}
|
||||
[{GetText(strs.channel)}]: {x.ChannelName} #{x.ChannelId}
|
||||
{GetIntervalText(x.Interval)}
|
||||
[{GetText(strs.command_text)}]: {x.CommandText}```")),
|
||||
title: string.Empty,
|
||||
footer: GetText(strs.page(page + 1)));
|
||||
title: string.Empty,
|
||||
footer: GetText(strs.page(page + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
private string GetIntervalText(int interval)
|
||||
=> $"[{GetText(strs.interval)}]: {interval}";
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task Wait(int miliseconds)
|
||||
{
|
||||
@@ -162,8 +170,9 @@ public partial class Administration
|
||||
|
||||
await Task.Delay(miliseconds);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
@@ -174,11 +183,12 @@ public partial class Administration
|
||||
await ReplyErrorLocalizedAsync(strs.acrm_fail);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task StartupCommandRemove([Leftover] int index)
|
||||
@@ -189,7 +199,8 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.scrm);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[OwnerOnly]
|
||||
@@ -200,7 +211,8 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.startcmds_cleared);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task ForwardMessages()
|
||||
{
|
||||
@@ -212,7 +224,8 @@ public partial class Administration
|
||||
await ReplyPendingLocalizedAsync(strs.fwdm_stop);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task ForwardToAll()
|
||||
{
|
||||
@@ -222,10 +235,10 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.fwall_start);
|
||||
else
|
||||
await ReplyPendingLocalizedAsync(strs.fwall_stop);
|
||||
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task ShardStats(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
@@ -233,35 +246,36 @@ public partial class Administration
|
||||
|
||||
var statuses = _coord.GetAllShardStatuses();
|
||||
|
||||
var status = string.Join(" : ", statuses
|
||||
.Select(x => (ConnectionStateToEmoji(x), x))
|
||||
.GroupBy(x => x.Item1)
|
||||
.Select(x => $"`{x.Count()} {x.Key}`")
|
||||
.ToArray());
|
||||
var status = string.Join(" : ",
|
||||
statuses.Select(x => (ConnectionStateToEmoji(x), x))
|
||||
.GroupBy(x => x.Item1)
|
||||
.Select(x => $"`{x.Count()} {x.Key}`")
|
||||
.ToArray());
|
||||
|
||||
var allShardStrings = statuses
|
||||
.Select(st =>
|
||||
var allShardStrings = statuses.Select(st =>
|
||||
{
|
||||
var stateStr = ConnectionStateToEmoji(st);
|
||||
var timeDiff = DateTime.UtcNow - st.LastUpdate;
|
||||
var maxGuildCountLength =
|
||||
statuses.Max(x => x.GuildCount).ToString().Length;
|
||||
return $"`{stateStr} "
|
||||
+ $"| #{st.ShardId.ToString().PadBoth(3)} "
|
||||
+ $"| {timeDiff:mm\\:ss} "
|
||||
+ $"| {st.GuildCount.ToString().PadBoth(maxGuildCountLength)} `";
|
||||
})
|
||||
.ToArray();
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
var stateStr = ConnectionStateToEmoji(st);
|
||||
var timeDiff = DateTime.UtcNow - st.LastUpdate;
|
||||
var maxGuildCountLength = statuses.Max(x => x.GuildCount).ToString().Length;
|
||||
return $"`{stateStr} " +
|
||||
$"| #{st.ShardId.ToString().PadBoth(3)} " +
|
||||
$"| {timeDiff:mm\\:ss} " +
|
||||
$"| {st.GuildCount.ToString().PadBoth(maxGuildCountLength)} `";
|
||||
})
|
||||
.ToArray();
|
||||
await ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
var str = string.Join("\n", allShardStrings.Skip(25 * curPage).Take(25));
|
||||
var str = string.Join("\n", allShardStrings.Skip(25 * curPage).Take(25));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
str = GetText(strs.no_shards_on_page);
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
str = GetText(strs.no_shards_on_page);
|
||||
|
||||
return _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription($"{status}\n\n{str}");
|
||||
}, allShardStrings.Length, 25);
|
||||
return _eb.Create().WithOkColor().WithDescription($"{status}\n\n{str}");
|
||||
},
|
||||
allShardStrings.Length,
|
||||
25);
|
||||
}
|
||||
|
||||
private static string ConnectionStateToEmoji(ShardStatus status)
|
||||
@@ -276,28 +290,27 @@ public partial class Administration
|
||||
};
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task RestartShard(int shardId)
|
||||
{
|
||||
var success = _coord.RestartShard(shardId);
|
||||
if (success)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.shard_reconnecting(Format.Bold("#" + shardId)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_shard_id);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public Task Leave([Leftover] string guildStr)
|
||||
=> _service.LeaveGuild(guildStr);
|
||||
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task Die(bool graceful = false)
|
||||
{
|
||||
@@ -309,11 +322,13 @@ public partial class Administration
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
await Task.Delay(2000);
|
||||
_coord.Die(graceful);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task Restart()
|
||||
{
|
||||
@@ -324,10 +339,12 @@ public partial class Administration
|
||||
return;
|
||||
}
|
||||
|
||||
try { await ReplyConfirmLocalizedAsync(strs.restarting); } catch { }
|
||||
try { await ReplyConfirmLocalizedAsync(strs.restarting); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SetName([Leftover] string newName)
|
||||
{
|
||||
@@ -346,7 +363,8 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.bot_name(Format.Bold(newName)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[UserPerm(GuildPerm.ManageNicknames)]
|
||||
[BotPerm(GuildPerm.ChangeNickname)]
|
||||
[Priority(0)]
|
||||
@@ -360,26 +378,28 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.bot_nick(Format.Bold(newNick) ?? "-"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[BotPerm(GuildPerm.ManageNicknames)]
|
||||
[UserPerm(GuildPerm.ManageNicknames)]
|
||||
[Priority(1)]
|
||||
public async Task SetNick(IGuildUser gu, [Leftover] string newNick = null)
|
||||
{
|
||||
var sg = (SocketGuild) ctx.Guild;
|
||||
if (sg.OwnerId == gu.Id ||
|
||||
gu.GetRoles().Max(r => r.Position) >= sg.CurrentUser.GetRoles().Max(r => r.Position))
|
||||
var sg = (SocketGuild)ctx.Guild;
|
||||
if (sg.OwnerId == gu.Id
|
||||
|| gu.GetRoles().Max(r => r.Position) >= sg.CurrentUser.GetRoles().Max(r => r.Position))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.insuf_perms_i);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await gu.ModifyAsync(u => u.Nickname = newNick);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.user_nick(Format.Bold(gu.ToString()), Format.Bold(newNick) ?? "-"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SetStatus([Leftover] SettableUserStatus status)
|
||||
{
|
||||
@@ -388,32 +408,30 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.bot_status(Format.Bold(status.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SetAvatar([Leftover] string img = null)
|
||||
{
|
||||
var success = await _service.SetAvatar(img);
|
||||
|
||||
if (success)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.set_avatar);
|
||||
}
|
||||
if (success) await ReplyConfirmLocalizedAsync(strs.set_avatar);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SetGame(ActivityType type, [Leftover] string game = null)
|
||||
{
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(Context)
|
||||
.Build();
|
||||
var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
||||
|
||||
await _service.SetGameAsync(game is null ? game : rep.Replace(game), type);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.set_game);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SetStream(string url, [Leftover] string name = null)
|
||||
{
|
||||
@@ -424,23 +442,22 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.set_stream);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task Send(string where, [Leftover] SmartText text = null)
|
||||
{
|
||||
var ids = where.Split('|');
|
||||
if (ids.Length != 2)
|
||||
return;
|
||||
|
||||
|
||||
var sid = ulong.Parse(ids[0]);
|
||||
var server = _client.Guilds.FirstOrDefault(s => s.Id == sid);
|
||||
|
||||
if (server is null)
|
||||
return;
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(Context)
|
||||
.Build();
|
||||
var rep = new ReplacementBuilder().WithDefault(Context).Build();
|
||||
|
||||
if (ids[1].ToUpperInvariant().StartsWith("C:", StringComparison.InvariantCulture))
|
||||
{
|
||||
@@ -450,7 +467,7 @@ public partial class Administration
|
||||
return;
|
||||
|
||||
text = rep.Replace(text);
|
||||
await ch.SendAsync(text, sanitizeAll: false);
|
||||
await ch.SendAsync(text);
|
||||
}
|
||||
else if (ids[1].ToUpperInvariant().StartsWith("U:", StringComparison.InvariantCulture))
|
||||
{
|
||||
@@ -468,27 +485,30 @@ public partial class Administration
|
||||
await ReplyErrorLocalizedAsync(strs.invalid_format);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.message_sent);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task ImagesReload()
|
||||
{
|
||||
await _service.ReloadImagesAsync();
|
||||
await ReplyConfirmLocalizedAsync(strs.images_loading);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task StringsReload()
|
||||
{
|
||||
_strings.Reload();
|
||||
await ReplyConfirmLocalizedAsync(strs.bot_strings_reloaded);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task CoordReload()
|
||||
{
|
||||
@@ -512,13 +532,5 @@ public partial class Administration
|
||||
|
||||
return UserStatus.Online;
|
||||
}
|
||||
|
||||
public enum SettableUserStatus
|
||||
{
|
||||
Online,
|
||||
Invisible,
|
||||
Idle,
|
||||
Dnd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
@@ -13,19 +13,20 @@ public class AdministrationService : INService
|
||||
private readonly DbService _db;
|
||||
private readonly ILogCommandService _logService;
|
||||
|
||||
public AdministrationService(Bot bot, CommandHandler cmdHandler, DbService db, ILogCommandService logService)
|
||||
public AdministrationService(
|
||||
Bot bot,
|
||||
CommandHandler cmdHandler,
|
||||
DbService db,
|
||||
ILogCommandService logService)
|
||||
{
|
||||
_db = db;
|
||||
_logService = logService;
|
||||
|
||||
DeleteMessagesOnCommand = new(bot.AllGuildConfigs
|
||||
.Where(g => g.DeleteMessageOnCommand)
|
||||
.Select(g => g.GuildId));
|
||||
DeleteMessagesOnCommand = new(bot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
|
||||
|
||||
DeleteMessagesOnCommandChannels = new(bot.AllGuildConfigs
|
||||
.SelectMany(x => x.DelMsgOnCmdChannels)
|
||||
.ToDictionary(x => x.ChannelId, x => x.State)
|
||||
.ToConcurrent());
|
||||
DeleteMessagesOnCommandChannels = new(bot.AllGuildConfigs.SelectMany(x => x.DelMsgOnCmdChannels)
|
||||
.ToDictionary(x => x.ChannelId, x => x.State)
|
||||
.ToConcurrent());
|
||||
|
||||
cmdHandler.CommandExecuted += DelMsgOnCmd_Handler;
|
||||
}
|
||||
@@ -33,8 +34,7 @@ public class AdministrationService : INService
|
||||
public (bool DelMsgOnCmd, IEnumerable<DelMsgOnCmdChannel> channels) GetDelMsgOnCmdData(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.DelMsgOnCmdChannels));
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.DelMsgOnCmdChannels));
|
||||
|
||||
return (conf.DeleteMessageOnCommand, conf.DelMsgOnCmdChannels);
|
||||
}
|
||||
@@ -52,14 +52,16 @@ public class AdministrationService : INService
|
||||
if (state && cmd.Name != "prune" && cmd.Name != "pick")
|
||||
{
|
||||
_logService.AddDeleteIgnore(msg.Id);
|
||||
try { await msg.DeleteAsync(); } catch { }
|
||||
try { await msg.DeleteAsync(); }
|
||||
catch { }
|
||||
}
|
||||
//if state is false, that means do not do it
|
||||
}
|
||||
else if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune" && cmd.Name != "pick")
|
||||
{
|
||||
_logService.AddDeleteIgnore(msg.Id);
|
||||
try { await msg.DeleteAsync(); } catch { }
|
||||
try { await msg.DeleteAsync(); }
|
||||
catch { }
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
@@ -80,8 +82,7 @@ public class AdministrationService : INService
|
||||
{
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var conf = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.DelMsgOnCmdChannels));
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.DelMsgOnCmdChannels));
|
||||
|
||||
var old = conf.DelMsgOnCmdChannels.FirstOrDefault(x => x.ChannelId == chId);
|
||||
if (newState == Administration.State.Inherit)
|
||||
@@ -125,7 +126,6 @@ public class AdministrationService : INService
|
||||
if (!users.Any())
|
||||
return;
|
||||
foreach (var u in users)
|
||||
{
|
||||
try
|
||||
{
|
||||
await u.ModifyAsync(usr => usr.Deaf = value);
|
||||
@@ -134,23 +134,24 @@ public class AdministrationService : INService
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EditMessage(ICommandContext context, ITextChannel chanl, ulong messageId, string input)
|
||||
public async Task EditMessage(
|
||||
ICommandContext context,
|
||||
ITextChannel chanl,
|
||||
ulong messageId,
|
||||
string input)
|
||||
{
|
||||
var msg = await chanl.GetMessageAsync(messageId);
|
||||
|
||||
if (msg is not IUserMessage umsg || msg.Author.Id != context.Client.CurrentUser.Id)
|
||||
return;
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(context)
|
||||
.Build();
|
||||
var rep = new ReplacementBuilder().WithDefault(context).Build();
|
||||
|
||||
var text = SmartText.CreateFrom(input);
|
||||
text = rep.Replace(text);
|
||||
|
||||
await umsg.EditAsync(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
#nullable disable
|
||||
using System.Threading.Channels;
|
||||
using LinqToDB;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Net;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
@@ -18,9 +19,7 @@ public sealed class AutoAssignRoleService : INService
|
||||
private readonly Channel<SocketGuildUser> _assignQueue = Channel.CreateBounded<SocketGuildUser>(
|
||||
new BoundedChannelOptions(100)
|
||||
{
|
||||
FullMode = BoundedChannelFullMode.DropOldest,
|
||||
SingleReader = true,
|
||||
SingleWriter = false,
|
||||
FullMode = BoundedChannelFullMode.DropOldest, SingleReader = true, SingleWriter = false
|
||||
});
|
||||
|
||||
public AutoAssignRoleService(DiscordSocketClient client, Bot bot, DbService db)
|
||||
@@ -28,10 +27,10 @@ public sealed class AutoAssignRoleService : INService
|
||||
_client = client;
|
||||
_db = db;
|
||||
|
||||
_autoAssignableRoles = bot.AllGuildConfigs
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.AutoAssignRoleIds))
|
||||
.ToDictionary<GuildConfig, ulong, IReadOnlyList<ulong>>(k => k.GuildId, v => v.GetAutoAssignableRoles())
|
||||
.ToConcurrent();
|
||||
_autoAssignableRoles = bot.AllGuildConfigs.Where(x => !string.IsNullOrWhiteSpace(x.AutoAssignRoleIds))
|
||||
.ToDictionary<GuildConfig, ulong, IReadOnlyList<ulong>>(k => k.GuildId,
|
||||
v => v.GetAutoAssignableRoles())
|
||||
.ToConcurrent();
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
@@ -40,14 +39,13 @@ public sealed class AutoAssignRoleService : INService
|
||||
var user = await _assignQueue.Reader.ReadAsync();
|
||||
if (!_autoAssignableRoles.TryGetValue(user.Guild.Id, out var savedRoleIds))
|
||||
continue;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var roleIds = savedRoleIds
|
||||
.Select(roleId => user.Guild.GetRole(roleId))
|
||||
.Where(x => x is not null)
|
||||
.ToList();
|
||||
|
||||
var roleIds = savedRoleIds.Select(roleId => user.Guild.GetRole(roleId))
|
||||
.Where(x => x is not null)
|
||||
.ToList();
|
||||
|
||||
if (roleIds.Any())
|
||||
{
|
||||
await user.AddRolesAsync(roleIds);
|
||||
@@ -59,16 +57,17 @@ public sealed class AutoAssignRoleService : INService
|
||||
"Disabled 'Auto assign role' feature on {GuildName} [{GuildId}] server the roles dont exist",
|
||||
user.Guild.Name,
|
||||
user.Guild.Id);
|
||||
|
||||
|
||||
await DisableAarAsync(user.Guild.Id);
|
||||
}
|
||||
}
|
||||
catch (Discord.Net.HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
Log.Warning("Disabled 'Auto assign role' feature on {GuildName} [{GuildId}] server because I don't have role management permissions",
|
||||
Log.Warning(
|
||||
"Disabled 'Auto assign role' feature on {GuildName} [{GuildId}] server because I don't have role management permissions",
|
||||
user.Guild.Name,
|
||||
user.Guild.Id);
|
||||
|
||||
|
||||
await DisableAarAsync(user.Guild.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -84,16 +83,13 @@ public sealed class AutoAssignRoleService : INService
|
||||
|
||||
private async Task OnClientRoleDeleted(SocketRole role)
|
||||
{
|
||||
if (_autoAssignableRoles.TryGetValue(role.Guild.Id, out var roles)
|
||||
&& roles.Contains(role.Id))
|
||||
{
|
||||
if (_autoAssignableRoles.TryGetValue(role.Guild.Id, out var roles) && roles.Contains(role.Id))
|
||||
await ToggleAarAsync(role.Guild.Id, role.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnClientOnUserJoined(SocketGuildUser user)
|
||||
{
|
||||
if (_autoAssignableRoles.TryGetValue(user.Guild.Id, out _))
|
||||
if (_autoAssignableRoles.TryGetValue(user.Guild.Id, out _))
|
||||
await _assignQueue.Writer.WriteAsync(user);
|
||||
}
|
||||
|
||||
@@ -102,42 +98,40 @@ public sealed class AutoAssignRoleService : INService
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set);
|
||||
var roles = gc.GetAutoAssignableRoles();
|
||||
if(!roles.Remove(roleId) && roles.Count < 3)
|
||||
if (!roles.Remove(roleId) && roles.Count < 3)
|
||||
roles.Add(roleId);
|
||||
|
||||
|
||||
gc.SetAutoAssignableRoles(roles);
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
|
||||
if (roles.Count > 0)
|
||||
_autoAssignableRoles[guildId] = roles;
|
||||
else
|
||||
_autoAssignableRoles.TryRemove(guildId, out _);
|
||||
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
public async Task DisableAarAsync(ulong guildId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
await uow
|
||||
.GuildConfigs
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.UpdateAsync(_ => new(){ AutoAssignRoleIds = null});
|
||||
|
||||
|
||||
await uow.GuildConfigs.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.UpdateAsync(_ => new() { AutoAssignRoleIds = null });
|
||||
|
||||
_autoAssignableRoles.TryRemove(guildId, out _);
|
||||
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task SetAarRolesAsync(ulong guildId, IEnumerable<ulong> newRoles)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set);
|
||||
gc.SetAutoAssignableRoles(newRoles);
|
||||
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
@@ -157,4 +151,4 @@ public static class GuildConfigExtensions
|
||||
|
||||
public static void SetAutoAssignableRoles(this GuildConfig gc, IEnumerable<ulong> roles)
|
||||
=> gc.AutoAssignRoleIds = roles.Join(',');
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
@@ -11,12 +11,18 @@ public class DangerousCommandsService : INService
|
||||
public const string WaifusDeleteSql = @"DELETE FROM WaifuUpdates;
|
||||
DELETE FROM WaifuItem;
|
||||
DELETE FROM WaifuInfo;";
|
||||
public const string WaifuDeleteSql = @"DELETE FROM WaifuUpdates WHERE UserId=(SELECT Id FROM DiscordUser WHERE UserId={0});
|
||||
|
||||
public const string WaifuDeleteSql =
|
||||
@"DELETE FROM WaifuUpdates WHERE UserId=(SELECT Id FROM DiscordUser WHERE UserId={0});
|
||||
DELETE FROM WaifuItem WHERE WaifuInfoId=(SELECT Id FROM WaifuInfo WHERE WaifuId=(SELECT Id FROM DiscordUser WHERE UserId={0}));
|
||||
UPDATE WaifuInfo SET ClaimerId=NULL WHERE ClaimerId=(SELECT Id FROM DiscordUser WHERE UserId={0});
|
||||
DELETE FROM WaifuInfo WHERE WaifuId=(SELECT Id FROM DiscordUser WHERE UserId={0});";
|
||||
public const string CurrencyDeleteSql = "UPDATE DiscordUser SET CurrencyAmount=0; DELETE FROM CurrencyTransactions; DELETE FROM PlantedCurrency;";
|
||||
|
||||
public const string CurrencyDeleteSql =
|
||||
"UPDATE DiscordUser SET CurrencyAmount=0; DELETE FROM CurrencyTransactions; DELETE FROM PlantedCurrency;";
|
||||
|
||||
public const string MusicPlaylistDeleteSql = "DELETE FROM MusicPlaylists;";
|
||||
|
||||
public const string XpDeleteSql = @"DELETE FROM UserXpStats;
|
||||
UPDATE DiscordUser
|
||||
SET ClubId=NULL,
|
||||
@@ -44,19 +50,9 @@ DELETE FROM Clubs;";
|
||||
return res;
|
||||
}
|
||||
|
||||
public class SelectResult
|
||||
{
|
||||
public List<string> ColumnNames { get; set; }
|
||||
public List<string[]> Results { get; set; }
|
||||
}
|
||||
|
||||
public SelectResult SelectSql(string sql)
|
||||
{
|
||||
var result = new SelectResult()
|
||||
{
|
||||
ColumnNames = new(),
|
||||
Results = new(),
|
||||
};
|
||||
var result = new SelectResult { ColumnNames = new(), Results = new() };
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var conn = uow.Database.GetDbConnection();
|
||||
@@ -65,10 +61,7 @@ DELETE FROM Clubs;";
|
||||
using var reader = cmd.ExecuteReader();
|
||||
if (reader.HasRows)
|
||||
{
|
||||
for (var i = 0; i < reader.FieldCount; i++)
|
||||
{
|
||||
result.ColumnNames.Add(reader.GetName(i));
|
||||
}
|
||||
for (var i = 0; i < reader.FieldCount; i++) result.ColumnNames.Add(reader.GetName(i));
|
||||
while (reader.Read())
|
||||
{
|
||||
var obj = new object[reader.FieldCount];
|
||||
@@ -83,50 +76,45 @@ DELETE FROM Clubs;";
|
||||
public async Task PurgeUserAsync(ulong userId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
|
||||
// get waifu info
|
||||
var wi = await uow.Set<WaifuInfo>()
|
||||
.FirstOrDefaultAsyncEF(x => x.Waifu.UserId == userId);
|
||||
var wi = await uow.Set<WaifuInfo>().FirstOrDefaultAsyncEF(x => x.Waifu.UserId == userId);
|
||||
|
||||
// if it exists, delete waifu related things
|
||||
if (wi is not null)
|
||||
{
|
||||
// remove updates which have new or old as this waifu
|
||||
await uow
|
||||
.WaifuUpdates
|
||||
.DeleteAsync(wu => wu.New.UserId == userId || wu.Old.UserId == userId);
|
||||
await uow.WaifuUpdates.DeleteAsync(wu => wu.New.UserId == userId || wu.Old.UserId == userId);
|
||||
|
||||
// delete all items this waifu owns
|
||||
await uow
|
||||
.Set<WaifuItem>()
|
||||
.DeleteAsync(x => x.WaifuInfoId == wi.Id);
|
||||
await uow.Set<WaifuItem>().DeleteAsync(x => x.WaifuInfoId == wi.Id);
|
||||
|
||||
// all waifus this waifu claims are released
|
||||
await uow
|
||||
.Set<WaifuInfo>()
|
||||
.AsQueryable()
|
||||
.Where(x => x.Claimer.UserId == userId)
|
||||
.UpdateAsync(x => new() {ClaimerId = null});
|
||||
|
||||
await uow.Set<WaifuInfo>()
|
||||
.AsQueryable()
|
||||
.Where(x => x.Claimer.UserId == userId)
|
||||
.UpdateAsync(x => new() { ClaimerId = null });
|
||||
|
||||
// all affinities set to this waifu are reset
|
||||
await uow
|
||||
.Set<WaifuInfo>()
|
||||
.AsQueryable()
|
||||
.Where(x => x.Affinity.UserId == userId)
|
||||
.UpdateAsync(x => new() {AffinityId = null});
|
||||
await uow.Set<WaifuInfo>()
|
||||
.AsQueryable()
|
||||
.Where(x => x.Affinity.UserId == userId)
|
||||
.UpdateAsync(x => new() { AffinityId = null });
|
||||
}
|
||||
|
||||
// delete guild xp
|
||||
await uow
|
||||
.UserXpStats
|
||||
.DeleteAsync(x => x.UserId == userId);
|
||||
|
||||
await uow.UserXpStats.DeleteAsync(x => x.UserId == userId);
|
||||
|
||||
// delete currency transactions
|
||||
await uow.Set<CurrencyTransaction>()
|
||||
.DeleteAsync(x => x.UserId == userId);
|
||||
|
||||
await uow.Set<CurrencyTransaction>().DeleteAsync(x => x.UserId == userId);
|
||||
|
||||
// delete user, currency, and clubs go away with it
|
||||
await uow.DiscordUser
|
||||
.DeleteAsync(u => u.UserId == userId);
|
||||
await uow.DiscordUser.DeleteAsync(u => u.UserId == userId);
|
||||
}
|
||||
}
|
||||
|
||||
public class SelectResult
|
||||
{
|
||||
public List<string> ColumnNames { get; set; }
|
||||
public List<string[]> Results { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,10 @@ namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public class DiscordPermOverrideService : INService, ILateBlocker
|
||||
{
|
||||
public int Priority { get; } = int.MaxValue;
|
||||
private readonly DbService _db;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public int Priority { get; } = int.MaxValue;
|
||||
|
||||
private readonly ConcurrentDictionary<(ulong, string), DiscordPermOverride> _overrides;
|
||||
|
||||
public DiscordPermOverrideService(DbService db, IServiceProvider services)
|
||||
@@ -19,11 +18,10 @@ public class DiscordPermOverrideService : INService, ILateBlocker
|
||||
_db = db;
|
||||
_services = services;
|
||||
using var uow = _db.GetDbContext();
|
||||
_overrides = uow.DiscordPermOverrides
|
||||
.AsNoTracking()
|
||||
.AsEnumerable()
|
||||
.ToDictionary(o => (o.GuildId ?? 0, o.Command), o => o)
|
||||
.ToConcurrent();
|
||||
_overrides = uow.DiscordPermOverrides.AsNoTracking()
|
||||
.AsEnumerable()
|
||||
.ToDictionary(o => (o.GuildId ?? 0, o.Command), o => o)
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
public bool TryGetOverrides(ulong guildId, string commandName, out GuildPerm? perm)
|
||||
@@ -39,8 +37,11 @@ public class DiscordPermOverrideService : INService, ILateBlocker
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task<PreconditionResult> ExecuteOverrides(ICommandContext ctx, CommandInfo command,
|
||||
GuildPerm perms, IServiceProvider services)
|
||||
public Task<PreconditionResult> ExecuteOverrides(
|
||||
ICommandContext ctx,
|
||||
CommandInfo command,
|
||||
GuildPerm perms,
|
||||
IServiceProvider services)
|
||||
{
|
||||
var rupa = new RequireUserPermissionAttribute(perms);
|
||||
return rupa.CheckPermissionsAsync(ctx, command, services);
|
||||
@@ -50,20 +51,14 @@ public class DiscordPermOverrideService : INService, ILateBlocker
|
||||
{
|
||||
commandName = commandName.ToLowerInvariant();
|
||||
await using var uow = _db.GetDbContext();
|
||||
var over = await uow
|
||||
.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command);
|
||||
var over = await uow.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command);
|
||||
|
||||
if (over is null)
|
||||
{
|
||||
uow.Set<DiscordPermOverride>()
|
||||
.Add(over = new() { Command = commandName, Perm = perm, GuildId = guildId, });
|
||||
}
|
||||
uow.Set<DiscordPermOverride>().Add(over = new() { Command = commandName, Perm = perm, GuildId = guildId });
|
||||
else
|
||||
{
|
||||
over.Perm = perm;
|
||||
}
|
||||
|
||||
_overrides[(guildId, commandName)] = over;
|
||||
|
||||
@@ -73,20 +68,16 @@ public class DiscordPermOverrideService : INService, ILateBlocker
|
||||
public async Task ClearAllOverrides(ulong guildId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var overrides = await uow
|
||||
.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.ToListAsync();
|
||||
var overrides = await uow.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.ToListAsync();
|
||||
|
||||
uow.RemoveRange(overrides);
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
foreach (var over in overrides)
|
||||
{
|
||||
_overrides.TryRemove((guildId, over.Command), out _);
|
||||
}
|
||||
foreach (var over in overrides) _overrides.TryRemove((guildId, over.Command), out _);
|
||||
}
|
||||
|
||||
public async Task RemoveOverride(ulong guildId, string commandName)
|
||||
@@ -94,11 +85,10 @@ public class DiscordPermOverrideService : INService, ILateBlocker
|
||||
commandName = commandName.ToLowerInvariant();
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var over = await uow
|
||||
.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName);
|
||||
var over = await uow.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName);
|
||||
|
||||
if (over is null)
|
||||
return;
|
||||
@@ -112,24 +102,25 @@ public class DiscordPermOverrideService : INService, ILateBlocker
|
||||
public async Task<List<DiscordPermOverride>> GetAllOverrides(ulong guildId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
return await uow
|
||||
.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.ToListAsync();
|
||||
return await uow.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command)
|
||||
{
|
||||
if (TryGetOverrides(context.Guild?.Id ?? 0, command.Name, out var perm) && perm is not null)
|
||||
{
|
||||
var result = await new RequireUserPermissionAttribute((GuildPermission)perm)
|
||||
.CheckPermissionsAsync(context, command, _services);
|
||||
var result =
|
||||
await new RequireUserPermissionAttribute((GuildPermission)perm).CheckPermissionsAsync(context,
|
||||
command,
|
||||
_services);
|
||||
|
||||
return !result.IsSuccess;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,8 @@ public class GameVoiceChannelService : INService
|
||||
_db = db;
|
||||
_client = client;
|
||||
|
||||
GameVoiceChannels = new(
|
||||
bot.AllGuildConfigs.Where(gc => gc.GameVoiceChannel != null)
|
||||
.Select(gc => gc.GameVoiceChannel.Value));
|
||||
GameVoiceChannels = new(bot.AllGuildConfigs.Where(gc => gc.GameVoiceChannel != null)
|
||||
.Select(gc => gc.GameVoiceChannel.Value));
|
||||
|
||||
_client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
|
||||
_client.GuildMemberUpdated += _client_GuildMemberUpdated;
|
||||
@@ -39,11 +38,8 @@ public class GameVoiceChannelService : INService
|
||||
var oldActivity = before.Value.Activities.FirstOrDefault();
|
||||
var newActivity = after.Activities.FirstOrDefault();
|
||||
if (oldActivity != newActivity && newActivity is { Type: ActivityType.Playing })
|
||||
{
|
||||
//trigger gvc
|
||||
await TriggerGvc(after, newActivity.Name);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -87,12 +83,10 @@ public class GameVoiceChannelService : INService
|
||||
|
||||
var game = gUser.Activities.FirstOrDefault()?.Name;
|
||||
|
||||
if (oldState.VoiceChannel == newState.VoiceChannel ||
|
||||
newState.VoiceChannel is null)
|
||||
if (oldState.VoiceChannel == newState.VoiceChannel || newState.VoiceChannel is null)
|
||||
return;
|
||||
|
||||
if (!GameVoiceChannels.Contains(newState.VoiceChannel.Id) ||
|
||||
string.IsNullOrWhiteSpace(game))
|
||||
if (!GameVoiceChannels.Contains(newState.VoiceChannel.Id) || string.IsNullOrWhiteSpace(game))
|
||||
return;
|
||||
|
||||
await TriggerGvc(gUser, game);
|
||||
@@ -112,8 +106,7 @@ public class GameVoiceChannelService : INService
|
||||
return;
|
||||
|
||||
game = game.TrimTo(50).ToLowerInvariant();
|
||||
var vch = gUser.Guild.VoiceChannels
|
||||
.FirstOrDefault(x => x.Name.ToLowerInvariant() == game);
|
||||
var vch = gUser.Guild.VoiceChannels.FirstOrDefault(x => x.Name.ToLowerInvariant() == game);
|
||||
|
||||
if (vch is null)
|
||||
return;
|
||||
@@ -121,4 +114,4 @@ public class GameVoiceChannelService : INService
|
||||
await Task.Delay(1000);
|
||||
await gUser.ModifyAsync(gu => gu.Channel = vch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
@@ -12,11 +12,10 @@ public class GuildTimezoneService : INService
|
||||
|
||||
public GuildTimezoneService(DiscordSocketClient client, Bot bot, DbService db)
|
||||
{
|
||||
_timezones = bot.AllGuildConfigs
|
||||
.Select(GetTimzezoneTuple)
|
||||
.Where(x => x.Timezone != null)
|
||||
.ToDictionary(x => x.GuildId, x => x.Timezone)
|
||||
.ToConcurrent();
|
||||
_timezones = bot.AllGuildConfigs.Select(GetTimzezoneTuple)
|
||||
.Where(x => x.Timezone != null)
|
||||
.ToDictionary(x => x.GuildId, x => x.Timezone)
|
||||
.ToConcurrent();
|
||||
|
||||
var curUser = client.CurrentUser;
|
||||
if (curUser != null)
|
||||
@@ -48,6 +47,7 @@ public class GuildTimezoneService : INService
|
||||
{
|
||||
tz = null;
|
||||
}
|
||||
|
||||
return (x.GuildId, Timezone: tz);
|
||||
}
|
||||
|
||||
@@ -74,4 +74,4 @@ public class GuildTimezoneService : INService
|
||||
|
||||
public TimeZoneInfo GetTimeZoneOrUtc(ulong guildId)
|
||||
=> GetTimeZoneOrDefault(guildId) ?? TimeZoneInfo.Utc;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
#nullable disable
|
||||
using System.Net;
|
||||
using System.Threading.Channels;
|
||||
using LinqToDB;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using System.Net;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public sealed class ImageOnlyChannelService : IEarlyBehavior
|
||||
{
|
||||
public int Priority { get; } = 0;
|
||||
private readonly IMemoryCache _ticketCache;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _enabledOn;
|
||||
|
||||
private readonly Channel<IUserMessage> _deleteQueue = Channel.CreateBounded<IUserMessage>(new BoundedChannelOptions(100)
|
||||
{
|
||||
FullMode = BoundedChannelFullMode.DropOldest,
|
||||
SingleReader = true,
|
||||
SingleWriter = false,
|
||||
});
|
||||
private readonly Channel<IUserMessage> _deleteQueue = Channel.CreateBounded<IUserMessage>(
|
||||
new BoundedChannelOptions(100)
|
||||
{
|
||||
FullMode = BoundedChannelFullMode.DropOldest, SingleReader = true, SingleWriter = false
|
||||
});
|
||||
|
||||
|
||||
public ImageOnlyChannelService(IMemoryCache ticketCache, DiscordSocketClient client, DbService db)
|
||||
@@ -29,14 +29,13 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior
|
||||
_db = db;
|
||||
|
||||
var uow = _db.GetDbContext();
|
||||
_enabledOn = uow.ImageOnlyChannels
|
||||
.ToList()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key, x => new ConcurrentHashSet<ulong>(x.Select(x => x.ChannelId)))
|
||||
.ToConcurrent();
|
||||
|
||||
_enabledOn = uow.ImageOnlyChannels.ToList()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key, x => new ConcurrentHashSet<ulong>(x.Select(x => x.ChannelId)))
|
||||
.ToConcurrent();
|
||||
|
||||
_ = Task.Run(DeleteQueueRunner);
|
||||
|
||||
|
||||
_client.ChannelDestroyed += ClientOnChannelDestroyed;
|
||||
}
|
||||
|
||||
@@ -73,25 +72,19 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior
|
||||
{
|
||||
var newState = false;
|
||||
using var uow = _db.GetDbContext();
|
||||
if (forceDisable
|
||||
|| (_enabledOn.TryGetValue(guildId, out var channels)
|
||||
&& channels.TryRemove(channelId)))
|
||||
if (forceDisable || (_enabledOn.TryGetValue(guildId, out var channels) && channels.TryRemove(channelId)))
|
||||
{
|
||||
uow.ImageOnlyChannels.Delete(x => x.ChannelId == channelId);
|
||||
}
|
||||
else
|
||||
{
|
||||
uow.ImageOnlyChannels.Add(new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId
|
||||
});
|
||||
uow.ImageOnlyChannels.Add(new() { GuildId = guildId, ChannelId = channelId });
|
||||
|
||||
channels = _enabledOn.GetOrAdd(guildId, new ConcurrentHashSet<ulong>());
|
||||
channels.Add(channelId);
|
||||
newState = true;
|
||||
}
|
||||
|
||||
|
||||
uow.SaveChanges();
|
||||
return newState;
|
||||
}
|
||||
@@ -100,20 +93,19 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior
|
||||
{
|
||||
if (msg.Channel is not ITextChannel tch)
|
||||
return false;
|
||||
|
||||
|
||||
if (msg.Attachments.Any(x => x is { Height: > 0, Width: > 0 }))
|
||||
return false;
|
||||
|
||||
if (!_enabledOn.TryGetValue(tch.GuildId, out var chs)
|
||||
|| !chs.Contains(msg.Channel.Id))
|
||||
if (!_enabledOn.TryGetValue(tch.GuildId, out var chs) || !chs.Contains(msg.Channel.Id))
|
||||
return false;
|
||||
|
||||
var user = await tch.Guild.GetUserAsync(msg.Author.Id)
|
||||
?? await _client.Rest.GetGuildUserAsync(tch.GuildId, msg.Author.Id);
|
||||
|
||||
|
||||
if (user is null)
|
||||
return false;
|
||||
|
||||
|
||||
// ignore owner and admin
|
||||
if (user.Id == tch.Guild.OwnerId || user.GuildPermissions.Administrator)
|
||||
{
|
||||
@@ -129,7 +121,8 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior
|
||||
// can't modify channel perms if not admin apparently
|
||||
if (!botUser.GuildPermissions.ManageGuild)
|
||||
{
|
||||
ToggleImageOnlyChannel( tch.GuildId, tch.Id, true);;
|
||||
ToggleImageOnlyChannel(tch.GuildId, tch.Id, true);
|
||||
;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -142,16 +135,14 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior
|
||||
msg.Author.Id,
|
||||
msg.Channel.Id);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
await _deleteQueue.Writer.WriteAsync(msg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error deleting message {MessageId} in image-only channel {ChannelId}.",
|
||||
msg.Id,
|
||||
tch.Id);
|
||||
Log.Error(ex, "Error deleting message {MessageId} in image-only channel {ChannelId}.", msg.Id, tch.Id);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -159,18 +150,17 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior
|
||||
|
||||
private bool AddUserTicket(ulong guildId, ulong userId)
|
||||
{
|
||||
var old = _ticketCache.GetOrCreate($"{guildId}_{userId}", entry =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1);
|
||||
return 0;
|
||||
});
|
||||
var old = _ticketCache.GetOrCreate($"{guildId}_{userId}",
|
||||
entry =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1);
|
||||
return 0;
|
||||
});
|
||||
|
||||
_ticketCache.Set($"{guildId}_{userId}", ++old);
|
||||
|
||||
|
||||
// if this is the third time that the user posts a
|
||||
// non image in an image-only channel on this server
|
||||
return old > 2;
|
||||
}
|
||||
|
||||
public int Priority { get; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Administration.Common;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
@@ -15,7 +15,7 @@ public interface ILogCommandService
|
||||
LogSetting GetGuildLogSettings(ulong guildId);
|
||||
bool Log(ulong guildId, ulong? channelId, LogType type);
|
||||
}
|
||||
|
||||
|
||||
public sealed class DummyLogCommandService : ILogCommandService
|
||||
{
|
||||
public void AddDeleteIgnore(ulong xId)
|
||||
@@ -34,14 +34,13 @@ public sealed class DummyLogCommandService : ILogCommandService
|
||||
public bool Log(ulong guildId, ulong? channelId, LogType type)
|
||||
=> false;
|
||||
}
|
||||
|
||||
|
||||
public sealed class LogCommandService : ILogCommandService
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public ConcurrentDictionary<ulong, LogSetting> GuildLogSettings { get; }
|
||||
|
||||
private ConcurrentDictionary<ITextChannel, List<string>> PresenceUpdates { get; } = new();
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
private readonly Timer _timerReference;
|
||||
private readonly IBotStrings _strings;
|
||||
@@ -51,13 +50,19 @@ public sealed class LogCommandService : ILogCommandService
|
||||
private readonly GuildTimezoneService _tz;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
|
||||
|
||||
private readonly Timer _clearTimer;
|
||||
private readonly ConcurrentHashSet<ulong> _ignoreMessageIds = new();
|
||||
|
||||
public LogCommandService(DiscordSocketClient client, IBotStrings strings,
|
||||
DbService db, MuteService mute, ProtectionService prot, GuildTimezoneService tz,
|
||||
IMemoryCache memoryCache, IEmbedBuilderService eb)
|
||||
public LogCommandService(
|
||||
DiscordSocketClient client,
|
||||
IBotStrings strings,
|
||||
DbService db,
|
||||
MuteService mute,
|
||||
ProtectionService prot,
|
||||
GuildTimezoneService tz,
|
||||
IMemoryCache memoryCache,
|
||||
IEmbedBuilderService eb)
|
||||
{
|
||||
_client = client;
|
||||
_memoryCache = memoryCache;
|
||||
@@ -69,41 +74,41 @@ public sealed class LogCommandService : ILogCommandService
|
||||
_tz = tz;
|
||||
|
||||
#if !GLOBAL_NADEKO
|
||||
|
||||
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||
var configs = uow
|
||||
.LogSettings
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.Include(ls => ls.LogIgnores)
|
||||
.ToList();
|
||||
var configs = uow.LogSettings.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.Include(ls => ls.LogIgnores)
|
||||
.ToList();
|
||||
|
||||
GuildLogSettings = configs
|
||||
.ToDictionary(ls => ls.GuildId)
|
||||
.ToConcurrent();
|
||||
GuildLogSettings = configs.ToDictionary(ls => ls.GuildId).ToConcurrent();
|
||||
}
|
||||
|
||||
_timerReference = new(async state =>
|
||||
{
|
||||
var keys = PresenceUpdates.Keys.ToList();
|
||||
|
||||
await keys.Select(key =>
|
||||
{
|
||||
if (!((SocketGuild) key.Guild).CurrentUser.GetPermissions(key).SendMessages)
|
||||
return Task.CompletedTask;
|
||||
if (PresenceUpdates.TryRemove(key, out var msgs))
|
||||
{
|
||||
var title = GetText(key.Guild, strs.presence_updates);
|
||||
var desc = string.Join(Environment.NewLine, msgs);
|
||||
return key.SendConfirmAsync(_eb, title, desc.TrimTo(2048));
|
||||
}
|
||||
var keys = PresenceUpdates.Keys.ToList();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}).WhenAll();
|
||||
}, null, TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15));
|
||||
await keys.Select(key =>
|
||||
{
|
||||
if (!((SocketGuild)key.Guild).CurrentUser.GetPermissions(key).SendMessages)
|
||||
return Task.CompletedTask;
|
||||
if (PresenceUpdates.TryRemove(key, out var msgs))
|
||||
{
|
||||
var title = GetText(key.Guild, strs.presence_updates);
|
||||
var desc = string.Join(Environment.NewLine, msgs);
|
||||
return key.SendConfirmAsync(_eb, title, desc.TrimTo(2048));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
})
|
||||
.WhenAll();
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromSeconds(15),
|
||||
TimeSpan.FromSeconds(15));
|
||||
|
||||
//_client.MessageReceived += _client_MessageReceived;
|
||||
_client.MessageUpdated += _client_MessageUpdated;
|
||||
@@ -128,10 +133,13 @@ public sealed class LogCommandService : ILogCommandService
|
||||
_prot.OnAntiProtectionTriggered += TriggeredAntiProtection;
|
||||
|
||||
_clearTimer = new(_ =>
|
||||
{
|
||||
_ignoreMessageIds.Clear();
|
||||
}, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
|
||||
|
||||
{
|
||||
_ignoreMessageIds.Clear();
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromHours(1),
|
||||
TimeSpan.FromHours(1));
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -150,12 +158,11 @@ public sealed class LogCommandService : ILogCommandService
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var logSetting = uow.LogSettingsFor(gid);
|
||||
removed = logSetting.LogIgnores
|
||||
.RemoveAll(x => x.ItemType == itemType && itemId == x.LogItemId);
|
||||
|
||||
removed = logSetting.LogIgnores.RemoveAll(x => x.ItemType == itemType && itemId == x.LogItemId);
|
||||
|
||||
if (removed == 0)
|
||||
{
|
||||
var toAdd = new IgnoredLogItem { LogItemId = itemId, ItemType = itemType};
|
||||
var toAdd = new IgnoredLogItem { LogItemId = itemId, ItemType = itemType };
|
||||
logSetting.LogIgnores.Add(toAdd);
|
||||
}
|
||||
|
||||
@@ -165,9 +172,9 @@ public sealed class LogCommandService : ILogCommandService
|
||||
|
||||
return removed > 0;
|
||||
}
|
||||
|
||||
private string GetText(IGuild guild, LocStr str) =>
|
||||
_strings.GetText(str, guild.Id);
|
||||
|
||||
private string GetText(IGuild guild, LocStr str)
|
||||
=> _strings.GetText(str, guild.Id);
|
||||
|
||||
private string PrettyCurrentTime(IGuild g)
|
||||
{
|
||||
@@ -190,23 +197,12 @@ public sealed class LogCommandService : ILogCommandService
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var logSetting = uow.LogSettingsFor(guildId);
|
||||
|
||||
logSetting.LogOtherId =
|
||||
logSetting.MessageUpdatedId =
|
||||
logSetting.MessageDeletedId =
|
||||
logSetting.UserJoinedId =
|
||||
logSetting.UserLeftId =
|
||||
logSetting.UserBannedId =
|
||||
logSetting.UserUnbannedId =
|
||||
logSetting.UserUpdatedId =
|
||||
logSetting.ChannelCreatedId =
|
||||
logSetting.ChannelDestroyedId =
|
||||
logSetting.ChannelUpdatedId =
|
||||
logSetting.LogUserPresenceId =
|
||||
logSetting.LogVoicePresenceId =
|
||||
logSetting.UserMutedId =
|
||||
logSetting.LogVoicePresenceTTSId =
|
||||
value ? channelId : null;
|
||||
|
||||
logSetting.LogOtherId = logSetting.MessageUpdatedId = logSetting.MessageDeletedId = logSetting.UserJoinedId =
|
||||
logSetting.UserLeftId = logSetting.UserBannedId = logSetting.UserUnbannedId = logSetting.UserUpdatedId =
|
||||
logSetting.ChannelCreatedId = logSetting.ChannelDestroyedId = logSetting.ChannelUpdatedId =
|
||||
logSetting.LogUserPresenceId = logSetting.LogVoicePresenceId = logSetting.UserMutedId =
|
||||
logSetting.LogVoicePresenceTTSId = value ? channelId : null;
|
||||
;
|
||||
await uow.SaveChangesAsync();
|
||||
GuildLogSettings.AddOrUpdate(guildId, id => logSetting, (id, old) => logSetting);
|
||||
@@ -223,13 +219,11 @@ public sealed class LogCommandService : ILogCommandService
|
||||
|
||||
var g = after.Guild;
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(g.Id, out var logSetting)
|
||||
|| logSetting.UserUpdatedId is null)
|
||||
if (!GuildLogSettings.TryGetValue(g.Id, out var logSetting) || logSetting.UserUpdatedId is null)
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
if ((logChannel =
|
||||
await TryGetLogChannel(g, logSetting, LogType.UserUpdated)) is null)
|
||||
if ((logChannel = await TryGetLogChannel(g, logSetting, LogType.UserUpdated)) is null)
|
||||
return;
|
||||
|
||||
var embed = _eb.Create();
|
||||
@@ -237,18 +231,18 @@ public sealed class LogCommandService : ILogCommandService
|
||||
if (before.Username != after.Username)
|
||||
{
|
||||
embed.WithTitle("👥 " + GetText(g, strs.username_changed))
|
||||
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
|
||||
.AddField("Old Name", $"{before.Username}", true)
|
||||
.AddField("New Name", $"{after.Username}", true)
|
||||
.WithFooter(CurrentTime(g))
|
||||
.WithOkColor();
|
||||
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
|
||||
.AddField("Old Name", $"{before.Username}", true)
|
||||
.AddField("New Name", $"{after.Username}", true)
|
||||
.WithFooter(CurrentTime(g))
|
||||
.WithOkColor();
|
||||
}
|
||||
else if (before.AvatarId != after.AvatarId)
|
||||
{
|
||||
embed.WithTitle("👥" + GetText(g, strs.avatar_changed))
|
||||
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
|
||||
.WithFooter(CurrentTime(g))
|
||||
.WithOkColor();
|
||||
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
|
||||
.WithFooter(CurrentTime(g))
|
||||
.WithOkColor();
|
||||
|
||||
var bav = before.RealAvatarUrl();
|
||||
if (bav != null && bav.IsAbsoluteUri)
|
||||
@@ -273,7 +267,7 @@ public sealed class LogCommandService : ILogCommandService
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public bool Log(ulong gid, ulong? cid, LogType type/*, string options*/)
|
||||
public bool Log(ulong gid, ulong? cid, LogType type /*, string options*/)
|
||||
{
|
||||
ulong? channelId = null;
|
||||
using (var uow = _db.GetDbContext())
|
||||
@@ -314,19 +308,16 @@ public sealed class LogCommandService : ILogCommandService
|
||||
channelId = logSetting.ChannelCreatedId = logSetting.ChannelCreatedId is null ? cid : default;
|
||||
break;
|
||||
case LogType.ChannelDestroyed:
|
||||
channelId = logSetting.ChannelDestroyedId =
|
||||
logSetting.ChannelDestroyedId is null ? cid : default;
|
||||
channelId = logSetting.ChannelDestroyedId = logSetting.ChannelDestroyedId is null ? cid : default;
|
||||
break;
|
||||
case LogType.ChannelUpdated:
|
||||
channelId = logSetting.ChannelUpdatedId = logSetting.ChannelUpdatedId is null ? cid : default;
|
||||
break;
|
||||
case LogType.UserPresence:
|
||||
channelId = logSetting.LogUserPresenceId =
|
||||
logSetting.LogUserPresenceId is null ? cid : default;
|
||||
channelId = logSetting.LogUserPresenceId = logSetting.LogUserPresenceId is null ? cid : default;
|
||||
break;
|
||||
case LogType.VoicePresence:
|
||||
channelId = logSetting.LogVoicePresenceId =
|
||||
logSetting.LogVoicePresenceId is null ? cid : default;
|
||||
channelId = logSetting.LogVoicePresenceId = logSetting.LogVoicePresenceId is null ? cid : default;
|
||||
break;
|
||||
case LogType.VoicePresenceTTS:
|
||||
channelId = logSetting.LogVoicePresenceTTSId =
|
||||
@@ -365,17 +356,11 @@ public sealed class LogCommandService : ILogCommandService
|
||||
|
||||
var str = string.Empty;
|
||||
if (beforeVch?.Guild == afterVch?.Guild)
|
||||
{
|
||||
str = GetText(logChannel.Guild, strs.log_vc_moved(usr.Username, beforeVch?.Name, afterVch?.Name));
|
||||
}
|
||||
else if (beforeVch is null)
|
||||
{
|
||||
str = GetText(logChannel.Guild, strs.log_vc_joined(usr.Username, afterVch.Name));
|
||||
}
|
||||
else if (afterVch is null)
|
||||
{
|
||||
str = GetText(logChannel.Guild, strs.log_vc_left(usr.Username, beforeVch.Name));
|
||||
}
|
||||
|
||||
var toDelete = await logChannel.SendMessageAsync(str, true);
|
||||
toDelete.DeleteAfter(5);
|
||||
@@ -388,14 +373,17 @@ public sealed class LogCommandService : ILogCommandService
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void MuteCommands_UserMuted(IGuildUser usr, IUser mod, MuteType muteType, string reason)
|
||||
private void MuteCommands_UserMuted(
|
||||
IGuildUser usr,
|
||||
IUser mod,
|
||||
MuteType muteType,
|
||||
string reason)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting)
|
||||
|| logSetting.UserMutedId is null)
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting) || logSetting.UserMutedId is null)
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -412,15 +400,16 @@ public sealed class LogCommandService : ILogCommandService
|
||||
mutes = "🔇 " + GetText(logChannel.Guild, strs.xmuted_text(mutedLocalized, mod.ToString()));
|
||||
break;
|
||||
case MuteType.All:
|
||||
mutes = "🔇 " + GetText(logChannel.Guild, strs.xmuted_text_and_voice(mutedLocalized,
|
||||
mod.ToString()));
|
||||
mutes = "🔇 "
|
||||
+ GetText(logChannel.Guild, strs.xmuted_text_and_voice(mutedLocalized, mod.ToString()));
|
||||
break;
|
||||
}
|
||||
|
||||
var embed = _eb.Create().WithAuthor(mutes)
|
||||
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
|
||||
.WithFooter(CurrentTime(usr.Guild))
|
||||
.WithOkColor();
|
||||
var embed = _eb.Create()
|
||||
.WithAuthor(mutes)
|
||||
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
|
||||
.WithFooter(CurrentTime(usr.Guild))
|
||||
.WithOkColor();
|
||||
|
||||
await logChannel.EmbedAsync(embed);
|
||||
}
|
||||
@@ -431,14 +420,17 @@ public sealed class LogCommandService : ILogCommandService
|
||||
});
|
||||
}
|
||||
|
||||
private void MuteCommands_UserUnmuted(IGuildUser usr, IUser mod, MuteType muteType, string reason)
|
||||
private void MuteCommands_UserUnmuted(
|
||||
IGuildUser usr,
|
||||
IUser mod,
|
||||
MuteType muteType,
|
||||
string reason)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting)
|
||||
|| logSetting.UserMutedId is null)
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting) || logSetting.UserMutedId is null)
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -456,15 +448,17 @@ public sealed class LogCommandService : ILogCommandService
|
||||
mutes = "🔊 " + GetText(logChannel.Guild, strs.xmuted_text(unmutedLocalized, mod.ToString()));
|
||||
break;
|
||||
case MuteType.All:
|
||||
mutes = "🔊 " + GetText(logChannel.Guild, strs.xmuted_text_and_voice(unmutedLocalized,
|
||||
mod.ToString()));
|
||||
mutes = "🔊 "
|
||||
+ GetText(logChannel.Guild,
|
||||
strs.xmuted_text_and_voice(unmutedLocalized, mod.ToString()));
|
||||
break;
|
||||
}
|
||||
|
||||
var embed = _eb.Create().WithAuthor(mutes)
|
||||
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
|
||||
.WithFooter($"{CurrentTime(usr.Guild)}")
|
||||
.WithOkColor();
|
||||
var embed = _eb.Create()
|
||||
.WithAuthor(mutes)
|
||||
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
|
||||
.WithFooter($"{CurrentTime(usr.Guild)}")
|
||||
.WithOkColor();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(reason))
|
||||
embed.WithDescription(reason);
|
||||
@@ -478,8 +472,7 @@ public sealed class LogCommandService : ILogCommandService
|
||||
});
|
||||
}
|
||||
|
||||
public Task TriggeredAntiProtection(PunishmentAction action, ProtectionType protection,
|
||||
params IGuildUser[] users)
|
||||
public Task TriggeredAntiProtection(PunishmentAction action, ProtectionType protection, params IGuildUser[] users)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
@@ -515,11 +508,12 @@ public sealed class LogCommandService : ILogCommandService
|
||||
break;
|
||||
}
|
||||
|
||||
var embed = _eb.Create().WithAuthor($"🛡 Anti-{protection}")
|
||||
.WithTitle(GetText(logChannel.Guild, strs.users) + " " + punishment)
|
||||
.WithDescription(string.Join("\n", users.Select(u => u.ToString())))
|
||||
.WithFooter(CurrentTime(logChannel.Guild))
|
||||
.WithOkColor();
|
||||
var embed = _eb.Create()
|
||||
.WithAuthor($"🛡 Anti-{protection}")
|
||||
.WithTitle(GetText(logChannel.Guild, strs.users) + " " + punishment)
|
||||
.WithDescription(string.Join("\n", users.Select(u => u.ToString())))
|
||||
.WithFooter(CurrentTime(logChannel.Guild))
|
||||
.WithOkColor();
|
||||
|
||||
await logChannel.EmbedAsync(embed);
|
||||
}
|
||||
@@ -533,13 +527,11 @@ public sealed class LogCommandService : ILogCommandService
|
||||
|
||||
private string GetRoleDeletedKey(ulong roleId)
|
||||
=> $"role_deleted_{roleId}";
|
||||
|
||||
|
||||
private Task _client_RoleDeleted(SocketRole socketRole)
|
||||
{
|
||||
Serilog.Log.Information("Role deleted {RoleId}", socketRole.Id);
|
||||
_memoryCache.Set(GetRoleDeletedKey(socketRole.Id),
|
||||
true,
|
||||
TimeSpan.FromMinutes(5));
|
||||
_memoryCache.Set(GetRoleDeletedKey(socketRole.Id), true, TimeSpan.FromMinutes(5));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -559,25 +551,27 @@ public sealed class LogCommandService : ILogCommandService
|
||||
|
||||
if (before is null)
|
||||
return;
|
||||
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out var logSetting)
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
|| logSetting.LogIgnores.Any(ilc
|
||||
=> ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
if (logSetting.UserUpdatedId != null &&
|
||||
(logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) != null)
|
||||
if (logSetting.UserUpdatedId != null
|
||||
&& (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) != null)
|
||||
{
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithFooter(CurrentTime(before.Guild))
|
||||
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}");
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithFooter(CurrentTime(before.Guild))
|
||||
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}");
|
||||
if (before.Nickname != after.Nickname)
|
||||
{
|
||||
embed.WithAuthor("👥 " + GetText(logChannel.Guild, strs.nick_change))
|
||||
.AddField(GetText(logChannel.Guild, strs.old_nick)
|
||||
, $"{before.Nickname}#{before.Discriminator}")
|
||||
.AddField(GetText(logChannel.Guild, strs.new_nick)
|
||||
, $"{after.Nickname}#{after.Discriminator}");
|
||||
.AddField(GetText(logChannel.Guild, strs.old_nick),
|
||||
$"{before.Nickname}#{before.Discriminator}")
|
||||
.AddField(GetText(logChannel.Guild, strs.new_nick),
|
||||
$"{after.Nickname}#{after.Discriminator}");
|
||||
|
||||
await logChannel.EmbedAsync(embed);
|
||||
}
|
||||
@@ -587,22 +581,21 @@ public sealed class LogCommandService : ILogCommandService
|
||||
{
|
||||
var diffRoles = after.Roles.Where(r => !before.Roles.Contains(r)).Select(r => r.Name);
|
||||
embed.WithAuthor("⚔ " + GetText(logChannel.Guild, strs.user_role_add))
|
||||
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
|
||||
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
|
||||
|
||||
await logChannel.EmbedAsync(embed);
|
||||
}
|
||||
else if (before.Roles.Count > after.Roles.Count)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
var diffRoles = before.Roles
|
||||
.Where(r => !after.Roles.Contains(r) && !IsRoleDeleted(r.Id))
|
||||
.Select(r => r.Name)
|
||||
.ToList();
|
||||
var diffRoles = before.Roles.Where(r => !after.Roles.Contains(r) && !IsRoleDeleted(r.Id))
|
||||
.Select(r => r.Name)
|
||||
.ToList();
|
||||
|
||||
if (diffRoles.Any())
|
||||
{
|
||||
embed.WithAuthor("⚔ " + GetText(logChannel.Guild, strs.user_role_rem))
|
||||
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
|
||||
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
|
||||
|
||||
await logChannel.EmbedAsync(embed);
|
||||
}
|
||||
@@ -611,17 +604,20 @@ public sealed class LogCommandService : ILogCommandService
|
||||
}
|
||||
|
||||
logChannel = null;
|
||||
if (!before.IsBot && logSetting.LogUserPresenceId != null && (logChannel =
|
||||
await TryGetLogChannel(before.Guild, logSetting, LogType.UserPresence)) != null)
|
||||
if (!before.IsBot
|
||||
&& logSetting.LogUserPresenceId != null
|
||||
&& (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserPresence)) != null)
|
||||
{
|
||||
if (before.Status != after.Status)
|
||||
{
|
||||
var str = "🎭" + Format.Code(PrettyCurrentTime(after.Guild)) +
|
||||
GetText(logChannel.Guild, strs.user_status_change(
|
||||
"👤" + Format.Bold(after.Username),
|
||||
Format.Bold(after.Status.ToString())));
|
||||
var str = "🎭"
|
||||
+ Format.Code(PrettyCurrentTime(after.Guild))
|
||||
+ GetText(logChannel.Guild,
|
||||
strs.user_status_change("👤" + Format.Bold(after.Username),
|
||||
Format.Bold(after.Status.ToString())));
|
||||
PresenceUpdates.AddOrUpdate(logChannel,
|
||||
new List<string>() {str}, (id, list) =>
|
||||
new List<string> { str },
|
||||
(id, list) =>
|
||||
{
|
||||
list.Add(str);
|
||||
return list;
|
||||
@@ -632,7 +628,8 @@ public sealed class LogCommandService : ILogCommandService
|
||||
var str =
|
||||
$"👾`{PrettyCurrentTime(after.Guild)}`👤__**{after.Username}**__ is now playing **{after.Activities.FirstOrDefault()?.Name ?? "-"}**.";
|
||||
PresenceUpdates.AddOrUpdate(logChannel,
|
||||
new List<string>() {str}, (id, list) =>
|
||||
new List<string> { str },
|
||||
(id, list) =>
|
||||
{
|
||||
list.Add(str);
|
||||
return list;
|
||||
@@ -657,36 +654,32 @@ public sealed class LogCommandService : ILogCommandService
|
||||
if (cbefore is not IGuildChannel before)
|
||||
return;
|
||||
|
||||
var after = (IGuildChannel) cafter;
|
||||
var after = (IGuildChannel)cafter;
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out var logSetting)
|
||||
|| logSetting.ChannelUpdatedId is null
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
|| logSetting.LogIgnores.Any(ilc
|
||||
=> ilc.LogItemId == after.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.ChannelUpdated)) is null)
|
||||
return;
|
||||
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithFooter(CurrentTime(before.Guild));
|
||||
var embed = _eb.Create().WithOkColor().WithFooter(CurrentTime(before.Guild));
|
||||
|
||||
var beforeTextChannel = cbefore as ITextChannel;
|
||||
var afterTextChannel = cafter as ITextChannel;
|
||||
|
||||
if (before.Name != after.Name)
|
||||
{
|
||||
embed.WithTitle("ℹ️ " + GetText(logChannel.Guild, strs.ch_name_change))
|
||||
.WithDescription($"{after} | {after.Id}")
|
||||
.AddField(GetText(logChannel.Guild, strs.ch_old_name), before.Name);
|
||||
}
|
||||
.WithDescription($"{after} | {after.Id}")
|
||||
.AddField(GetText(logChannel.Guild, strs.ch_old_name), before.Name);
|
||||
else if (beforeTextChannel?.Topic != afterTextChannel?.Topic)
|
||||
{
|
||||
embed.WithTitle("ℹ️ " + GetText(logChannel.Guild, strs.ch_topic_change))
|
||||
.WithDescription($"{after} | {after.Id}")
|
||||
.AddField(GetText(logChannel.Guild, strs.old_topic) , beforeTextChannel?.Topic ?? "-")
|
||||
.AddField(GetText(logChannel.Guild, strs.new_topic), afterTextChannel?.Topic ?? "-");
|
||||
}
|
||||
.WithDescription($"{after} | {after.Id}")
|
||||
.AddField(GetText(logChannel.Guild, strs.old_topic), beforeTextChannel?.Topic ?? "-")
|
||||
.AddField(GetText(logChannel.Guild, strs.new_topic), afterTextChannel?.Topic ?? "-");
|
||||
else
|
||||
return;
|
||||
|
||||
@@ -711,7 +704,8 @@ public sealed class LogCommandService : ILogCommandService
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out var logSetting)
|
||||
|| logSetting.ChannelDestroyedId is null
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == ch.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
|| logSetting.LogIgnores.Any(ilc
|
||||
=> ilc.LogItemId == ch.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -719,17 +713,15 @@ public sealed class LogCommandService : ILogCommandService
|
||||
return;
|
||||
string title;
|
||||
if (ch is IVoiceChannel)
|
||||
{
|
||||
title = GetText(logChannel.Guild, strs.voice_chan_destroyed);
|
||||
}
|
||||
else
|
||||
title = GetText(logChannel.Guild, strs.text_chan_destroyed);
|
||||
|
||||
await logChannel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("🆕 " + title)
|
||||
.WithDescription($"{ch.Name} | {ch.Id}")
|
||||
.WithFooter(CurrentTime(ch.Guild)));
|
||||
.WithOkColor()
|
||||
.WithTitle("🆕 " + title)
|
||||
.WithDescription($"{ch.Name} | {ch.Id}")
|
||||
.WithFooter(CurrentTime(ch.Guild)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -757,17 +749,15 @@ public sealed class LogCommandService : ILogCommandService
|
||||
return;
|
||||
string title;
|
||||
if (ch is IVoiceChannel)
|
||||
{
|
||||
title = GetText(logChannel.Guild, strs.voice_chan_created);
|
||||
}
|
||||
else
|
||||
title = GetText(logChannel.Guild, strs.text_chan_created);
|
||||
|
||||
await logChannel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("🆕 " + title)
|
||||
.WithDescription($"{ch.Name} | {ch.Id}")
|
||||
.WithFooter(CurrentTime(ch.Guild)));
|
||||
.WithOkColor()
|
||||
.WithTitle("🆕 " + title)
|
||||
.WithDescription($"{ch.Name} | {ch.Id}")
|
||||
.WithFooter(CurrentTime(ch.Guild)));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -794,7 +784,8 @@ public sealed class LogCommandService : ILogCommandService
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting)
|
||||
|| logSetting.LogVoicePresenceId is null
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == iusr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
|| logSetting.LogIgnores.Any(
|
||||
ilc => ilc.LogItemId == iusr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -803,32 +794,33 @@ public sealed class LogCommandService : ILogCommandService
|
||||
|
||||
string str = null;
|
||||
if (beforeVch?.Guild == afterVch?.Guild)
|
||||
{
|
||||
str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild,
|
||||
strs.user_vmoved(
|
||||
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
|
||||
Format.Bold(beforeVch?.Name ?? ""), Format.Bold(afterVch?.Name ?? "")));
|
||||
}
|
||||
str = "🎙"
|
||||
+ Format.Code(PrettyCurrentTime(usr.Guild))
|
||||
+ GetText(logChannel.Guild,
|
||||
strs.user_vmoved("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
|
||||
Format.Bold(beforeVch?.Name ?? ""),
|
||||
Format.Bold(afterVch?.Name ?? "")));
|
||||
else if (beforeVch is null)
|
||||
{
|
||||
str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild,
|
||||
strs.user_vjoined(
|
||||
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
|
||||
Format.Bold(afterVch.Name ?? "")));
|
||||
}
|
||||
str = "🎙"
|
||||
+ Format.Code(PrettyCurrentTime(usr.Guild))
|
||||
+ GetText(logChannel.Guild,
|
||||
strs.user_vjoined("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
|
||||
Format.Bold(afterVch.Name ?? "")));
|
||||
else if (afterVch is null)
|
||||
{
|
||||
str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild,
|
||||
strs.user_vleft("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
|
||||
Format.Bold(beforeVch.Name ?? "")));
|
||||
}
|
||||
str = "🎙"
|
||||
+ Format.Code(PrettyCurrentTime(usr.Guild))
|
||||
+ GetText(logChannel.Guild,
|
||||
strs.user_vleft("👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
|
||||
Format.Bold(beforeVch.Name ?? "")));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(str))
|
||||
PresenceUpdates.AddOrUpdate(logChannel, new List<string>() {str}, (id, list) =>
|
||||
{
|
||||
list.Add(str);
|
||||
return list;
|
||||
});
|
||||
PresenceUpdates.AddOrUpdate(logChannel,
|
||||
new List<string> { str },
|
||||
(id, list) =>
|
||||
{
|
||||
list.Add(str);
|
||||
return list;
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -846,18 +838,19 @@ public sealed class LogCommandService : ILogCommandService
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(guild.Id, out var logSetting)
|
||||
|| logSetting.UserLeftId is null
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
|| logSetting.LogIgnores.Any(ilc
|
||||
=> ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserLeft)) is null)
|
||||
return;
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("❌ " + GetText(logChannel.Guild, strs.user_left))
|
||||
.WithDescription(usr.ToString())
|
||||
.AddField("Id", usr.Id.ToString())
|
||||
.WithFooter(CurrentTime(guild));
|
||||
.WithOkColor()
|
||||
.WithTitle("❌ " + GetText(logChannel.Guild, strs.user_left))
|
||||
.WithDescription(usr.ToString())
|
||||
.AddField("Id", usr.Id.ToString())
|
||||
.WithFooter(CurrentTime(guild));
|
||||
|
||||
if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute))
|
||||
embed.WithThumbnailUrl(usr.GetAvatarUrl());
|
||||
@@ -878,8 +871,7 @@ public sealed class LogCommandService : ILogCommandService
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting)
|
||||
|| logSetting.UserJoinedId is null)
|
||||
if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out var logSetting) || logSetting.UserJoinedId is null)
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
@@ -887,17 +879,17 @@ public sealed class LogCommandService : ILogCommandService
|
||||
return;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("✅ " + GetText(logChannel.Guild, strs.user_joined))
|
||||
.WithDescription($"{usr.Mention} `{usr}`")
|
||||
.AddField("Id", usr.Id.ToString())
|
||||
.AddField(GetText(logChannel.Guild, strs.joined_server),
|
||||
$"{usr.JoinedAt?.ToString("dd.MM.yyyy HH:mm" ?? "?")}",
|
||||
true)
|
||||
.AddField(GetText(logChannel.Guild, strs.joined_discord),
|
||||
$"{usr.CreatedAt:dd.MM.yyyy HH:mm}",
|
||||
true)
|
||||
.WithFooter(CurrentTime(usr.Guild));
|
||||
.WithOkColor()
|
||||
.WithTitle("✅ " + GetText(logChannel.Guild, strs.user_joined))
|
||||
.WithDescription($"{usr.Mention} `{usr}`")
|
||||
.AddField("Id", usr.Id.ToString())
|
||||
.AddField(GetText(logChannel.Guild, strs.joined_server),
|
||||
$"{usr.JoinedAt?.ToString("dd.MM.yyyy HH:mm" ?? "?")}",
|
||||
true)
|
||||
.AddField(GetText(logChannel.Guild, strs.joined_discord),
|
||||
$"{usr.CreatedAt:dd.MM.yyyy HH:mm}",
|
||||
true)
|
||||
.WithFooter(CurrentTime(usr.Guild));
|
||||
|
||||
if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute))
|
||||
embed.WithThumbnailUrl(usr.GetAvatarUrl());
|
||||
@@ -920,18 +912,19 @@ public sealed class LogCommandService : ILogCommandService
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(guild.Id, out var logSetting)
|
||||
|| logSetting.UserUnbannedId is null
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
|| logSetting.LogIgnores.Any(ilc
|
||||
=> ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserUnbanned)) is null)
|
||||
return;
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("♻️ " + GetText(logChannel.Guild, strs.user_unbanned))
|
||||
.WithDescription(usr.ToString())
|
||||
.AddField("Id", usr.Id.ToString())
|
||||
.WithFooter(CurrentTime(guild));
|
||||
.WithOkColor()
|
||||
.WithTitle("♻️ " + GetText(logChannel.Guild, strs.user_unbanned))
|
||||
.WithDescription(usr.ToString())
|
||||
.AddField("Id", usr.Id.ToString())
|
||||
.WithFooter(CurrentTime(guild));
|
||||
|
||||
if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute))
|
||||
embed.WithThumbnailUrl(usr.GetAvatarUrl());
|
||||
@@ -954,20 +947,19 @@ public sealed class LogCommandService : ILogCommandService
|
||||
{
|
||||
if (!GuildLogSettings.TryGetValue(guild.Id, out var logSetting)
|
||||
|| logSetting.UserBannedId is null
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
|| logSetting.LogIgnores.Any(ilc
|
||||
=> ilc.LogItemId == usr.Id && ilc.ItemType == IgnoredItemType.User))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
if ((logChannel =
|
||||
await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) ==
|
||||
null)
|
||||
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) == null)
|
||||
return;
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned))
|
||||
.WithDescription(usr.ToString())
|
||||
.AddField("Id", usr.Id.ToString())
|
||||
.WithFooter(CurrentTime(guild));
|
||||
.WithOkColor()
|
||||
.WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned))
|
||||
.WithDescription(usr.ToString())
|
||||
.AddField("Id", usr.Id.ToString())
|
||||
.WithFooter(CurrentTime(guild));
|
||||
|
||||
var avatarUrl = usr.GetAvatarUrl();
|
||||
|
||||
@@ -1002,27 +994,28 @@ public sealed class LogCommandService : ILogCommandService
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out var logSetting)
|
||||
|| logSetting.MessageDeletedId is null
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
|| logSetting.LogIgnores.Any(ilc
|
||||
=> ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageDeleted)) is null || logChannel.Id == msg.Id)
|
||||
if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageDeleted)) is null
|
||||
|| logChannel.Id == msg.Id)
|
||||
return;
|
||||
|
||||
var resolvedMessage = msg.Resolve(userHandling: TagHandling.FullName);
|
||||
var resolvedMessage = msg.Resolve(TagHandling.FullName);
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("🗑 " + GetText(logChannel.Guild, strs.msg_del(((ITextChannel) msg.Channel).Name)))
|
||||
.WithDescription(msg.Author.ToString())
|
||||
.AddField(GetText(logChannel.Guild, strs.content),
|
||||
string.IsNullOrWhiteSpace(resolvedMessage) ? "-" : resolvedMessage,
|
||||
false)
|
||||
.AddField("Id", msg.Id.ToString(), false)
|
||||
.WithFooter(CurrentTime(channel.Guild));
|
||||
.WithOkColor()
|
||||
.WithTitle("🗑 "
|
||||
+ GetText(logChannel.Guild, strs.msg_del(((ITextChannel)msg.Channel).Name)))
|
||||
.WithDescription(msg.Author.ToString())
|
||||
.AddField(GetText(logChannel.Guild, strs.content),
|
||||
string.IsNullOrWhiteSpace(resolvedMessage) ? "-" : resolvedMessage)
|
||||
.AddField("Id", msg.Id.ToString())
|
||||
.WithFooter(CurrentTime(channel.Guild));
|
||||
if (msg.Attachments.Any())
|
||||
embed.AddField(GetText(logChannel.Guild, strs.attachments),
|
||||
string.Join(", ", msg.Attachments.Select(a => a.Url)),
|
||||
false);
|
||||
string.Join(", ", msg.Attachments.Select(a => a.Url)));
|
||||
|
||||
await logChannel.EmbedAsync(embed);
|
||||
}
|
||||
@@ -1034,7 +1027,9 @@ public sealed class LogCommandService : ILogCommandService
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task _client_MessageUpdated(Cacheable<IMessage, ulong> optmsg, SocketMessage imsg2,
|
||||
private Task _client_MessageUpdated(
|
||||
Cacheable<IMessage, ulong> optmsg,
|
||||
SocketMessage imsg2,
|
||||
ISocketMessageChannel ch)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
@@ -1058,30 +1053,29 @@ public sealed class LogCommandService : ILogCommandService
|
||||
|
||||
if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out var logSetting)
|
||||
|| logSetting.MessageUpdatedId is null
|
||||
|| logSetting.LogIgnores.Any(ilc => ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
|| logSetting.LogIgnores.Any(ilc
|
||||
=> ilc.LogItemId == channel.Id && ilc.ItemType == IgnoredItemType.Channel))
|
||||
return;
|
||||
|
||||
ITextChannel logChannel;
|
||||
if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageUpdated)) is null || logChannel.Id == after.Channel.Id)
|
||||
if ((logChannel = await TryGetLogChannel(channel.Guild, logSetting, LogType.MessageUpdated)) is null
|
||||
|| logChannel.Id == after.Channel.Id)
|
||||
return;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("📝 " + GetText(logChannel.Guild, strs.msg_update(((ITextChannel)after.Channel).Name)))
|
||||
.WithDescription(after.Author.ToString())
|
||||
.AddField(GetText(logChannel.Guild, strs.old_msg),
|
||||
string.IsNullOrWhiteSpace(before.Content)
|
||||
? "-"
|
||||
: before.Resolve(userHandling: TagHandling.FullName),
|
||||
false)
|
||||
.AddField(
|
||||
GetText(logChannel.Guild, strs.new_msg),
|
||||
string.IsNullOrWhiteSpace(after.Content)
|
||||
? "-"
|
||||
: after.Resolve(userHandling: TagHandling.FullName),
|
||||
false)
|
||||
.AddField("Id", after.Id.ToString(), false)
|
||||
.WithFooter(CurrentTime(channel.Guild));
|
||||
.WithOkColor()
|
||||
.WithTitle("📝 "
|
||||
+ GetText(logChannel.Guild,
|
||||
strs.msg_update(((ITextChannel)after.Channel).Name)))
|
||||
.WithDescription(after.Author.ToString())
|
||||
.AddField(GetText(logChannel.Guild, strs.old_msg),
|
||||
string.IsNullOrWhiteSpace(before.Content)
|
||||
? "-"
|
||||
: before.Resolve(TagHandling.FullName))
|
||||
.AddField(GetText(logChannel.Guild, strs.new_msg),
|
||||
string.IsNullOrWhiteSpace(after.Content) ? "-" : after.Resolve(TagHandling.FullName))
|
||||
.AddField("Id", after.Id.ToString())
|
||||
.WithFooter(CurrentTime(channel.Guild));
|
||||
|
||||
await logChannel.EmbedAsync(embed);
|
||||
}
|
||||
@@ -1158,8 +1152,8 @@ public sealed class LogCommandService : ILogCommandService
|
||||
UnsetLogSetting(guild.Id, logChannelType);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
return channel;
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
private void UnsetLogSetting(ulong guildId, LogType logChannelType)
|
||||
@@ -1218,4 +1212,4 @@ public sealed class LogCommandService : ILogCommandService
|
||||
GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (gid, old) => newLogSetting);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
@@ -14,17 +14,19 @@ public enum MuteType
|
||||
|
||||
public class MuteService : INService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, string> GuildMuteRoles { get; }
|
||||
public ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; }
|
||||
public enum TimerType { Mute, Ban, AddRole }
|
||||
|
||||
public ConcurrentDictionary<ulong, ConcurrentDictionary<(ulong, TimerType), Timer>> Un_Timers { get; } = new();
|
||||
private static readonly OverwritePermissions denyOverwrite = new(addReactions: PermValue.Deny,
|
||||
sendMessages: PermValue.Deny,
|
||||
attachFiles: PermValue.Deny);
|
||||
|
||||
public event Action<IGuildUser, IUser, MuteType, string> UserMuted = delegate { };
|
||||
public event Action<IGuildUser, IUser, MuteType, string> UserUnmuted = delegate { };
|
||||
|
||||
private static readonly OverwritePermissions denyOverwrite =
|
||||
new(addReactions: PermValue.Deny, sendMessages: PermValue.Deny,
|
||||
attachFiles: PermValue.Deny);
|
||||
public ConcurrentDictionary<ulong, string> GuildMuteRoles { get; }
|
||||
public ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; }
|
||||
|
||||
public ConcurrentDictionary<ulong, ConcurrentDictionary<(ulong, TimerType), Timer>> Un_Timers { get; } = new();
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
@@ -39,24 +41,21 @@ public class MuteService : INService
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||
var configs = uow.Set<GuildConfig>().AsQueryable()
|
||||
.Include(x => x.MutedUsers)
|
||||
.Include(x => x.UnbanTimer)
|
||||
.Include(x => x.UnmuteTimers)
|
||||
.Include(x => x.UnroleTimer)
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.ToList();
|
||||
var configs = uow.Set<GuildConfig>()
|
||||
.AsQueryable()
|
||||
.Include(x => x.MutedUsers)
|
||||
.Include(x => x.UnbanTimer)
|
||||
.Include(x => x.UnmuteTimers)
|
||||
.Include(x => x.UnroleTimer)
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.ToList();
|
||||
|
||||
GuildMuteRoles = configs
|
||||
.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
|
||||
.ToDictionary(c => c.GuildId, c => c.MuteRoleName)
|
||||
.ToConcurrent();
|
||||
GuildMuteRoles = configs.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
|
||||
.ToDictionary(c => c.GuildId, c => c.MuteRoleName)
|
||||
.ToConcurrent();
|
||||
|
||||
MutedUsers = new(configs
|
||||
.ToDictionary(
|
||||
k => k.GuildId,
|
||||
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
|
||||
));
|
||||
MutedUsers = new(configs.ToDictionary(k => k.GuildId,
|
||||
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))));
|
||||
|
||||
var max = TimeSpan.FromDays(49);
|
||||
|
||||
@@ -118,30 +117,40 @@ public class MuteService : INService
|
||||
UserUnmuted += OnUserUnmuted;
|
||||
}
|
||||
|
||||
private void OnUserMuted(IGuildUser user, IUser mod, MuteType type, string reason)
|
||||
private void OnUserMuted(
|
||||
IGuildUser user,
|
||||
IUser mod,
|
||||
MuteType type,
|
||||
string reason)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
return;
|
||||
|
||||
|
||||
var _ = Task.Run(() => user.SendMessageAsync(embed: _eb.Create()
|
||||
.WithDescription($"You've been muted in {user.Guild} server")
|
||||
.AddField("Mute Type", type.ToString())
|
||||
.AddField("Moderator", mod.ToString())
|
||||
.AddField("Reason", reason)
|
||||
.Build()));
|
||||
.WithDescription(
|
||||
$"You've been muted in {user.Guild} server")
|
||||
.AddField("Mute Type", type.ToString())
|
||||
.AddField("Moderator", mod.ToString())
|
||||
.AddField("Reason", reason)
|
||||
.Build()));
|
||||
}
|
||||
|
||||
private void OnUserUnmuted(IGuildUser user, IUser mod, MuteType type, string reason)
|
||||
private void OnUserUnmuted(
|
||||
IGuildUser user,
|
||||
IUser mod,
|
||||
MuteType type,
|
||||
string reason)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
return;
|
||||
|
||||
|
||||
var _ = Task.Run(() => user.SendMessageAsync(embed: _eb.Create()
|
||||
.WithDescription($"You've been unmuted in {user.Guild} server")
|
||||
.AddField("Unmute Type", type.ToString())
|
||||
.AddField("Moderator", mod.ToString())
|
||||
.AddField("Reason", reason)
|
||||
.Build()));
|
||||
.WithDescription(
|
||||
$"You've been unmuted in {user.Guild} server")
|
||||
.AddField("Unmute Type", type.ToString())
|
||||
.AddField("Moderator", mod.ToString())
|
||||
.AddField("Reason", reason)
|
||||
.Build()));
|
||||
}
|
||||
|
||||
private Task Client_UserJoined(IGuildUser usr)
|
||||
@@ -158,6 +167,7 @@ public class MuteService : INService
|
||||
{
|
||||
Log.Warning(ex, "Error in MuteService UserJoined event");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -170,11 +180,17 @@ public class MuteService : INService
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task MuteUser(IGuildUser usr, IUser mod, MuteType type = MuteType.All, string reason = "")
|
||||
public async Task MuteUser(
|
||||
IGuildUser usr,
|
||||
IUser mod,
|
||||
MuteType type = MuteType.All,
|
||||
string reason = "")
|
||||
{
|
||||
if (type == MuteType.All)
|
||||
{
|
||||
try { await usr.ModifyAsync(x => x.Mute = true); } catch { }
|
||||
try { await usr.ModifyAsync(x => x.Mute = true); }
|
||||
catch { }
|
||||
|
||||
var muteRole = await GetMuteRole(usr.Guild);
|
||||
if (!usr.RoleIds.Contains(muteRole.Id))
|
||||
await usr.AddRoleAsync(muteRole);
|
||||
@@ -182,12 +198,8 @@ public class MuteService : INService
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(usr.Guild.Id,
|
||||
set => set.Include(gc => gc.MutedUsers)
|
||||
.Include(gc => gc.UnmuteTimers));
|
||||
config.MutedUsers.Add(new()
|
||||
{
|
||||
UserId = usr.Id
|
||||
});
|
||||
set => set.Include(gc => gc.MutedUsers).Include(gc => gc.UnmuteTimers));
|
||||
config.MutedUsers.Add(new() { UserId = usr.Id });
|
||||
if (MutedUsers.TryGetValue(usr.Guild.Id, out var muted))
|
||||
muted.Add(usr.Id);
|
||||
|
||||
@@ -195,6 +207,7 @@ public class MuteService : INService
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
UserMuted(usr, mod, MuteType.All, reason);
|
||||
}
|
||||
else if (type == MuteType.Voice)
|
||||
@@ -213,7 +226,12 @@ public class MuteService : INService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UnmuteUser(ulong guildId, ulong usrId, IUser mod, MuteType type = MuteType.All, string reason = "")
|
||||
public async Task UnmuteUser(
|
||||
ulong guildId,
|
||||
ulong usrId,
|
||||
IUser mod,
|
||||
MuteType type = MuteType.All,
|
||||
string reason = "")
|
||||
{
|
||||
var usr = _client.GetGuild(guildId)?.GetUser(usrId);
|
||||
if (type == MuteType.All)
|
||||
@@ -221,17 +239,11 @@ public class MuteService : INService
|
||||
StopTimer(guildId, usrId, TimerType.Mute);
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(guildId, set => set.Include(gc => gc.MutedUsers)
|
||||
.Include(gc => gc.UnmuteTimers));
|
||||
var match = new MutedUserId()
|
||||
{
|
||||
UserId = usrId
|
||||
};
|
||||
var config = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(gc => gc.MutedUsers).Include(gc => gc.UnmuteTimers));
|
||||
var match = new MutedUserId { UserId = usrId };
|
||||
var toRemove = config.MutedUsers.FirstOrDefault(x => x.Equals(match));
|
||||
if (toRemove != null)
|
||||
{
|
||||
uow.Remove(toRemove);
|
||||
}
|
||||
if (toRemove != null) uow.Remove(toRemove);
|
||||
if (MutedUsers.TryGetValue(guildId, out var muted))
|
||||
muted.TryRemove(usrId);
|
||||
|
||||
@@ -239,10 +251,18 @@ public class MuteService : INService
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (usr != null)
|
||||
{
|
||||
try { await usr.ModifyAsync(x => x.Mute = false); } catch { }
|
||||
try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild)); } catch { /*ignore*/ }
|
||||
try { await usr.ModifyAsync(x => x.Mute = false); }
|
||||
catch { }
|
||||
|
||||
try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild)); }
|
||||
catch
|
||||
{
|
||||
/*ignore*/
|
||||
}
|
||||
|
||||
UserUnmuted(usr, mod, MuteType.All, reason);
|
||||
}
|
||||
}
|
||||
@@ -277,20 +297,16 @@ public class MuteService : INService
|
||||
|
||||
var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName);
|
||||
if (muteRole is null)
|
||||
{
|
||||
|
||||
//if it doesn't exist, create it
|
||||
try { muteRole = await guild.CreateRoleAsync(muteRoleName, isMentionable: false); }
|
||||
catch
|
||||
{
|
||||
//if creations fails, maybe the name is not correct, find default one, if doesn't work, create default one
|
||||
muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ??
|
||||
await guild.CreateRoleAsync(defaultMuteRoleName, isMentionable: false);
|
||||
muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName)
|
||||
?? await guild.CreateRoleAsync(defaultMuteRoleName, isMentionable: false);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var toOverwrite in await guild.GetTextChannelsAsync())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!toOverwrite.PermissionOverwrites.Any(x => x.TargetId == muteRole.Id
|
||||
@@ -305,12 +321,16 @@ public class MuteService : INService
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
return muteRole;
|
||||
}
|
||||
|
||||
public async Task TimedMute(IGuildUser user, IUser mod, TimeSpan after, MuteType muteType = MuteType.All, string reason = "")
|
||||
public async Task TimedMute(
|
||||
IGuildUser user,
|
||||
IUser mod,
|
||||
TimeSpan after,
|
||||
MuteType muteType = MuteType.All,
|
||||
string reason = "")
|
||||
{
|
||||
await MuteUser(user, mod, muteType, reason); // mute the user. This will also remove any previous unmute timers
|
||||
await using (var uow = _db.GetDbContext())
|
||||
@@ -318,8 +338,7 @@ public class MuteService : INService
|
||||
var config = uow.GuildConfigsForId(user.GuildId, set => set.Include(x => x.UnmuteTimers));
|
||||
config.UnmuteTimers.Add(new()
|
||||
{
|
||||
UserId = user.Id,
|
||||
UnmuteAt = DateTime.UtcNow + after,
|
||||
UserId = user.Id, UnmuteAt = DateTime.UtcNow + after
|
||||
}); // add teh unmute timer to the database
|
||||
uow.SaveChanges();
|
||||
}
|
||||
@@ -327,7 +346,11 @@ public class MuteService : INService
|
||||
StartUn_Timer(user.GuildId, user.Id, after, TimerType.Mute); // start the timer
|
||||
}
|
||||
|
||||
public async Task TimedBan(IGuild guild, IUser user, TimeSpan after, string reason)
|
||||
public async Task TimedBan(
|
||||
IGuild guild,
|
||||
IUser user,
|
||||
TimeSpan after,
|
||||
string reason)
|
||||
{
|
||||
await guild.AddBanAsync(user.Id, 0, reason);
|
||||
await using (var uow = _db.GetDbContext())
|
||||
@@ -335,8 +358,7 @@ public class MuteService : INService
|
||||
var config = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.UnbanTimer));
|
||||
config.UnbanTimer.Add(new()
|
||||
{
|
||||
UserId = user.Id,
|
||||
UnbanAt = DateTime.UtcNow + after,
|
||||
UserId = user.Id, UnbanAt = DateTime.UtcNow + after
|
||||
}); // add teh unmute timer to the database
|
||||
uow.SaveChanges();
|
||||
}
|
||||
@@ -344,7 +366,11 @@ public class MuteService : INService
|
||||
StartUn_Timer(guild.Id, user.Id, after, TimerType.Ban); // start the timer
|
||||
}
|
||||
|
||||
public async Task TimedRole(IGuildUser user, TimeSpan after, string reason, IRole role)
|
||||
public async Task TimedRole(
|
||||
IGuildUser user,
|
||||
TimeSpan after,
|
||||
string reason,
|
||||
IRole role)
|
||||
{
|
||||
await user.AddRoleAsync(role);
|
||||
await using (var uow = _db.GetDbContext())
|
||||
@@ -352,81 +378,78 @@ public class MuteService : INService
|
||||
var config = uow.GuildConfigsForId(user.GuildId, set => set.Include(x => x.UnroleTimer));
|
||||
config.UnroleTimer.Add(new()
|
||||
{
|
||||
UserId = user.Id,
|
||||
UnbanAt = DateTime.UtcNow + after,
|
||||
RoleId = role.Id
|
||||
UserId = user.Id, UnbanAt = DateTime.UtcNow + after, RoleId = role.Id
|
||||
}); // add teh unmute timer to the database
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
StartUn_Timer(user.GuildId, user.Id, after, TimerType.AddRole, role.Id); // start the timer
|
||||
}
|
||||
|
||||
public enum TimerType { Mute, Ban, AddRole }
|
||||
public void StartUn_Timer(ulong guildId, ulong userId, TimeSpan after, TimerType type, ulong? roleId = null)
|
||||
public void StartUn_Timer(
|
||||
ulong guildId,
|
||||
ulong userId,
|
||||
TimeSpan after,
|
||||
TimerType type,
|
||||
ulong? roleId = null)
|
||||
{
|
||||
//load the unmute timers for this guild
|
||||
var userUnTimers = Un_Timers.GetOrAdd(guildId, new ConcurrentDictionary<(ulong, TimerType), Timer>());
|
||||
|
||||
//unmute timer to be added
|
||||
var toAdd = new Timer(async _ =>
|
||||
{
|
||||
if (type == TimerType.Ban)
|
||||
{
|
||||
try
|
||||
{
|
||||
RemoveTimerFromDb(guildId, userId, type);
|
||||
StopTimer(guildId, userId, type);
|
||||
var guild = _client.GetGuild(guildId); // load the guild
|
||||
if (guild != null)
|
||||
if (type == TimerType.Ban)
|
||||
try
|
||||
{
|
||||
await guild.RemoveBanAsync(userId);
|
||||
RemoveTimerFromDb(guildId, userId, type);
|
||||
StopTimer(guildId, userId, type);
|
||||
var guild = _client.GetGuild(guildId); // load the guild
|
||||
if (guild != null) await guild.RemoveBanAsync(userId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Couldn't unban user {0} in guild {1}", userId, guildId);
|
||||
}
|
||||
}
|
||||
else if (type == TimerType.AddRole)
|
||||
{
|
||||
try
|
||||
{
|
||||
RemoveTimerFromDb(guildId, userId, type);
|
||||
StopTimer(guildId, userId, type);
|
||||
var guild = _client.GetGuild(guildId);
|
||||
var user = guild?.GetUser(userId);
|
||||
var role = guild.GetRole(roleId.Value);
|
||||
if (guild != null && user != null && user.Roles.Contains(role))
|
||||
catch (Exception ex)
|
||||
{
|
||||
await user.RemoveRoleAsync(role);
|
||||
Log.Warning(ex, "Couldn't unban user {0} in guild {1}", userId, guildId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Couldn't remove role from user {0} in guild {1}", userId, guildId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// unmute the user, this will also remove the timer from the db
|
||||
await UnmuteUser(guildId, userId, _client.CurrentUser, reason: "Timed mute expired");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RemoveTimerFromDb(guildId, userId, type); // if unmute errored, just remove unmute from db
|
||||
Log.Warning(ex, "Couldn't unmute user {0} in guild {1}", userId, guildId);
|
||||
}
|
||||
}
|
||||
}, null, after, Timeout.InfiniteTimeSpan);
|
||||
else if (type == TimerType.AddRole)
|
||||
try
|
||||
{
|
||||
RemoveTimerFromDb(guildId, userId, type);
|
||||
StopTimer(guildId, userId, type);
|
||||
var guild = _client.GetGuild(guildId);
|
||||
var user = guild?.GetUser(userId);
|
||||
var role = guild.GetRole(roleId.Value);
|
||||
if (guild != null && user != null && user.Roles.Contains(role))
|
||||
await user.RemoveRoleAsync(role);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Couldn't remove role from user {0} in guild {1}", userId, guildId);
|
||||
}
|
||||
else
|
||||
try
|
||||
{
|
||||
// unmute the user, this will also remove the timer from the db
|
||||
await UnmuteUser(guildId, userId, _client.CurrentUser, reason: "Timed mute expired");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RemoveTimerFromDb(guildId, userId, type); // if unmute errored, just remove unmute from db
|
||||
Log.Warning(ex, "Couldn't unmute user {0} in guild {1}", userId, guildId);
|
||||
}
|
||||
},
|
||||
null,
|
||||
after,
|
||||
Timeout.InfiniteTimeSpan);
|
||||
|
||||
//add it, or stop the old one and add this one
|
||||
userUnTimers.AddOrUpdate((userId, type), key => toAdd, (key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return toAdd;
|
||||
});
|
||||
userUnTimers.AddOrUpdate((userId, type),
|
||||
key => toAdd,
|
||||
(key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return toAdd;
|
||||
});
|
||||
}
|
||||
|
||||
public void StopTimer(ulong guildId, ulong userId, TimerType type)
|
||||
@@ -434,10 +457,7 @@ public class MuteService : INService
|
||||
if (!Un_Timers.TryGetValue(guildId, out var userTimer))
|
||||
return;
|
||||
|
||||
if (userTimer.TryRemove((userId, type), out var removed))
|
||||
{
|
||||
removed.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
if (userTimer.TryRemove((userId, type), out var removed)) removed.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
|
||||
private void RemoveTimerFromDb(ulong guildId, ulong userId, TimerType type)
|
||||
@@ -454,10 +474,8 @@ public class MuteService : INService
|
||||
var config = uow.GuildConfigsForId(guildId, set => set.Include(x => x.UnbanTimer));
|
||||
toDelete = config.UnbanTimer.FirstOrDefault(x => x.UserId == userId);
|
||||
}
|
||||
if (toDelete != null)
|
||||
{
|
||||
uow.Remove(toDelete);
|
||||
}
|
||||
|
||||
if (toDelete != null) uow.Remove(toDelete);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
@@ -13,13 +13,13 @@ public sealed class PlayingRotateService : INService
|
||||
private readonly DbService _db;
|
||||
private readonly Bot _bot;
|
||||
|
||||
private class TimerState
|
||||
{
|
||||
public int Index { get; set; }
|
||||
}
|
||||
|
||||
public PlayingRotateService(DiscordSocketClient client, DbService db, Bot bot,
|
||||
BotConfigService bss, IEnumerable<IPlaceholderProvider> phProviders, SelfService selfService)
|
||||
public PlayingRotateService(
|
||||
DiscordSocketClient client,
|
||||
DbService db,
|
||||
Bot bot,
|
||||
BotConfigService bss,
|
||||
IEnumerable<IPlaceholderProvider> phProviders,
|
||||
SelfService selfService)
|
||||
{
|
||||
_db = db;
|
||||
_bot = bot;
|
||||
@@ -28,10 +28,7 @@ public sealed class PlayingRotateService : INService
|
||||
|
||||
if (client.ShardId == 0)
|
||||
{
|
||||
_rep = new ReplacementBuilder()
|
||||
.WithClient(client)
|
||||
.WithProviders(phProviders)
|
||||
.Build();
|
||||
_rep = new ReplacementBuilder().WithClient(client).WithProviders(phProviders).Build();
|
||||
|
||||
_t = new(RotatingStatuses, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
||||
}
|
||||
@@ -41,17 +38,14 @@ public sealed class PlayingRotateService : INService
|
||||
{
|
||||
try
|
||||
{
|
||||
var state = (TimerState) objState;
|
||||
var state = (TimerState)objState;
|
||||
|
||||
if (!_bss.Data.RotateStatuses) return;
|
||||
|
||||
IReadOnlyList<RotatingPlayingStatus> rotatingStatuses;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
rotatingStatuses = uow.RotatingStatus
|
||||
.AsNoTracking()
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
rotatingStatuses = uow.RotatingStatus.AsNoTracking().OrderBy(x => x.Id).ToList();
|
||||
}
|
||||
|
||||
if (rotatingStatuses.Count == 0)
|
||||
@@ -76,11 +70,7 @@ public sealed class PlayingRotateService : INService
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toRemove = await uow.RotatingStatus
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsync();
|
||||
var toRemove = await uow.RotatingStatus.AsQueryable().AsNoTracking().Skip(index).FirstOrDefaultAsync();
|
||||
|
||||
if (toRemove is null)
|
||||
return null;
|
||||
@@ -93,7 +83,7 @@ public sealed class PlayingRotateService : INService
|
||||
public async Task AddPlaying(ActivityType t, string status)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toAdd = new RotatingPlayingStatus {Status = status, Type = t};
|
||||
var toAdd = new RotatingPlayingStatus { Status = status, Type = t };
|
||||
uow.Add(toAdd);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
@@ -110,4 +100,9 @@ public sealed class PlayingRotateService : INService
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.RotatingStatus.AsNoTracking().ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private class TimerState
|
||||
{
|
||||
public int Index { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,40 @@
|
||||
#nullable disable
|
||||
using System.Threading.Channels;
|
||||
using NadekoBot.Modules.Administration.Common;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Administration.Common;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public class ProtectionService : INService
|
||||
{
|
||||
public event Func<PunishmentAction, ProtectionType, IGuildUser[], Task> OnAntiProtectionTriggered = delegate
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, AntiRaidStats> _antiRaidGuilds = new();
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, AntiSpamStats> _antiSpamGuilds = new();
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, AntiAltStats> _antiAltGuilds = new();
|
||||
|
||||
public event Func<PunishmentAction, ProtectionType, IGuildUser[], Task> OnAntiProtectionTriggered
|
||||
= delegate { return Task.CompletedTask; };
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly MuteService _mute;
|
||||
private readonly DbService _db;
|
||||
private readonly UserPunishService _punishService;
|
||||
|
||||
private readonly Channel<PunishQueueItem> PunishUserQueue =
|
||||
System.Threading.Channels.Channel.CreateUnbounded<PunishQueueItem>(new()
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = false
|
||||
});
|
||||
|
||||
public ProtectionService(DiscordSocketClient client, Bot bot,
|
||||
MuteService mute, DbService db, UserPunishService punishService)
|
||||
{
|
||||
private readonly Channel<PunishQueueItem> PunishUserQueue =
|
||||
Channel.CreateUnbounded<PunishQueueItem>(new() { SingleReader = true, SingleWriter = false });
|
||||
|
||||
public ProtectionService(
|
||||
DiscordSocketClient client,
|
||||
Bot bot,
|
||||
MuteService mute,
|
||||
DbService db,
|
||||
UserPunishService punishService)
|
||||
{
|
||||
_client = client;
|
||||
_mute = mute;
|
||||
_db = db;
|
||||
@@ -42,18 +44,15 @@ public class ProtectionService : INService
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
var configs = uow.Set<GuildConfig>()
|
||||
.AsQueryable()
|
||||
.Include(x => x.AntiRaidSetting)
|
||||
.Include(x => x.AntiSpamSetting)
|
||||
.ThenInclude(x => x.IgnoredChannels)
|
||||
.Include(x => x.AntiAltSetting)
|
||||
.Where(x => ids.Contains(x.GuildId))
|
||||
.ToList();
|
||||
.AsQueryable()
|
||||
.Include(x => x.AntiRaidSetting)
|
||||
.Include(x => x.AntiSpamSetting)
|
||||
.ThenInclude(x => x.IgnoredChannels)
|
||||
.Include(x => x.AntiAltSetting)
|
||||
.Where(x => ids.Contains(x.GuildId))
|
||||
.ToList();
|
||||
|
||||
foreach (var gc in configs)
|
||||
{
|
||||
Initialize(gc);
|
||||
}
|
||||
foreach (var gc in configs) Initialize(gc);
|
||||
}
|
||||
|
||||
_client.MessageReceived += HandleAntiSpam;
|
||||
@@ -61,7 +60,7 @@ public class ProtectionService : INService
|
||||
|
||||
bot.JoinedGuild += _bot_JoinedGuild;
|
||||
_client.LeftGuild += _client_LeftGuild;
|
||||
|
||||
|
||||
_ = Task.Run(RunQueue);
|
||||
}
|
||||
|
||||
@@ -75,8 +74,13 @@ public class ProtectionService : INService
|
||||
var gu = item.User;
|
||||
try
|
||||
{
|
||||
await _punishService.ApplyPunishment(gu.Guild, gu, _client.CurrentUser,
|
||||
item.Action, muteTime, item.RoleId, $"{item.Type} Protection");
|
||||
await _punishService.ApplyPunishment(gu.Guild,
|
||||
gu,
|
||||
_client.CurrentUser,
|
||||
item.Action,
|
||||
muteTime,
|
||||
item.RoleId,
|
||||
$"{item.Type} Protection");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -104,11 +108,10 @@ public class ProtectionService : INService
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gcWithData = uow.GuildConfigsForId(gc.GuildId,
|
||||
set => set
|
||||
.Include(x => x.AntiRaidSetting)
|
||||
.Include(x => x.AntiAltSetting)
|
||||
.Include(x => x.AntiSpamSetting)
|
||||
.ThenInclude(x => x.IgnoredChannels));
|
||||
set => set.Include(x => x.AntiRaidSetting)
|
||||
.Include(x => x.AntiAltSetting)
|
||||
.Include(x => x.AntiSpamSetting)
|
||||
.ThenInclude(x => x.IgnoredChannels));
|
||||
|
||||
Initialize(gcWithData);
|
||||
return Task.CompletedTask;
|
||||
@@ -121,7 +124,7 @@ public class ProtectionService : INService
|
||||
|
||||
if (raid != null)
|
||||
{
|
||||
var raidStats = new AntiRaidStats() { AntiRaidSettings = raid };
|
||||
var raidStats = new AntiRaidStats { AntiRaidSettings = raid };
|
||||
_antiRaidGuilds[gc.GuildId] = raidStats;
|
||||
}
|
||||
|
||||
@@ -137,41 +140,38 @@ public class ProtectionService : INService
|
||||
{
|
||||
if (user.IsBot)
|
||||
return Task.CompletedTask;
|
||||
|
||||
|
||||
_antiRaidGuilds.TryGetValue(user.Guild.Id, out var maybeStats);
|
||||
_antiAltGuilds.TryGetValue(user.Guild.Id, out var maybeAlts);
|
||||
|
||||
|
||||
if (maybeStats is null && maybeAlts is null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (maybeAlts is { } alts)
|
||||
{
|
||||
if (user.CreatedAt != default)
|
||||
{
|
||||
var diff = DateTime.UtcNow - user.CreatedAt.UtcDateTime;
|
||||
if (diff < alts.MinAge)
|
||||
{
|
||||
alts.Increment();
|
||||
|
||||
await PunishUsers(
|
||||
alts.Action,
|
||||
|
||||
await PunishUsers(alts.Action,
|
||||
ProtectionType.Alting,
|
||||
alts.ActionDurationMinutes,
|
||||
alts.ActionDurationMinutes,
|
||||
alts.RoleId,
|
||||
user);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (maybeStats is not { } stats || !stats.RaidUsers.Add(user))
|
||||
return;
|
||||
|
||||
|
||||
++stats.UsersCount;
|
||||
|
||||
if (stats.UsersCount >= stats.AntiRaidSettings.UserThreshold)
|
||||
@@ -180,14 +180,13 @@ public class ProtectionService : INService
|
||||
stats.RaidUsers.Clear();
|
||||
var settings = stats.AntiRaidSettings;
|
||||
|
||||
await PunishUsers(settings.Action, ProtectionType.Raiding,
|
||||
settings.PunishDuration, null, users);
|
||||
await PunishUsers(settings.Action, ProtectionType.Raiding, settings.PunishDuration, null, users);
|
||||
}
|
||||
|
||||
await Task.Delay(1000 * stats.AntiRaidSettings.Seconds);
|
||||
|
||||
stats.RaidUsers.TryRemove(user);
|
||||
--stats.UsersCount;
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -208,29 +207,29 @@ public class ProtectionService : INService
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_antiSpamGuilds.TryGetValue(channel.Guild.Id, out var spamSettings) ||
|
||||
spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new()
|
||||
{
|
||||
ChannelId = channel.Id
|
||||
}))
|
||||
if (!_antiSpamGuilds.TryGetValue(channel.Guild.Id, out var spamSettings)
|
||||
|| spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new() { ChannelId = channel.Id }))
|
||||
return;
|
||||
|
||||
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, id => new(msg),
|
||||
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id,
|
||||
id => new(msg),
|
||||
(id, old) =>
|
||||
{
|
||||
old.ApplyNextMessage(msg); return old;
|
||||
old.ApplyNextMessage(msg);
|
||||
return old;
|
||||
});
|
||||
|
||||
if (stats.Count >= spamSettings.AntiSpamSettings.MessageThreshold)
|
||||
{
|
||||
if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats))
|
||||
{
|
||||
stats.Dispose();
|
||||
var settings = spamSettings.AntiSpamSettings;
|
||||
await PunishUsers(settings.Action, ProtectionType.Spamming, settings.MuteTime,
|
||||
settings.RoleId, (IGuildUser)msg.Author);
|
||||
await PunishUsers(settings.Action,
|
||||
ProtectionType.Spamming,
|
||||
settings.MuteTime,
|
||||
settings.RoleId,
|
||||
(IGuildUser)msg.Author);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -240,18 +239,20 @@ public class ProtectionService : INService
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task PunishUsers(PunishmentAction action, ProtectionType pt, int muteTime, ulong? roleId,
|
||||
private async Task PunishUsers(
|
||||
PunishmentAction action,
|
||||
ProtectionType pt,
|
||||
int muteTime,
|
||||
ulong? roleId,
|
||||
params IGuildUser[] gus)
|
||||
{
|
||||
Log.Information(
|
||||
"[{PunishType}] - Punishing [{Count}] users with [{PunishAction}] in {GuildName} guild",
|
||||
Log.Information("[{PunishType}] - Punishing [{Count}] users with [{PunishAction}] in {GuildName} guild",
|
||||
pt,
|
||||
gus.Length,
|
||||
action,
|
||||
gus[0].Guild.Name);
|
||||
|
||||
|
||||
foreach (var gu in gus)
|
||||
{
|
||||
await PunishUserQueue.Writer.WriteAsync(new()
|
||||
{
|
||||
Action = action,
|
||||
@@ -260,24 +261,27 @@ public class ProtectionService : INService
|
||||
MuteTime = muteTime,
|
||||
RoleId = roleId
|
||||
});
|
||||
}
|
||||
|
||||
_ = OnAntiProtectionTriggered(action, pt, gus);
|
||||
}
|
||||
|
||||
public async Task<AntiRaidStats> StartAntiRaidAsync(ulong guildId, int userThreshold, int seconds,
|
||||
PunishmentAction action, int minutesDuration)
|
||||
public async Task<AntiRaidStats> StartAntiRaidAsync(
|
||||
ulong guildId,
|
||||
int userThreshold,
|
||||
int seconds,
|
||||
PunishmentAction action,
|
||||
int minutesDuration)
|
||||
{
|
||||
var g = _client.GetGuild(guildId);
|
||||
await _mute.GetMuteRole(g);
|
||||
|
||||
if (action == PunishmentAction.AddRole)
|
||||
return null;
|
||||
|
||||
|
||||
if (!IsDurationAllowed(action))
|
||||
minutesDuration = 0;
|
||||
|
||||
var stats = new AntiRaidStats()
|
||||
var stats = new AntiRaidStats
|
||||
{
|
||||
AntiRaidSettings = new()
|
||||
{
|
||||
@@ -310,6 +314,7 @@ public class ProtectionService : INService
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -317,24 +322,26 @@ public class ProtectionService : INService
|
||||
{
|
||||
if (_antiSpamGuilds.TryRemove(guildId, out var removed))
|
||||
{
|
||||
foreach (var (_, val) in removed.UserStats)
|
||||
{
|
||||
val.Dispose();
|
||||
}
|
||||
|
||||
foreach (var (_, val) in removed.UserStats) val.Dispose();
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting)
|
||||
.ThenInclude(x => x.IgnoredChannels));
|
||||
var gc = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels));
|
||||
|
||||
gc.AntiSpamSetting = null;
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<AntiSpamStats> StartAntiSpamAsync(ulong guildId, int messageCount, PunishmentAction action,
|
||||
int punishDurationMinutes, ulong? roleId)
|
||||
public async Task<AntiSpamStats> StartAntiSpamAsync(
|
||||
ulong guildId,
|
||||
int messageCount,
|
||||
PunishmentAction action,
|
||||
int punishDurationMinutes,
|
||||
ulong? roleId)
|
||||
{
|
||||
var g = _client.GetGuild(guildId);
|
||||
await _mute.GetMuteRole(g);
|
||||
@@ -349,15 +356,17 @@ public class ProtectionService : INService
|
||||
Action = action,
|
||||
MessageThreshold = messageCount,
|
||||
MuteTime = punishDurationMinutes,
|
||||
RoleId = roleId,
|
||||
RoleId = roleId
|
||||
}
|
||||
};
|
||||
|
||||
stats = _antiSpamGuilds.AddOrUpdate(guildId, stats, (key, old) =>
|
||||
{
|
||||
stats.AntiSpamSettings.IgnoredChannels = old.AntiSpamSettings.IgnoredChannels;
|
||||
return stats;
|
||||
});
|
||||
stats = _antiSpamGuilds.AddOrUpdate(guildId,
|
||||
stats,
|
||||
(key, old) =>
|
||||
{
|
||||
stats.AntiSpamSettings.IgnoredChannels = old.AntiSpamSettings.IgnoredChannels;
|
||||
return stats;
|
||||
});
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting));
|
||||
@@ -373,24 +382,20 @@ public class ProtectionService : INService
|
||||
{
|
||||
gc.AntiSpamSetting = stats.AntiSpamSettings;
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
return stats;
|
||||
}
|
||||
|
||||
public async Task<bool?> AntiSpamIgnoreAsync(ulong guildId, ulong channelId)
|
||||
{
|
||||
var obj = new AntiSpamIgnore()
|
||||
{
|
||||
ChannelId = channelId
|
||||
};
|
||||
var obj = new AntiSpamIgnore { ChannelId = channelId };
|
||||
bool added;
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels));
|
||||
var gc = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels));
|
||||
var spam = gc.AntiSpamSetting;
|
||||
if (spam is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (spam is null) return null;
|
||||
|
||||
if (spam.IgnoredChannels.Add(obj)) // if adding to db is successful
|
||||
{
|
||||
@@ -403,9 +408,7 @@ public class ProtectionService : INService
|
||||
var toRemove = spam.IgnoredChannels.First(x => x.ChannelId == channelId);
|
||||
uow.Set<AntiSpamIgnore>().Remove(toRemove); // remove from db
|
||||
if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
|
||||
{
|
||||
temp.AntiSpamSettings.IgnoredChannels.Remove(toRemove); // remove from local cache
|
||||
}
|
||||
added = false;
|
||||
}
|
||||
|
||||
@@ -437,8 +440,12 @@ public class ProtectionService : INService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartAntiAltAsync(ulong guildId, int minAgeMinutes, PunishmentAction action,
|
||||
int actionDurationMinutes = 0, ulong? roleId = null)
|
||||
public async Task StartAntiAltAsync(
|
||||
ulong guildId,
|
||||
int minAgeMinutes,
|
||||
PunishmentAction action,
|
||||
int actionDurationMinutes = 0,
|
||||
ulong? roleId = null)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiAltSetting));
|
||||
@@ -447,7 +454,7 @@ public class ProtectionService : INService
|
||||
Action = action,
|
||||
ActionDurationMinutes = actionDurationMinutes,
|
||||
MinAge = TimeSpan.FromMinutes(minAgeMinutes),
|
||||
RoleId = roleId,
|
||||
RoleId = roleId
|
||||
};
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
@@ -465,4 +472,4 @@ public class ProtectionService : INService
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,12 @@ public class PruneService : INService
|
||||
private readonly ILogCommandService _logService;
|
||||
|
||||
public PruneService(ILogCommandService logService)
|
||||
=> this._logService = logService;
|
||||
=> _logService = logService;
|
||||
|
||||
public async Task PruneWhere(ITextChannel channel, int amount, Func<IMessage, bool> predicate)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel, nameof(channel));
|
||||
|
||||
|
||||
if (amount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
|
||||
@@ -46,17 +46,16 @@ public class PruneService : INService
|
||||
await Task.WhenAll(Task.Delay(1000), channel.DeleteMessagesAsync(bulkDeletable));
|
||||
|
||||
foreach (var group in singleDeletable.Chunk(5))
|
||||
await Task.WhenAll(
|
||||
Task.Delay(1000),
|
||||
group.Select(x => x.DeleteAsync())
|
||||
.WhenAll()
|
||||
);
|
||||
await Task.WhenAll(Task.Delay(1000), group.Select(x => x.DeleteAsync()).WhenAll());
|
||||
|
||||
//this isn't good, because this still work as if i want to remove only specific user's messages from the last
|
||||
//100 messages, Maybe this needs to be reduced by msgs.Length instead of 100
|
||||
amount -= 50;
|
||||
if(amount > 0)
|
||||
msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).FlattenAsync()).Where(predicate).Take(amount).ToArray();
|
||||
if (amount > 0)
|
||||
msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).FlattenAsync())
|
||||
.Where(predicate)
|
||||
.Take(amount)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -68,4 +67,4 @@ public class PruneService : INService
|
||||
_pruningGuilds.TryRemove(channel.GuildId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
@@ -15,36 +15,34 @@ public class RoleCommandsService : INService
|
||||
private readonly ConcurrentDictionary<ulong, IndexedCollection<ReactionRoleMessage>> _models;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the (Message ID, User ID) of reaction roles that are currently being processed.
|
||||
/// Contains the (Message ID, User ID) of reaction roles that are currently being processed.
|
||||
/// </summary>
|
||||
private readonly ConcurrentHashSet<(ulong, ulong)> _reacting = new();
|
||||
|
||||
public RoleCommandsService(DiscordSocketClient client, DbService db,
|
||||
Bot bot)
|
||||
public RoleCommandsService(DiscordSocketClient client, DbService db, Bot bot)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
#if !GLOBAL_NADEKO
|
||||
_models = bot.AllGuildConfigs.ToDictionary(x => x.GuildId,
|
||||
x => x.ReactionRoleMessages)
|
||||
.ToConcurrent();
|
||||
_models = bot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.ReactionRoleMessages).ToConcurrent();
|
||||
|
||||
_client.ReactionAdded += _client_ReactionAdded;
|
||||
_client.ReactionRemoved += _client_ReactionRemoved;
|
||||
#endif
|
||||
}
|
||||
|
||||
private Task _client_ReactionAdded(Cacheable<IUserMessage, ulong> msg,
|
||||
private Task _client_ReactionAdded(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> chan,
|
||||
SocketReaction reaction)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (!reaction.User.IsSpecified ||
|
||||
reaction.User.Value.IsBot ||
|
||||
reaction.User.Value is not SocketGuildUser gusr ||
|
||||
chan.Value is not SocketGuildChannel gch ||
|
||||
!_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||
if (!reaction.User.IsSpecified
|
||||
|| reaction.User.Value.IsBot
|
||||
|| reaction.User.Value is not SocketGuildUser gusr
|
||||
|| chan.Value is not SocketGuildChannel gch
|
||||
|| !_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||
return;
|
||||
|
||||
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
|
||||
@@ -53,7 +51,8 @@ public class RoleCommandsService : INService
|
||||
return;
|
||||
|
||||
// compare emote names for backwards compatibility :facepalm:
|
||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x
|
||||
=> x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||
|
||||
if (reactionRole != null)
|
||||
{
|
||||
@@ -69,7 +68,12 @@ public class RoleCommandsService : INService
|
||||
|
||||
try
|
||||
{
|
||||
var removeExclusiveTask = RemoveExclusiveReactionRoleAsync(msg, gusr, reaction, conf, reactionRole, CancellationToken.None);
|
||||
var removeExclusiveTask = RemoveExclusiveReactionRoleAsync(msg,
|
||||
gusr,
|
||||
reaction,
|
||||
conf,
|
||||
reactionRole,
|
||||
CancellationToken.None);
|
||||
var addRoleTask = AddReactionRoleAsync(gusr, reactionRole);
|
||||
|
||||
await Task.WhenAll(removeExclusiveTask, addRoleTask);
|
||||
@@ -83,11 +87,9 @@ public class RoleCommandsService : INService
|
||||
else
|
||||
{
|
||||
var dl = await msg.GetOrDownloadAsync();
|
||||
await dl.RemoveReactionAsync(reaction.Emote, dl.Author,
|
||||
new()
|
||||
{
|
||||
RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502
|
||||
});
|
||||
await dl.RemoveReactionAsync(reaction.Emote,
|
||||
dl.Author,
|
||||
new() { RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502 });
|
||||
Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
|
||||
}
|
||||
});
|
||||
@@ -95,7 +97,8 @@ public class RoleCommandsService : INService
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task _client_ReactionRemoved(Cacheable<IUserMessage, ulong> msg,
|
||||
private Task _client_ReactionRemoved(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> chan,
|
||||
SocketReaction reaction)
|
||||
{
|
||||
@@ -103,9 +106,9 @@ public class RoleCommandsService : INService
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!reaction.User.IsSpecified ||
|
||||
reaction.User.Value.IsBot ||
|
||||
reaction.User.Value is not SocketGuildUser gusr)
|
||||
if (!reaction.User.IsSpecified
|
||||
|| reaction.User.Value.IsBot
|
||||
|| reaction.User.Value is not SocketGuildUser gusr)
|
||||
return;
|
||||
|
||||
if (chan.Value is not SocketGuildChannel gch)
|
||||
@@ -119,7 +122,8 @@ public class RoleCommandsService : INService
|
||||
if (conf is null)
|
||||
return;
|
||||
|
||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x
|
||||
=> x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||
|
||||
if (reactionRole != null)
|
||||
{
|
||||
@@ -143,20 +147,17 @@ public class RoleCommandsService : INService
|
||||
using var uow = _db.GetDbContext();
|
||||
var table = uow.GetTable<ReactionRoleMessage>();
|
||||
table.Delete(x => x.MessageId == rrm.MessageId);
|
||||
|
||||
var gc = uow.GuildConfigsForId(id, set => set
|
||||
.Include(x => x.ReactionRoleMessages)
|
||||
.ThenInclude(x => x.ReactionRoles));
|
||||
|
||||
|
||||
var gc = uow.GuildConfigsForId(id,
|
||||
set => set.Include(x => x.ReactionRoleMessages).ThenInclude(x => x.ReactionRoles));
|
||||
|
||||
if (gc.ReactionRoleMessages.Count >= 10)
|
||||
return false;
|
||||
|
||||
|
||||
gc.ReactionRoleMessages.Add(rrm);
|
||||
uow.SaveChanges();
|
||||
|
||||
_models.AddOrUpdate(id,
|
||||
gc.ReactionRoleMessages,
|
||||
delegate { return gc.ReactionRoleMessages; });
|
||||
|
||||
_models.AddOrUpdate(id, gc.ReactionRoleMessages, delegate { return gc.ReactionRoleMessages; });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -164,19 +165,15 @@ public class RoleCommandsService : INService
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(id,
|
||||
set => set.Include(x => x.ReactionRoleMessages)
|
||||
.ThenInclude(x => x.ReactionRoles));
|
||||
uow.Set<ReactionRole>()
|
||||
.RemoveRange(gc.ReactionRoleMessages[index].ReactionRoles);
|
||||
set => set.Include(x => x.ReactionRoleMessages).ThenInclude(x => x.ReactionRoles));
|
||||
uow.Set<ReactionRole>().RemoveRange(gc.ReactionRoleMessages[index].ReactionRoles);
|
||||
gc.ReactionRoleMessages.RemoveAt(index);
|
||||
_models.AddOrUpdate(id,
|
||||
gc.ReactionRoleMessages,
|
||||
delegate { return gc.ReactionRoleMessages; });
|
||||
_models.AddOrUpdate(id, gc.ReactionRoleMessages, delegate { return gc.ReactionRoleMessages; });
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a reaction role to the specified user.
|
||||
/// Adds a reaction role to the specified user.
|
||||
/// </summary>
|
||||
/// <param name="user">A Discord guild user.</param>
|
||||
/// <param name="dbRero">The database settings of this reaction role.</param>
|
||||
@@ -184,13 +181,11 @@ public class RoleCommandsService : INService
|
||||
{
|
||||
var toAdd = user.Guild.GetRole(dbRero.RoleId);
|
||||
|
||||
return toAdd != null && !user.Roles.Contains(toAdd)
|
||||
? user.AddRoleAsync(toAdd)
|
||||
: Task.CompletedTask;
|
||||
return toAdd != null && !user.Roles.Contains(toAdd) ? user.AddRoleAsync(toAdd) : Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the exclusive reaction roles and reactions from the specified user.
|
||||
/// Removes the exclusive reaction roles and reactions from the specified user.
|
||||
/// </summary>
|
||||
/// <param name="reactionMessage">The Discord message that contains the reaction roles.</param>
|
||||
/// <param name="user">A Discord guild user.</param>
|
||||
@@ -200,14 +195,20 @@ public class RoleCommandsService : INService
|
||||
/// <param name="cToken">A cancellation token to cancel the operation.</param>
|
||||
/// <exception cref="OperationCanceledException">Occurs when the operation is cancelled before it began.</exception>
|
||||
/// <exception cref="TaskCanceledException">Occurs when the operation is cancelled while it's still executing.</exception>
|
||||
private Task RemoveExclusiveReactionRoleAsync(Cacheable<IUserMessage, ulong> reactionMessage, SocketGuildUser user, SocketReaction reaction, ReactionRoleMessage dbReroMsg, ReactionRole dbRero, CancellationToken cToken = default)
|
||||
private Task RemoveExclusiveReactionRoleAsync(
|
||||
Cacheable<IUserMessage, ulong> reactionMessage,
|
||||
SocketGuildUser user,
|
||||
SocketReaction reaction,
|
||||
ReactionRoleMessage dbReroMsg,
|
||||
ReactionRole dbRero,
|
||||
CancellationToken cToken = default)
|
||||
{
|
||||
cToken.ThrowIfCancellationRequested();
|
||||
|
||||
var roleIds = dbReroMsg.ReactionRoles.Select(x => x.RoleId)
|
||||
.Where(x => x != dbRero.RoleId)
|
||||
.Select(x => user.Guild.GetRole(x))
|
||||
.Where(x => x != null);
|
||||
.Where(x => x != dbRero.RoleId)
|
||||
.Select(x => user.Guild.GetRole(x))
|
||||
.Where(x => x != null);
|
||||
|
||||
var removeReactionsTask = RemoveOldReactionsAsync(reactionMessage, user, reaction, cToken);
|
||||
|
||||
@@ -217,7 +218,7 @@ public class RoleCommandsService : INService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes old reactions from an exclusive reaction role.
|
||||
/// Removes old reactions from an exclusive reaction role.
|
||||
/// </summary>
|
||||
/// <param name="reactionMessage">The Discord message that contains the reaction roles.</param>
|
||||
/// <param name="user">A Discord guild user.</param>
|
||||
@@ -225,7 +226,11 @@ public class RoleCommandsService : INService
|
||||
/// <param name="cToken">A cancellation token to cancel the operation.</param>
|
||||
/// <exception cref="OperationCanceledException">Occurs when the operation is cancelled before it began.</exception>
|
||||
/// <exception cref="TaskCanceledException">Occurs when the operation is cancelled while it's still executing.</exception>
|
||||
private async Task RemoveOldReactionsAsync(Cacheable<IUserMessage, ulong> reactionMessage, SocketGuildUser user, SocketReaction reaction, CancellationToken cToken = default)
|
||||
private async Task RemoveOldReactionsAsync(
|
||||
Cacheable<IUserMessage, ulong> reactionMessage,
|
||||
SocketGuildUser user,
|
||||
SocketReaction reaction,
|
||||
CancellationToken cToken = default)
|
||||
{
|
||||
cToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -236,8 +241,10 @@ public class RoleCommandsService : INService
|
||||
{
|
||||
if (r.Key.Name == reaction.Emote.Name)
|
||||
continue;
|
||||
try { await dl.RemoveReactionAsync(r.Key, user); } catch { }
|
||||
try { await dl.RemoveReactionAsync(r.Key, user); }
|
||||
catch { }
|
||||
|
||||
await Task.Delay(100, cToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,32 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Xp;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public class SelfAssignedRolesService : INService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
|
||||
public enum RemoveResult
|
||||
{
|
||||
Removed, // successfully removed
|
||||
Err_Not_Assignable, // not assignable (error)
|
||||
Err_Not_Have, // you don't have a role you want to remove (error)
|
||||
Err_Not_Perms, // bot doesn't have perms (error)
|
||||
}
|
||||
|
||||
public enum AssignResult
|
||||
{
|
||||
Assigned, // successfully removed
|
||||
Err_Not_Assignable, // not assignable (error)
|
||||
Err_Already_Have, // you already have that role (error)
|
||||
Err_Not_Perms, // bot doesn't have perms (error)
|
||||
Err_Lvl_Req, // you are not required level (error)
|
||||
Err_Lvl_Req // you are not required level (error)
|
||||
}
|
||||
|
||||
public enum RemoveResult
|
||||
{
|
||||
Removed, // successfully removed
|
||||
Err_Not_Assignable, // not assignable (error)
|
||||
Err_Not_Have, // you don't have a role you want to remove (error)
|
||||
Err_Not_Perms // bot doesn't have perms (error)
|
||||
}
|
||||
|
||||
private readonly DbService _db;
|
||||
|
||||
public SelfAssignedRolesService(DbService db)
|
||||
=> _db = db;
|
||||
|
||||
@@ -34,17 +34,9 @@ public class SelfAssignedRolesService : INService
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
|
||||
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id)) return false;
|
||||
|
||||
uow.SelfAssignableRoles.Add(new()
|
||||
{
|
||||
Group = @group,
|
||||
RoleId = role.Id,
|
||||
GuildId = role.Guild.Id
|
||||
});
|
||||
uow.SelfAssignableRoles.Add(new() { Group = group, RoleId = role.Id, GuildId = role.Guild.Id });
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
}
|
||||
@@ -72,31 +64,20 @@ public class SelfAssignedRolesService : INService
|
||||
|
||||
var theRoleYouWant = roles.FirstOrDefault(r => r.RoleId == role.Id);
|
||||
if (theRoleYouWant is null)
|
||||
{
|
||||
return (AssignResult.Err_Not_Assignable, autoDelete, null);
|
||||
}
|
||||
else if (theRoleYouWant.LevelRequirement > userLevelData.Level)
|
||||
{
|
||||
if (theRoleYouWant.LevelRequirement > userLevelData.Level)
|
||||
return (AssignResult.Err_Lvl_Req, autoDelete, theRoleYouWant.LevelRequirement);
|
||||
}
|
||||
else if (guildUser.RoleIds.Contains(role.Id))
|
||||
{
|
||||
return (AssignResult.Err_Already_Have, autoDelete, null);
|
||||
}
|
||||
if (guildUser.RoleIds.Contains(role.Id)) return (AssignResult.Err_Already_Have, autoDelete, null);
|
||||
|
||||
var roleIds = roles
|
||||
.Where(x => x.Group == theRoleYouWant.Group)
|
||||
.Select(x => x.RoleId).ToArray();
|
||||
var roleIds = roles.Where(x => x.Group == theRoleYouWant.Group).Select(x => x.RoleId).ToArray();
|
||||
if (exclusive)
|
||||
{
|
||||
var sameRoles = guildUser.RoleIds
|
||||
.Where(r => roleIds.Contains(r));
|
||||
var sameRoles = guildUser.RoleIds.Where(r => roleIds.Contains(r));
|
||||
|
||||
foreach (var roleId in sameRoles)
|
||||
{
|
||||
var sameRole = guildUser.Guild.GetRole(roleId);
|
||||
if (sameRole != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await guildUser.RemoveRoleAsync(sameRole);
|
||||
@@ -106,9 +87,9 @@ public class SelfAssignedRolesService : INService
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await guildUser.AddRoleAsync(role);
|
||||
@@ -126,7 +107,7 @@ public class SelfAssignedRolesService : INService
|
||||
var set = false;
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, y => y.Include(x => x.SelfAssignableRoleGroupNames));
|
||||
var toUpdate = gc.SelfAssignableRoleGroupNames.FirstOrDefault(x => x.Number == @group);
|
||||
var toUpdate = gc.SelfAssignableRoleGroupNames.FirstOrDefault(x => x.Number == group);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
@@ -135,11 +116,7 @@ public class SelfAssignedRolesService : INService
|
||||
}
|
||||
else if (toUpdate is null)
|
||||
{
|
||||
gc.SelfAssignableRoleGroupNames.Add(new()
|
||||
{
|
||||
Name = name,
|
||||
Number = @group,
|
||||
});
|
||||
gc.SelfAssignableRoleGroupNames.Add(new() { Name = name, Number = group });
|
||||
set = true;
|
||||
}
|
||||
else
|
||||
@@ -158,13 +135,8 @@ public class SelfAssignedRolesService : INService
|
||||
var (autoDelete, _, roles) = GetAdAndRoles(guildUser.Guild.Id);
|
||||
|
||||
if (roles.FirstOrDefault(r => r.RoleId == role.Id) is null)
|
||||
{
|
||||
return (RemoveResult.Err_Not_Assignable, autoDelete);
|
||||
}
|
||||
if (!guildUser.RoleIds.Contains(role.Id))
|
||||
{
|
||||
return (RemoveResult.Err_Not_Have, autoDelete);
|
||||
}
|
||||
if (!guildUser.RoleIds.Contains(role.Id)) return (RemoveResult.Err_Not_Have, autoDelete);
|
||||
try
|
||||
{
|
||||
await guildUser.RemoveRoleAsync(role);
|
||||
@@ -226,7 +198,8 @@ public class SelfAssignedRolesService : INService
|
||||
return areExclusive;
|
||||
}
|
||||
|
||||
public (bool Exclusive, IEnumerable<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary<int, string> GroupNames) GetRoles(IGuild guild)
|
||||
public (bool Exclusive, IEnumerable<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary<int, string> GroupNames
|
||||
) GetRoles(IGuild guild)
|
||||
{
|
||||
var exclusive = false;
|
||||
|
||||
@@ -238,12 +211,11 @@ public class SelfAssignedRolesService : INService
|
||||
exclusive = gc.ExclusiveSelfAssignedRoles;
|
||||
groupNames = gc.SelfAssignableRoleGroupNames.ToDictionary(x => x.Number, x => x.Name);
|
||||
var roleModels = uow.SelfAssignableRoles.GetFromGuild(guild.Id);
|
||||
roles = roleModels
|
||||
.Select(x => (Model: x, Role: guild.GetRole(x.RoleId)));
|
||||
roles = roleModels.Select(x => (Model: x, Role: guild.GetRole(x.RoleId)));
|
||||
uow.SelfAssignableRoles.RemoveRange(roles.Where(x => x.Role is null).Select(x => x.Model).ToArray());
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return (exclusive, roles.Where(x => x.Role != null), groupNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#nullable disable
|
||||
using System.Collections.Immutable;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
@@ -56,58 +56,49 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
_activitySetKey = new("activity.set");
|
||||
_imagesReloadKey = new("images.reload");
|
||||
_guildLeaveKey = new("guild.leave");
|
||||
|
||||
|
||||
HandleStatusChanges();
|
||||
|
||||
if (_client.ShardId == 0)
|
||||
{
|
||||
_pubSub.Sub(_imagesReloadKey, async _ => await _imgs.Reload());
|
||||
}
|
||||
|
||||
_pubSub.Sub(_guildLeaveKey, async input =>
|
||||
{
|
||||
var guildStr = input.ToString().Trim().ToUpperInvariant();
|
||||
if (string.IsNullOrWhiteSpace(guildStr))
|
||||
return;
|
||||
if (_client.ShardId == 0) _pubSub.Sub(_imagesReloadKey, async _ => await _imgs.Reload());
|
||||
|
||||
var server = _client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr
|
||||
|| g.Name.Trim().ToUpperInvariant() == guildStr);
|
||||
if (server is null)
|
||||
_pubSub.Sub(_guildLeaveKey,
|
||||
async input =>
|
||||
{
|
||||
return;
|
||||
}
|
||||
var guildStr = input.ToString().Trim().ToUpperInvariant();
|
||||
if (string.IsNullOrWhiteSpace(guildStr))
|
||||
return;
|
||||
|
||||
if (server.OwnerId != _client.CurrentUser.Id)
|
||||
{
|
||||
await server.LeaveAsync();
|
||||
Log.Information($"Left server {server.Name} [{server.Id}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
await server.DeleteAsync();
|
||||
Log.Information($"Deleted server {server.Name} [{server.Id}]");
|
||||
}
|
||||
});
|
||||
var server = _client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr
|
||||
|| g.Name.Trim().ToUpperInvariant() == guildStr);
|
||||
if (server is null) return;
|
||||
|
||||
if (server.OwnerId != _client.CurrentUser.Id)
|
||||
{
|
||||
await server.LeaveAsync();
|
||||
Log.Information($"Left server {server.Name} [{server.Id}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
await server.DeleteAsync();
|
||||
Log.Information($"Deleted server {server.Name} [{server.Id}]");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
_autoCommands = uow
|
||||
.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval >= 5)
|
||||
.AsEnumerable()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key,
|
||||
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand)
|
||||
.ToConcurrent())
|
||||
.ToConcurrent();
|
||||
_autoCommands = uow.AutoCommands.AsNoTracking()
|
||||
.Where(x => x.Interval >= 5)
|
||||
.AsEnumerable()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key,
|
||||
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
|
||||
.ToConcurrent();
|
||||
|
||||
var startupCommands = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
|
||||
foreach (var cmd in startupCommands)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ExecuteCommand(cmd);
|
||||
@@ -115,19 +106,12 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_client.ShardId == 0)
|
||||
{
|
||||
await LoadOwnerChannels();
|
||||
}
|
||||
if (_client.ShardId == 0) await LoadOwnerChannels();
|
||||
}
|
||||
|
||||
private Timer TimerFromAutoCommand(AutoCommand x)
|
||||
=> new(async obj => await ExecuteCommand((AutoCommand) obj),
|
||||
x,
|
||||
x.Interval * 1000,
|
||||
x.Interval * 1000);
|
||||
=> new(async obj => await ExecuteCommand((AutoCommand)obj), x, x.Interval * 1000, x.Interval * 1000);
|
||||
|
||||
private async Task ExecuteCommand(AutoCommand cmd)
|
||||
{
|
||||
@@ -136,7 +120,7 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
if (cmd.GuildId is null)
|
||||
return;
|
||||
|
||||
var guildShard = (int) ((cmd.GuildId.Value >> 22) % (ulong) _creds.TotalShards);
|
||||
var guildShard = (int)((cmd.GuildId.Value >> 22) % (ulong)_creds.TotalShards);
|
||||
if (guildShard != _client.ShardId)
|
||||
return;
|
||||
var prefix = _cmdHandler.GetPrefix(cmd.GuildId);
|
||||
@@ -162,34 +146,26 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
if (cmd.Interval >= 5)
|
||||
{
|
||||
var autos = _autoCommands.GetOrAdd(cmd.GuildId, new ConcurrentDictionary<int, Timer>());
|
||||
autos.AddOrUpdate(cmd.Id, key => TimerFromAutoCommand(cmd), (key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return TimerFromAutoCommand(cmd);
|
||||
});
|
||||
autos.AddOrUpdate(cmd.Id,
|
||||
key => TimerFromAutoCommand(cmd),
|
||||
(key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return TimerFromAutoCommand(cmd);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<AutoCommand> GetStartupCommands()
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow
|
||||
.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval == 0)
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
return uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0).OrderBy(x => x.Id).ToList();
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<AutoCommand> GetAutoCommands()
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow
|
||||
.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval >= 5)
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
return uow.AutoCommands.AsNoTracking().Where(x => x.Interval >= 5).OrderBy(x => x.Id).ToList();
|
||||
}
|
||||
|
||||
private async Task LoadOwnerChannels()
|
||||
@@ -204,8 +180,8 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
}));
|
||||
|
||||
ownerChannels = channels.Where(x => x != null)
|
||||
.ToDictionary(x => x.Recipient.Id, x => x)
|
||||
.ToImmutableDictionary();
|
||||
.ToDictionary(x => x.Recipient.Id, x => x)
|
||||
.ToImmutableDictionary();
|
||||
|
||||
if (!ownerChannels.Any())
|
||||
Log.Warning(
|
||||
@@ -214,7 +190,7 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
Log.Information($"Created {ownerChannels.Count} out of {_creds.OwnerIds.Count} owner message channels.");
|
||||
}
|
||||
|
||||
public Task LeaveGuild(string guildStr)
|
||||
public Task LeaveGuild(string guildStr)
|
||||
=> _pubSub.Pub(_guildLeaveKey, guildStr);
|
||||
|
||||
// forwards dms
|
||||
@@ -223,25 +199,21 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
var bs = _bss.Data;
|
||||
if (msg.Channel is IDMChannel && bs.ForwardMessages && ownerChannels.Any())
|
||||
{
|
||||
var title = _strings.GetText(strs.dm_from) +
|
||||
$" [{msg.Author}]({msg.Author.Id})";
|
||||
var title = _strings.GetText(strs.dm_from) + $" [{msg.Author}]({msg.Author.Id})";
|
||||
|
||||
var attachamentsTxt = _strings.GetText(strs.attachments);
|
||||
|
||||
var toSend = msg.Content;
|
||||
|
||||
if (msg.Attachments.Count > 0)
|
||||
{
|
||||
toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" +
|
||||
string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl));
|
||||
}
|
||||
toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n"
|
||||
+ string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl));
|
||||
|
||||
if (bs.ForwardToAllOwners)
|
||||
{
|
||||
var allOwnerChannels = ownerChannels.Values;
|
||||
|
||||
foreach (var ownerCh in allOwnerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id))
|
||||
{
|
||||
try
|
||||
{
|
||||
await ownerCh.SendConfirmAsync(_eb, title, toSend);
|
||||
@@ -250,13 +222,11 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
{
|
||||
Log.Warning("Can't contact owner with id {0}", ownerCh.Recipient.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var firstOwnerChannel = ownerChannels.Values.First();
|
||||
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await firstOwnerChannel.SendConfirmAsync(_eb, title, toSend);
|
||||
@@ -265,7 +235,6 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,11 +242,7 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
public bool RemoveStartupCommand(int index, out AutoCommand cmd)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
cmd = uow.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval == 0)
|
||||
.Skip(index)
|
||||
.FirstOrDefault();
|
||||
cmd = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0).Skip(index).FirstOrDefault();
|
||||
|
||||
if (cmd != null)
|
||||
{
|
||||
@@ -288,15 +253,11 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public bool RemoveAutoCommand(int index, out AutoCommand cmd)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
cmd = uow.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval >= 5)
|
||||
.Skip(index)
|
||||
.FirstOrDefault();
|
||||
cmd = uow.AutoCommands.AsNoTracking().Where(x => x.Interval >= 5).Skip(index).FirstOrDefault();
|
||||
|
||||
if (cmd != null)
|
||||
{
|
||||
@@ -337,16 +298,13 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
public void ClearStartupCommands()
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var toRemove = uow
|
||||
.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval == 0);
|
||||
var toRemove = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
|
||||
|
||||
uow.AutoCommands.RemoveRange(toRemove);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
public Task ReloadImagesAsync()
|
||||
public Task ReloadImagesAsync()
|
||||
=> _pubSub.Pub(_imagesReloadKey, true);
|
||||
|
||||
public bool ForwardMessages()
|
||||
@@ -363,22 +321,23 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
_bss.ModifyConfig(config => { isToAll = config.ForwardToAllOwners = !config.ForwardToAllOwners; });
|
||||
return isToAll;
|
||||
}
|
||||
|
||||
private void HandleStatusChanges()
|
||||
=> _pubSub.Sub(_activitySetKey, async data =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await _client.SetGameAsync(data.Name, data.Link, type: data.Type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error setting activity");
|
||||
}
|
||||
});
|
||||
|
||||
public Task SetGameAsync(string game, ActivityType type)
|
||||
=> _pubSub.Pub(_activitySetKey, new() {Name = game, Link = null, Type = type});
|
||||
private void HandleStatusChanges()
|
||||
=> _pubSub.Sub(_activitySetKey,
|
||||
async data =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await _client.SetGameAsync(data.Name, data.Link, data.Type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error setting activity");
|
||||
}
|
||||
});
|
||||
|
||||
public Task SetGameAsync(string game, ActivityType type)
|
||||
=> _pubSub.Pub(_activitySetKey, new() { Name = game, Link = null, Type = type });
|
||||
|
||||
public Task SetStreamAsync(string name, string link)
|
||||
=> _pubSub.Pub(_activitySetKey, new() { Name = name, Link = link, Type = ActivityType.Streaming });
|
||||
@@ -389,4 +348,4 @@ public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
public string Link { get; init; }
|
||||
public ActivityType Type { get; init; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.TypeReaders.Models;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Permissions.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
@@ -16,7 +16,11 @@ public class UserPunishService : INService
|
||||
private readonly BotConfigService _bcs;
|
||||
private readonly Timer _warnExpiryTimer;
|
||||
|
||||
public UserPunishService(MuteService mute, DbService db, BlacklistService blacklistService, BotConfigService bcs)
|
||||
public UserPunishService(
|
||||
MuteService mute,
|
||||
DbService db,
|
||||
BlacklistService blacklistService,
|
||||
BotConfigService bcs)
|
||||
{
|
||||
_mute = mute;
|
||||
_db = db;
|
||||
@@ -24,16 +28,24 @@ public class UserPunishService : INService
|
||||
_bcs = bcs;
|
||||
|
||||
_warnExpiryTimer = new(async _ =>
|
||||
{
|
||||
await CheckAllWarnExpiresAsync();
|
||||
}, null, TimeSpan.FromSeconds(0), TimeSpan.FromHours(12));
|
||||
{
|
||||
await CheckAllWarnExpiresAsync();
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromSeconds(0),
|
||||
TimeSpan.FromHours(12));
|
||||
}
|
||||
|
||||
public async Task<WarningPunishment> Warn(IGuild guild, ulong userId, IUser mod, int weight, string reason)
|
||||
public async Task<WarningPunishment> Warn(
|
||||
IGuild guild,
|
||||
ulong userId,
|
||||
IUser mod,
|
||||
int weight,
|
||||
string reason)
|
||||
{
|
||||
if (weight <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(weight));
|
||||
|
||||
|
||||
var modName = mod.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
@@ -41,28 +53,25 @@ public class UserPunishService : INService
|
||||
|
||||
var guildId = guild.Id;
|
||||
|
||||
var warn = new Warning()
|
||||
var warn = new Warning
|
||||
{
|
||||
UserId = userId,
|
||||
GuildId = guildId,
|
||||
Forgiven = false,
|
||||
Reason = reason,
|
||||
Moderator = modName,
|
||||
Weight = weight,
|
||||
Weight = weight
|
||||
};
|
||||
|
||||
var warnings = 1;
|
||||
List<WarningPunishment> ps;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments))
|
||||
.WarnPunishments;
|
||||
ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
|
||||
|
||||
warnings += uow
|
||||
.Warnings
|
||||
.ForId(guildId, userId)
|
||||
.Where(w => !w.Forgiven && w.UserId == userId)
|
||||
.Sum(x => x.Weight);
|
||||
warnings += uow.Warnings.ForId(guildId, userId)
|
||||
.Where(w => !w.Forgiven && w.UserId == userId)
|
||||
.Sum(x => x.Weight);
|
||||
|
||||
uow.Warnings.Add(warn);
|
||||
|
||||
@@ -84,13 +93,18 @@ public class UserPunishService : INService
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task ApplyPunishment(IGuild guild, IGuildUser user, IUser mod, PunishmentAction p, int minutes,
|
||||
ulong? roleId, string reason)
|
||||
public async Task ApplyPunishment(
|
||||
IGuild guild,
|
||||
IGuildUser user,
|
||||
IUser mod,
|
||||
PunishmentAction p,
|
||||
int minutes,
|
||||
ulong? roleId,
|
||||
string reason)
|
||||
{
|
||||
|
||||
if (!await CheckPermission(guild, p))
|
||||
return;
|
||||
|
||||
|
||||
switch (p)
|
||||
{
|
||||
case PunishmentAction.Mute:
|
||||
@@ -121,7 +135,7 @@ public class UserPunishService : INService
|
||||
await _mute.TimedBan(user.Guild, user, TimeSpan.FromMinutes(minutes), reason);
|
||||
break;
|
||||
case PunishmentAction.Softban:
|
||||
await guild.AddBanAsync(user, 7, reason: $"Softban | {reason}");
|
||||
await guild.AddBanAsync(user, 7, $"Softban | {reason}");
|
||||
try
|
||||
{
|
||||
await guild.RemoveBanAsync(user);
|
||||
@@ -151,22 +165,19 @@ public class UserPunishService : INService
|
||||
Log.Warning($"Can't find role {roleId.Value} on server {guild.Id} to apply punishment.");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to prevent the bot from hitting 403's when it needs to
|
||||
/// apply punishments with insufficient permissions
|
||||
/// Used to prevent the bot from hitting 403's when it needs to
|
||||
/// apply punishments with insufficient permissions
|
||||
/// </summary>
|
||||
/// <param name="guild">Guild the punishment is applied in</param>
|
||||
/// <param name="punish">Punishment to apply</param>
|
||||
/// <returns>Whether the bot has sufficient permissions</returns>
|
||||
private async Task<bool> CheckPermission(IGuild guild, PunishmentAction punish)
|
||||
{
|
||||
|
||||
var botUser = await guild.GetCurrentUserAsync();
|
||||
switch (punish)
|
||||
{
|
||||
@@ -194,21 +205,19 @@ public class UserPunishService : INService
|
||||
public async Task CheckAllWarnExpiresAsync()
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var cleared = await uow.Database.ExecuteSqlRawAsync($@"UPDATE Warnings
|
||||
var cleared = await uow.Database.ExecuteSqlRawAsync(@"UPDATE Warnings
|
||||
SET Forgiven = 1,
|
||||
ForgivenBy = 'Expiry'
|
||||
WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND WarnExpireAction = 0)
|
||||
AND Forgiven = 0
|
||||
AND DateAdded < datetime('now', (SELECT '-' || WarnExpireHours || ' hours' FROM GuildConfigs as gc WHERE gc.GuildId = Warnings.GuildId));");
|
||||
|
||||
var deleted = await uow.Database.ExecuteSqlRawAsync($@"DELETE FROM Warnings
|
||||
var deleted = await uow.Database.ExecuteSqlRawAsync(@"DELETE FROM Warnings
|
||||
WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND WarnExpireAction = 1)
|
||||
AND DateAdded < datetime('now', (SELECT '-' || WarnExpireHours || ' hours' FROM GuildConfigs as gc WHERE gc.GuildId = Warnings.GuildId));");
|
||||
|
||||
if(cleared > 0 || deleted > 0)
|
||||
{
|
||||
if (cleared > 0 || deleted > 0)
|
||||
Log.Information($"Cleared {cleared} warnings and deleted {deleted} warnings due to expiry.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckWarnExpiresAsync(ulong guildId)
|
||||
@@ -221,20 +230,16 @@ WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND
|
||||
|
||||
var hours = $"{-config.WarnExpireHours} hours";
|
||||
if (config.WarnExpireAction == WarnExpireAction.Clear)
|
||||
{
|
||||
await uow.Database.ExecuteSqlInterpolatedAsync($@"UPDATE warnings
|
||||
SET Forgiven = 1,
|
||||
ForgivenBy = 'Expiry'
|
||||
WHERE GuildId={guildId}
|
||||
AND Forgiven = 0
|
||||
AND DateAdded < datetime('now', {hours})");
|
||||
}
|
||||
else if (config.WarnExpireAction == WarnExpireAction.Delete)
|
||||
{
|
||||
await uow.Database.ExecuteSqlInterpolatedAsync($@"DELETE FROM warnings
|
||||
WHERE GuildId={guildId}
|
||||
AND DateAdded < datetime('now', {hours})");
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
@@ -245,7 +250,7 @@ WHERE GuildId={guildId}
|
||||
var config = uow.GuildConfigsForId(guildId, set => set);
|
||||
return Task.FromResult(config.WarnExpireHours / 24);
|
||||
}
|
||||
|
||||
|
||||
public async Task WarnExpireAsync(ulong guildId, int days, bool delete)
|
||||
{
|
||||
await using (var uow = _db.GetDbContext())
|
||||
@@ -276,23 +281,28 @@ WHERE GuildId={guildId}
|
||||
return uow.Warnings.ForId(gid, userId);
|
||||
}
|
||||
|
||||
public async Task<bool> WarnClearAsync(ulong guildId, ulong userId, int index, string moderator)
|
||||
public async Task<bool> WarnClearAsync(
|
||||
ulong guildId,
|
||||
ulong userId,
|
||||
int index,
|
||||
string moderator)
|
||||
{
|
||||
var toReturn = true;
|
||||
await using var uow = _db.GetDbContext();
|
||||
if (index == 0)
|
||||
{
|
||||
await uow.Warnings.ForgiveAll(guildId, userId, moderator);
|
||||
}
|
||||
else
|
||||
{
|
||||
toReturn = uow.Warnings.Forgive(guildId, userId, moderator, index - 1);
|
||||
}
|
||||
uow.SaveChanges();
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public bool WarnPunish(ulong guildId, int number, PunishmentAction punish, StoopidTime time, IRole role = null)
|
||||
public bool WarnPunish(
|
||||
ulong guildId,
|
||||
int number,
|
||||
PunishmentAction punish,
|
||||
StoopidTime time,
|
||||
IRole role = null)
|
||||
{
|
||||
// these 3 don't make sense with time
|
||||
if (punish is PunishmentAction.Softban or PunishmentAction.Kick or PunishmentAction.RemoveRoles && time != null)
|
||||
@@ -311,7 +321,7 @@ WHERE GuildId={guildId}
|
||||
Count = number,
|
||||
Punishment = punish,
|
||||
Time = (int?)time?.Time.TotalMinutes ?? 0,
|
||||
RoleId = punish == PunishmentAction.AddRole ? role.Id : default(ulong?),
|
||||
RoleId = punish == PunishmentAction.AddRole ? role.Id : default(ulong?)
|
||||
});
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
@@ -339,42 +349,37 @@ WHERE GuildId={guildId}
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.GuildConfigsForId(guildId, gc => gc.Include(x => x.WarnPunishments))
|
||||
.WarnPunishments
|
||||
.OrderBy(x => x.Count)
|
||||
.ToArray();
|
||||
.WarnPunishments.OrderBy(x => x.Count)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public (IEnumerable<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill(SocketGuild guild, string people)
|
||||
public (IEnumerable<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill(
|
||||
SocketGuild guild,
|
||||
string people)
|
||||
{
|
||||
var gusers = guild.Users;
|
||||
//get user objects and reasons
|
||||
var bans = people.Split("\n")
|
||||
.Select(x =>
|
||||
{
|
||||
var split = x.Trim().Split(" ");
|
||||
.Select(x =>
|
||||
{
|
||||
var split = x.Trim().Split(" ");
|
||||
|
||||
var reason = string.Join(" ", split.Skip(1));
|
||||
var reason = string.Join(" ", split.Skip(1));
|
||||
|
||||
if (ulong.TryParse(split[0], out var id))
|
||||
return (Original: split[0], Id: id, Reason: reason);
|
||||
if (ulong.TryParse(split[0], out var id))
|
||||
return (Original: split[0], Id: id, Reason: reason);
|
||||
|
||||
return (Original: split[0],
|
||||
Id: gusers
|
||||
.FirstOrDefault(u => u.ToString().ToLowerInvariant() == x)
|
||||
?.Id,
|
||||
Reason: reason);
|
||||
})
|
||||
.ToArray();
|
||||
return (Original: split[0],
|
||||
gusers.FirstOrDefault(u => u.ToString().ToLowerInvariant() == x)?.Id,
|
||||
Reason: reason);
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
//if user is null, means that person couldn't be found
|
||||
var missing = bans
|
||||
.Count(x => !x.Id.HasValue);
|
||||
var missing = bans.Count(x => !x.Id.HasValue);
|
||||
|
||||
//get only data for found users
|
||||
var found = bans
|
||||
.Where(x => x.Id.HasValue)
|
||||
.Select(x => x.Id.Value)
|
||||
.ToList();
|
||||
var found = bans.Where(x => x.Id.HasValue).Select(x => x.Id.Value).ToList();
|
||||
|
||||
_blacklistService.BlacklistUsers(found);
|
||||
|
||||
@@ -384,33 +389,25 @@ WHERE GuildId={guildId}
|
||||
public string GetBanTemplate(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var template = uow.BanTemplates
|
||||
.AsQueryable()
|
||||
.FirstOrDefault(x => x.GuildId == guildId);
|
||||
var template = uow.BanTemplates.AsQueryable().FirstOrDefault(x => x.GuildId == guildId);
|
||||
return template?.Text;
|
||||
}
|
||||
|
||||
public void SetBanTemplate(ulong guildId, string text)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var template = uow.BanTemplates
|
||||
.AsQueryable()
|
||||
.FirstOrDefault(x => x.GuildId == guildId);
|
||||
var template = uow.BanTemplates.AsQueryable().FirstOrDefault(x => x.GuildId == guildId);
|
||||
|
||||
if (text is null)
|
||||
{
|
||||
if (template is null)
|
||||
return;
|
||||
|
||||
|
||||
uow.Remove(template);
|
||||
}
|
||||
else if (template is null)
|
||||
{
|
||||
uow.BanTemplates.Add(new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Text = text,
|
||||
});
|
||||
uow.BanTemplates.Add(new() { GuildId = guildId, Text = text });
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -420,67 +417,66 @@ WHERE GuildId={guildId}
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
public SmartText GetBanUserDmEmbed(ICommandContext context, IGuildUser target, string defaultMessage,
|
||||
string banReason, TimeSpan? duration)
|
||||
=> GetBanUserDmEmbed(
|
||||
(DiscordSocketClient) context.Client,
|
||||
(SocketGuild) context.Guild,
|
||||
(IGuildUser) context.User,
|
||||
public SmartText GetBanUserDmEmbed(
|
||||
ICommandContext context,
|
||||
IGuildUser target,
|
||||
string defaultMessage,
|
||||
string banReason,
|
||||
TimeSpan? duration)
|
||||
=> GetBanUserDmEmbed((DiscordSocketClient)context.Client,
|
||||
(SocketGuild)context.Guild,
|
||||
(IGuildUser)context.User,
|
||||
target,
|
||||
defaultMessage,
|
||||
banReason,
|
||||
duration);
|
||||
|
||||
public SmartText GetBanUserDmEmbed(DiscordSocketClient client, SocketGuild guild,
|
||||
IGuildUser moderator, IGuildUser target, string defaultMessage, string banReason, TimeSpan? duration)
|
||||
public SmartText GetBanUserDmEmbed(
|
||||
DiscordSocketClient client,
|
||||
SocketGuild guild,
|
||||
IGuildUser moderator,
|
||||
IGuildUser target,
|
||||
string defaultMessage,
|
||||
string banReason,
|
||||
TimeSpan? duration)
|
||||
{
|
||||
var template = GetBanTemplate(guild.Id);
|
||||
|
||||
banReason = string.IsNullOrWhiteSpace(banReason)
|
||||
? "-"
|
||||
: banReason;
|
||||
banReason = string.IsNullOrWhiteSpace(banReason) ? "-" : banReason;
|
||||
|
||||
var replacer = new ReplacementBuilder()
|
||||
.WithServer(client, guild)
|
||||
.WithOverride("%ban.mod%", () => moderator.ToString())
|
||||
.WithOverride("%ban.mod.fullname%", () => moderator.ToString())
|
||||
.WithOverride("%ban.mod.name%", () => moderator.Username)
|
||||
.WithOverride("%ban.mod.discrim%", () => moderator.Discriminator)
|
||||
.WithOverride("%ban.user%", () => target.ToString())
|
||||
.WithOverride("%ban.user.fullname%", () => target.ToString())
|
||||
.WithOverride("%ban.user.name%", () => target.Username)
|
||||
.WithOverride("%ban.user.discrim%", () => target.Discriminator)
|
||||
.WithOverride("%reason%", () => banReason)
|
||||
.WithOverride("%ban.reason%", () => banReason)
|
||||
.WithOverride("%ban.duration%", () => duration?.ToString(@"d\.hh\:mm")?? "perma")
|
||||
.Build();
|
||||
var replacer = new ReplacementBuilder().WithServer(client, guild)
|
||||
.WithOverride("%ban.mod%", () => moderator.ToString())
|
||||
.WithOverride("%ban.mod.fullname%", () => moderator.ToString())
|
||||
.WithOverride("%ban.mod.name%", () => moderator.Username)
|
||||
.WithOverride("%ban.mod.discrim%", () => moderator.Discriminator)
|
||||
.WithOverride("%ban.user%", () => target.ToString())
|
||||
.WithOverride("%ban.user.fullname%", () => target.ToString())
|
||||
.WithOverride("%ban.user.name%", () => target.Username)
|
||||
.WithOverride("%ban.user.discrim%", () => target.Discriminator)
|
||||
.WithOverride("%reason%", () => banReason)
|
||||
.WithOverride("%ban.reason%", () => banReason)
|
||||
.WithOverride("%ban.duration%",
|
||||
() => duration?.ToString(@"d\.hh\:mm") ?? "perma")
|
||||
.Build();
|
||||
|
||||
// if template isn't set, use the old message style
|
||||
if (string.IsNullOrWhiteSpace(template))
|
||||
{
|
||||
template = JsonConvert.SerializeObject(new
|
||||
{
|
||||
color = _bcs.Data.Color.Error.PackedValue >> 8,
|
||||
description = defaultMessage
|
||||
color = _bcs.Data.Color.Error.PackedValue >> 8, description = defaultMessage
|
||||
});
|
||||
}
|
||||
// if template is set to "-" do not dm the user
|
||||
else if (template == "-")
|
||||
{
|
||||
return default;
|
||||
}
|
||||
// if template is an embed, send that embed with replacements
|
||||
// otherwise, treat template as a regular string with replacements
|
||||
else if (!SmartText.CreateFrom(template).IsEmbed)
|
||||
{
|
||||
template = JsonConvert.SerializeObject(new
|
||||
{
|
||||
color = _bcs.Data.Color.Error.PackedValue >> 8,
|
||||
description = template
|
||||
color = _bcs.Data.Color.Error.PackedValue >> 8, description = template
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var output = SmartText.CreateFrom(template);
|
||||
return replacer.Replace(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public class VcRoleService : INService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; }
|
||||
public ConcurrentDictionary<ulong, ConcurrentQueue<(bool, IGuildUser, IRole)>> ToAssign { get; }
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public VcRoleService(DiscordSocketClient client, Bot bot, DbService db)
|
||||
{
|
||||
@@ -26,12 +25,12 @@ public class VcRoleService : INService
|
||||
{
|
||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||
uow.Set<GuildConfig>()
|
||||
.AsQueryable()
|
||||
.Include(x => x.VcRoleInfos)
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.AsEnumerable()
|
||||
.Select(InitializeVcRole)
|
||||
.WhenAll();
|
||||
.AsQueryable()
|
||||
.Include(x => x.VcRoleInfos)
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.AsEnumerable()
|
||||
.Select(InitializeVcRole)
|
||||
.WhenAll();
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
@@ -39,42 +38,34 @@ public class VcRoleService : INService
|
||||
while (true)
|
||||
{
|
||||
Task Selector(ConcurrentQueue<(bool, IGuildUser, IRole)> queue)
|
||||
=> Task.Run(async () =>
|
||||
{
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
while (queue.TryDequeue(out var item))
|
||||
{
|
||||
while (queue.TryDequeue(out var item))
|
||||
var (add, user, role) = item;
|
||||
|
||||
try
|
||||
{
|
||||
var (add, user, role) = item;
|
||||
|
||||
try
|
||||
if (add)
|
||||
{
|
||||
if (add)
|
||||
{
|
||||
if (!user.RoleIds.Contains(role.Id))
|
||||
{
|
||||
await user.AddRoleAsync(role);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (user.RoleIds.Contains(role.Id))
|
||||
{
|
||||
await user.RemoveRoleAsync(role);
|
||||
|
||||
}
|
||||
}
|
||||
if (!user.RoleIds.Contains(role.Id)) await user.AddRoleAsync(role);
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
if (user.RoleIds.Contains(role.Id)) await user.RemoveRoleAsync(role);
|
||||
}
|
||||
|
||||
await Task.Delay(250);
|
||||
}
|
||||
}
|
||||
);
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
await ToAssign.Values.Select(Selector)
|
||||
.Append(Task.Delay(1000))
|
||||
.WhenAll();
|
||||
await Task.Delay(250);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await ToAssign.Values.Select(Selector).Append(Task.Delay(1000)).WhenAll();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -88,10 +79,7 @@ public class VcRoleService : INService
|
||||
// need to load new guildconfig with vc role included
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var configWithVcRole = uow.GuildConfigsForId(
|
||||
arg.GuildId,
|
||||
set => set.Include(x => x.VcRoleInfos)
|
||||
);
|
||||
var configWithVcRole = uow.GuildConfigsForId(arg.GuildId, set => set.Include(x => x.VcRoleInfos));
|
||||
var _ = InitializeVcRole(configWithVcRole);
|
||||
}
|
||||
|
||||
@@ -132,8 +120,7 @@ public class VcRoleService : INService
|
||||
await using var uow = _db.GetDbContext();
|
||||
Log.Warning("Removing {MissingRoleCount} missing roles from {ServiceName}",
|
||||
missingRoles.Count,
|
||||
nameof(VcRoleService)
|
||||
);
|
||||
nameof(VcRoleService));
|
||||
uow.RemoveRange(missingRoles);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
@@ -150,15 +137,8 @@ public class VcRoleService : INService
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.VcRoleInfos));
|
||||
var toDelete = conf.VcRoleInfos.FirstOrDefault(x => x.VoiceChannelId == vcId); // remove old one
|
||||
if(toDelete != null)
|
||||
{
|
||||
uow.Remove(toDelete);
|
||||
}
|
||||
conf.VcRoleInfos.Add(new()
|
||||
{
|
||||
VoiceChannelId = vcId,
|
||||
RoleId = role.Id,
|
||||
}); // add new one
|
||||
if (toDelete != null) uow.Remove(toDelete);
|
||||
conf.VcRoleInfos.Add(new() { VoiceChannelId = vcId, RoleId = role.Id }); // add new one
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
@@ -179,8 +159,7 @@ public class VcRoleService : INService
|
||||
return true;
|
||||
}
|
||||
|
||||
private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
|
||||
SocketVoiceState newState)
|
||||
private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState, SocketVoiceState newState)
|
||||
{
|
||||
if (usr is not SocketGuildUser gusr)
|
||||
return Task.CompletedTask;
|
||||
@@ -200,15 +179,9 @@ public class VcRoleService : INService
|
||||
{
|
||||
//remove old
|
||||
if (oldVc != null && guildVcRoles.TryGetValue(oldVc.Id, out var role))
|
||||
{
|
||||
Assign(false, gusr, role);
|
||||
}
|
||||
//add new
|
||||
if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role))
|
||||
{
|
||||
Assign(true, gusr, role);
|
||||
}
|
||||
|
||||
if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role)) Assign(true, gusr, role);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,4 +198,4 @@ public class VcRoleService : INService
|
||||
var queue = ToAssign.GetOrAdd(gusr.Guild.Id, new ConcurrentQueue<(bool, IGuildUser, IRole)>());
|
||||
queue.Enqueue((v, gusr, role));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@ public partial class Administration
|
||||
[Group]
|
||||
public class TimeZoneCommands : NadekoSubmodule<GuildTimezoneService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Timezones(int page = 1)
|
||||
{
|
||||
@@ -17,55 +18,50 @@ public partial class Administration
|
||||
if (page is < 0 or > 20)
|
||||
return;
|
||||
|
||||
var timezones = TimeZoneInfo.GetSystemTimeZones()
|
||||
.OrderBy(x => x.BaseUtcOffset)
|
||||
.ToArray();
|
||||
var timezones = TimeZoneInfo.GetSystemTimeZones().OrderBy(x => x.BaseUtcOffset).ToArray();
|
||||
var timezonesPerPage = 20;
|
||||
|
||||
var curTime = DateTimeOffset.UtcNow;
|
||||
|
||||
var i = 0;
|
||||
var timezoneStrings = timezones
|
||||
.Select(x => (x, ++i % 2 == 0))
|
||||
.Select(data =>
|
||||
{
|
||||
var (tzInfo, flip) = data;
|
||||
var nameStr = $"{tzInfo.Id,-30}";
|
||||
var offset = curTime.ToOffset(tzInfo.GetUtcOffset(curTime)).ToString("zzz");
|
||||
if (flip)
|
||||
{
|
||||
return $"{offset} {Format.Code(nameStr)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{Format.Code(offset)} {nameStr}";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
var timezoneStrings = timezones.Select(x => (x, ++i % 2 == 0))
|
||||
.Select(data =>
|
||||
{
|
||||
var (tzInfo, flip) = data;
|
||||
var nameStr = $"{tzInfo.Id,-30}";
|
||||
var offset = curTime.ToOffset(tzInfo.GetUtcOffset(curTime))
|
||||
.ToString("zzz");
|
||||
if (flip)
|
||||
return $"{offset} {Format.Code(nameStr)}";
|
||||
return $"{Format.Code(offset)} {nameStr}";
|
||||
});
|
||||
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage => _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.timezones_available))
|
||||
.WithDescription(string.Join("\n", timezoneStrings
|
||||
.Skip(curPage * timezonesPerPage)
|
||||
.Take(timezonesPerPage))),
|
||||
timezones.Length, timezonesPerPage);
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.timezones_available))
|
||||
.WithDescription(string.Join("\n",
|
||||
timezoneStrings.Skip(curPage * timezonesPerPage).Take(timezonesPerPage))),
|
||||
timezones.Length,
|
||||
timezonesPerPage);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Timezone()
|
||||
=> await ReplyConfirmLocalizedAsync(strs.timezone_guild(_service.GetTimeZoneOrUtc(ctx.Guild.Id)));
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task Timezone([Leftover] string id)
|
||||
{
|
||||
TimeZoneInfo tz;
|
||||
try { tz = TimeZoneInfo.FindSystemTimeZoneById(id); } catch { tz = null; }
|
||||
try { tz = TimeZoneInfo.FindSystemTimeZoneById(id); }
|
||||
catch { tz = null; }
|
||||
|
||||
|
||||
if (tz is null)
|
||||
@@ -73,9 +69,10 @@ public partial class Administration
|
||||
await ReplyErrorLocalizedAsync(strs.timezone_not_found);
|
||||
return;
|
||||
}
|
||||
|
||||
_service.SetTimeZone(ctx.Guild.Id, tz);
|
||||
|
||||
await SendConfirmAsync(tz.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
using CommandLine;
|
||||
using Humanizer.Localisation;
|
||||
using NadekoBot.Common.TypeReaders.Models;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Modules.Permissions.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
@@ -13,6 +13,11 @@ public partial class Administration
|
||||
[Group]
|
||||
public class UserPunishCommands : NadekoSubmodule<UserPunishService>
|
||||
{
|
||||
public enum AddRole
|
||||
{
|
||||
AddRole
|
||||
}
|
||||
|
||||
private readonly MuteService _mute;
|
||||
private readonly BlacklistService _blacklistService;
|
||||
|
||||
@@ -24,47 +29,52 @@ public partial class Administration
|
||||
|
||||
private async Task<bool> CheckRoleHierarchy(IGuildUser target)
|
||||
{
|
||||
var curUser = ((SocketGuild) ctx.Guild).CurrentUser;
|
||||
var curUser = ((SocketGuild)ctx.Guild).CurrentUser;
|
||||
var ownerId = ctx.Guild.OwnerId;
|
||||
var modMaxRole = ((IGuildUser) ctx.User).GetRoles().Max(r => r.Position);
|
||||
var modMaxRole = ((IGuildUser)ctx.User).GetRoles().Max(r => r.Position);
|
||||
var targetMaxRole = target.GetRoles().Max(r => r.Position);
|
||||
var botMaxRole = curUser.GetRoles().Max(r => r.Position);
|
||||
// bot can't punish a user who is higher in the hierarchy. Discord will return 403
|
||||
// moderator can be owner, in which case role hierarchy doesn't matter
|
||||
// otherwise, moderator has to have a higher role
|
||||
if (botMaxRole <= targetMaxRole || (ctx.User.Id != ownerId && targetMaxRole >= modMaxRole) || target.Id == ownerId)
|
||||
if (botMaxRole <= targetMaxRole
|
||||
|| (ctx.User.Id != ownerId && targetMaxRole >= modMaxRole)
|
||||
|| target.Id == ownerId)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.hierarchy);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public Task Warn(IGuildUser user, [Leftover] string reason = null)
|
||||
=> Warn(1, user, reason);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public async Task Warn(int weight, IGuildUser user, [Leftover] string reason = null)
|
||||
{
|
||||
if (weight <= 0)
|
||||
return;
|
||||
|
||||
|
||||
if (!await CheckRoleHierarchy(user))
|
||||
return;
|
||||
|
||||
var dmFailed = false;
|
||||
try
|
||||
{
|
||||
await user.EmbedAsync(_eb.Create().WithErrorColor()
|
||||
.WithDescription(GetText(strs.warned_on(ctx.Guild.ToString())))
|
||||
.AddField(GetText(strs.moderator), ctx.User.ToString())
|
||||
.AddField(GetText(strs.reason), reason ?? "-"));
|
||||
await user.EmbedAsync(_eb.Create()
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.warned_on(ctx.Guild.ToString())))
|
||||
.AddField(GetText(strs.moderator), ctx.User.ToString())
|
||||
.AddField(GetText(strs.reason), reason ?? "-"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -79,50 +89,28 @@ public partial class Administration
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex.Message);
|
||||
var errorEmbed = _eb.Create()
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.cant_apply_punishment));
|
||||
|
||||
if (dmFailed)
|
||||
{
|
||||
errorEmbed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
}
|
||||
|
||||
var errorEmbed = _eb.Create().WithErrorColor().WithDescription(GetText(strs.cant_apply_punishment));
|
||||
|
||||
if (dmFailed) errorEmbed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
|
||||
await ctx.Channel.EmbedAsync(errorEmbed);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor();
|
||||
var embed = _eb.Create().WithOkColor();
|
||||
if (punishment is null)
|
||||
{
|
||||
embed.WithDescription(GetText(strs.user_warned(Format.Bold(user.ToString()))));
|
||||
}
|
||||
else
|
||||
{
|
||||
embed.WithDescription(GetText(strs.user_warned_and_punished(Format.Bold(user.ToString()),
|
||||
Format.Bold(punishment.Punishment.ToString()))));
|
||||
}
|
||||
|
||||
if (dmFailed)
|
||||
{
|
||||
embed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
}
|
||||
if (dmFailed) embed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
public class WarnExpireOptions : INadekoCommandOptions
|
||||
{
|
||||
[Option('d', "delete", Default = false, HelpText = "Delete warnings instead of clearing them.")]
|
||||
public bool Delete { get; set; } = false;
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[NadekoOptions(typeof(WarnExpireOptions))]
|
||||
@@ -137,7 +125,8 @@ public partial class Administration
|
||||
await ReplyErrorLocalizedAsync(strs.warns_expire_in(expireDays));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[NadekoOptions(typeof(WarnExpireOptions))]
|
||||
@@ -152,51 +141,53 @@ public partial class Administration
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
|
||||
await _service.WarnExpireAsync(ctx.Guild.Id, days, opts.Delete);
|
||||
if(days == 0)
|
||||
if (days == 0)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_expire_reset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.Delete)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_expire_set_delete(Format.Bold(days.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_expire_set_clear(Format.Bold(days.ToString())));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[Priority(2)]
|
||||
public Task Warnlog(int page, [Leftover] IGuildUser user = null)
|
||||
{
|
||||
user ??= (IGuildUser) ctx.User;
|
||||
user ??= (IGuildUser)ctx.User;
|
||||
|
||||
return Warnlog(page, user.Id);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(3)]
|
||||
public Task Warnlog(IGuildUser user = null)
|
||||
{
|
||||
user ??= (IGuildUser) ctx.User;
|
||||
user ??= (IGuildUser)ctx.User;
|
||||
|
||||
return ctx.User.Id == user.Id || ((IGuildUser)ctx.User).GuildPermissions.BanMembers ? Warnlog(user.Id) : Task.CompletedTask;
|
||||
return ctx.User.Id == user.Id || ((IGuildUser)ctx.User).GuildPermissions.BanMembers
|
||||
? Warnlog(user.Id)
|
||||
: Task.CompletedTask;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[Priority(0)]
|
||||
public Task Warnlog(int page, ulong userId)
|
||||
=> InternalWarnlog(userId, page - 1);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[Priority(1)]
|
||||
@@ -207,58 +198,54 @@ public partial class Administration
|
||||
{
|
||||
if (inputPage < 0)
|
||||
return;
|
||||
|
||||
|
||||
var allWarnings = _service.UserWarnings(ctx.Guild.Id, userId);
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(inputPage, page =>
|
||||
{
|
||||
var warnings = allWarnings
|
||||
.Skip(page * 9)
|
||||
.Take(9)
|
||||
.ToArray();
|
||||
|
||||
var user = (ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString();
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.warnlog_for(user)));
|
||||
|
||||
if (!warnings.Any())
|
||||
await ctx.SendPaginatedConfirmAsync(inputPage,
|
||||
page =>
|
||||
{
|
||||
embed.WithDescription(GetText(strs.warnings_none));
|
||||
}
|
||||
else
|
||||
{
|
||||
var descText = GetText(strs.warn_count(
|
||||
Format.Bold(warnings.Where(x => !x.Forgiven).Sum(x => x.Weight).ToString()),
|
||||
Format.Bold(warnings.Sum(x => x.Weight).ToString())));
|
||||
|
||||
embed.WithDescription(descText);
|
||||
|
||||
var i = page * 9;
|
||||
foreach (var w in warnings)
|
||||
var warnings = allWarnings.Skip(page * 9).Take(9).ToArray();
|
||||
|
||||
var user = (ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString();
|
||||
var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.warnlog_for(user)));
|
||||
|
||||
if (!warnings.Any())
|
||||
{
|
||||
i++;
|
||||
var name = GetText(strs.warned_on_by(
|
||||
w.DateAdded?.ToString("dd.MM.yyy"),
|
||||
w.DateAdded?.ToString("HH:mm"),
|
||||
w.Moderator));
|
||||
|
||||
if (w.Forgiven)
|
||||
name = $"{Format.Strikethrough(name)} {GetText(strs.warn_cleared_by(w.ForgivenBy))}";
|
||||
|
||||
|
||||
embed.AddField($"#`{i}` " + name,
|
||||
Format.Code(GetText(strs.warn_weight(w.Weight))) +
|
||||
'\n' +
|
||||
w.Reason.TrimTo(1000));
|
||||
embed.WithDescription(GetText(strs.warnings_none));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var descText = GetText(strs.warn_count(
|
||||
Format.Bold(warnings.Where(x => !x.Forgiven).Sum(x => x.Weight).ToString()),
|
||||
Format.Bold(warnings.Sum(x => x.Weight).ToString())));
|
||||
|
||||
return embed;
|
||||
}, allWarnings.Length, 9);
|
||||
embed.WithDescription(descText);
|
||||
|
||||
var i = page * 9;
|
||||
foreach (var w in warnings)
|
||||
{
|
||||
i++;
|
||||
var name = GetText(strs.warned_on_by(w.DateAdded?.ToString("dd.MM.yyy"),
|
||||
w.DateAdded?.ToString("HH:mm"),
|
||||
w.Moderator));
|
||||
|
||||
if (w.Forgiven)
|
||||
name = $"{Format.Strikethrough(name)} {GetText(strs.warn_cleared_by(w.ForgivenBy))}";
|
||||
|
||||
|
||||
embed.AddField($"#`{i}` " + name,
|
||||
Format.Code(GetText(strs.warn_weight(w.Weight))) + '\n' + w.Reason.TrimTo(1000));
|
||||
}
|
||||
}
|
||||
|
||||
return embed;
|
||||
},
|
||||
allWarnings.Length,
|
||||
9);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public async Task WarnlogAll(int page = 1)
|
||||
@@ -267,33 +254,40 @@ public partial class Administration
|
||||
return;
|
||||
var warnings = _service.WarnlogAll(ctx.Guild.Id);
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
var ws = warnings.Skip(curPage * 15)
|
||||
.Take(15)
|
||||
.ToArray()
|
||||
.Select(x =>
|
||||
{
|
||||
var all = x.Count();
|
||||
var forgiven = x.Count(y => y.Forgiven);
|
||||
var total = all - forgiven;
|
||||
var usr = ((SocketGuild)ctx.Guild).GetUser(x.Key);
|
||||
return (usr?.ToString() ?? x.Key.ToString()) + $" | {total} ({all} - {forgiven})";
|
||||
});
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
var ws = warnings.Skip(curPage * 15)
|
||||
.Take(15)
|
||||
.ToArray()
|
||||
.Select(x =>
|
||||
{
|
||||
var all = x.Count();
|
||||
var forgiven = x.Count(y => y.Forgiven);
|
||||
var total = all - forgiven;
|
||||
var usr = ((SocketGuild)ctx.Guild).GetUser(x.Key);
|
||||
return (usr?.ToString() ?? x.Key.ToString())
|
||||
+ $" | {total} ({all} - {forgiven})";
|
||||
});
|
||||
|
||||
return _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.warnings_list))
|
||||
.WithDescription(string.Join("\n", ws));
|
||||
}, warnings.Length, 15);
|
||||
return _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.warnings_list))
|
||||
.WithDescription(string.Join("\n", ws));
|
||||
},
|
||||
warnings.Length,
|
||||
15);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public Task Warnclear(IGuildUser user, int index = 0)
|
||||
=> Warnclear(user.Id, index);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public async Task Warnclear(ulong userId, int index = 0)
|
||||
@@ -309,57 +303,48 @@ public partial class Administration
|
||||
else
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.warning_cleared(Format.Bold(index.ToString()), userStr));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.warning_clear_fail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum AddRole
|
||||
{
|
||||
AddRole
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[Priority(1)]
|
||||
public async Task WarnPunish(int number, AddRole _, IRole role, StoopidTime time = null)
|
||||
public async Task WarnPunish(
|
||||
int number,
|
||||
AddRole _,
|
||||
IRole role,
|
||||
StoopidTime time = null)
|
||||
{
|
||||
var punish = PunishmentAction.AddRole;
|
||||
|
||||
if (ctx.Guild.OwnerId != ctx.User.Id &&
|
||||
role.Position >= ((IGuildUser)ctx.User).GetRoles().Max(x => x.Position))
|
||||
if (ctx.Guild.OwnerId != ctx.User.Id
|
||||
&& role.Position >= ((IGuildUser)ctx.User).GetRoles().Max(x => x.Position))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.role_too_high);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time, role);
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
if (time is null)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_punish_set(
|
||||
Format.Bold(punish.ToString()),
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_punish_set(Format.Bold(punish.ToString()),
|
||||
Format.Bold(number.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_punish_set_timed(
|
||||
Format.Bold(punish.ToString()),
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
|
||||
Format.Bold(number.ToString()),
|
||||
Format.Bold(time.Input)));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public async Task WarnPunish(int number, PunishmentAction punish, StoopidTime time = null)
|
||||
@@ -374,35 +359,27 @@ public partial class Administration
|
||||
return;
|
||||
|
||||
if (time is null)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_punish_set(
|
||||
Format.Bold(punish.ToString()),
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_punish_set(Format.Bold(punish.ToString()),
|
||||
Format.Bold(number.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_punish_set_timed(
|
||||
Format.Bold(punish.ToString()),
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
|
||||
Format.Bold(number.ToString()),
|
||||
Format.Bold(time.Input)));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public async Task WarnPunish(int number)
|
||||
{
|
||||
if (!_service.WarnPunishRemove(ctx.Guild.Id, number))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!_service.WarnPunishRemove(ctx.Guild.Id, number)) return;
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_punish_rem(
|
||||
Format.Bold(number.ToString())));
|
||||
await ReplyConfirmLocalizedAsync(strs.warn_punish_rem(Format.Bold(number.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WarnPunishList()
|
||||
{
|
||||
@@ -410,20 +387,16 @@ public partial class Administration
|
||||
|
||||
string list;
|
||||
if (ps.Any())
|
||||
{
|
||||
|
||||
list = string.Join("\n", ps.Select(x => $"{x.Count} -> {x.Punishment} {(x.Punishment == PunishmentAction.AddRole ? $"<@&{x.RoleId}>" : "")} {(x.Time <= 0 ? "" : x.Time.ToString() + "m")} "));
|
||||
}
|
||||
list = string.Join("\n",
|
||||
ps.Select(x
|
||||
=> $"{x.Count} -> {x.Punishment} {(x.Punishment == PunishmentAction.AddRole ? $"<@&{x.RoleId}>" : "")} {(x.Time <= 0 ? "" : x.Time + "m")} "));
|
||||
else
|
||||
{
|
||||
list = GetText(strs.warnpl_none);
|
||||
}
|
||||
await SendConfirmAsync(
|
||||
GetText(strs.warn_punish_list),
|
||||
list);
|
||||
await SendConfirmAsync(GetText(strs.warn_punish_list), list);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
@@ -441,42 +414,34 @@ public partial class Administration
|
||||
var dmFailed = false;
|
||||
|
||||
if (guildUser != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
|
||||
var embed = _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, time.Time);
|
||||
if (embed is not null)
|
||||
{
|
||||
await guildUser.SendAsync(embed);
|
||||
}
|
||||
if (embed is not null) await guildUser.SendAsync(embed);
|
||||
}
|
||||
catch
|
||||
{
|
||||
dmFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
await _mute.TimedBan(ctx.Guild, user, time.Time, (ctx.User.ToString() + " | " + msg).TrimTo(512));
|
||||
var toSend = _eb.Create().WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true)
|
||||
.AddField(GetText(strs.duration),
|
||||
time.Time.Humanize(3,
|
||||
minUnit: TimeUnit.Minute,
|
||||
culture: Culture),
|
||||
true);
|
||||
await _mute.TimedBan(ctx.Guild, user, time.Time, (ctx.User + " | " + msg).TrimTo(512));
|
||||
var toSend = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true)
|
||||
.AddField(GetText(strs.duration),
|
||||
time.Time.Humanize(3, minUnit: TimeUnit.Minute, culture: Culture),
|
||||
true);
|
||||
|
||||
if (dmFailed)
|
||||
{
|
||||
toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
}
|
||||
if (dmFailed) toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
|
||||
await ctx.Channel.EmbedAsync(toSend);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
@@ -486,11 +451,12 @@ public partial class Administration
|
||||
var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
||||
if (user is null)
|
||||
{
|
||||
await ctx.Guild.AddBanAsync(userId, 7, (ctx.User.ToString() + " | " + msg).TrimTo(512));
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField("ID", userId.ToString(), true));
|
||||
await ctx.Guild.AddBanAsync(userId, 7, (ctx.User + " | " + msg).TrimTo(512));
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField("ID", userId.ToString(), true));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -498,7 +464,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
@@ -514,32 +481,28 @@ public partial class Administration
|
||||
{
|
||||
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
|
||||
var embed = _service.GetBanUserDmEmbed(Context, user, defaultMessage, msg, null);
|
||||
if (embed is not null)
|
||||
{
|
||||
await ctx.User.SendAsync(embed);
|
||||
}
|
||||
if (embed is not null) await ctx.User.SendAsync(embed);
|
||||
}
|
||||
catch
|
||||
{
|
||||
dmFailed = true;
|
||||
}
|
||||
|
||||
await ctx.Guild.AddBanAsync(user, 7, (ctx.User.ToString() + " | " + msg).TrimTo(512));
|
||||
await ctx.Guild.AddBanAsync(user, 7, (ctx.User + " | " + msg).TrimTo(512));
|
||||
|
||||
var toSend = _eb.Create().WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
var toSend = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
|
||||
if (dmFailed) toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
|
||||
if (dmFailed)
|
||||
{
|
||||
toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(toSend);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
@@ -557,12 +520,13 @@ public partial class Administration
|
||||
await SendConfirmAsync(template);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_service.SetBanTemplate(ctx.Guild.Id, message);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
@@ -572,30 +536,28 @@ public partial class Administration
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
[Priority(0)]
|
||||
public Task BanMessageTest([Leftover] string reason = null)
|
||||
=> InternalBanMessageTest(reason, null);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
[Priority(1)]
|
||||
public Task BanMessageTest(StoopidTime duration, [Leftover] string reason = null)
|
||||
=> InternalBanMessageTest(reason, duration.Time);
|
||||
|
||||
|
||||
private async Task InternalBanMessageTest(string reason, TimeSpan? duration)
|
||||
{
|
||||
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), reason));
|
||||
var embed = _service.GetBanUserDmEmbed(Context,
|
||||
(IGuildUser)ctx.User,
|
||||
defaultMessage,
|
||||
reason,
|
||||
duration);
|
||||
var embed = _service.GetBanUserDmEmbed(Context, (IGuildUser)ctx.User, defaultMessage, reason, duration);
|
||||
|
||||
if (embed is null)
|
||||
{
|
||||
@@ -617,7 +579,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
@@ -636,7 +599,8 @@ public partial class Administration
|
||||
await UnbanInternal(bun.User);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
@@ -662,14 +626,16 @@ public partial class Administration
|
||||
await ReplyConfirmLocalizedAsync(strs.unbanned_user(Format.Bold(user.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.KickMembers | GuildPerm.ManageMessages)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
public Task Softban(IGuildUser user, [Leftover] string msg = null)
|
||||
=> SoftbanInternal(user, msg);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.KickMembers | GuildPerm.ManageMessages)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
@@ -681,7 +647,7 @@ public partial class Administration
|
||||
|
||||
await SoftbanInternal(user, msg);
|
||||
}
|
||||
|
||||
|
||||
private async Task SoftbanInternal(IGuildUser user, [Leftover] string msg = null)
|
||||
{
|
||||
if (!await CheckRoleHierarchy(user))
|
||||
@@ -698,24 +664,23 @@ public partial class Administration
|
||||
dmFailed = true;
|
||||
}
|
||||
|
||||
await ctx.Guild.AddBanAsync(user, 7, ("Softban | " + ctx.User.ToString() + " | " + msg).TrimTo(512));
|
||||
await ctx.Guild.AddBanAsync(user, 7, ("Softban | " + ctx.User + " | " + msg).TrimTo(512));
|
||||
try { await ctx.Guild.RemoveBanAsync(user); }
|
||||
catch { await ctx.Guild.RemoveBanAsync(user); }
|
||||
|
||||
var toSend = _eb.Create().WithOkColor()
|
||||
.WithTitle("☣ " + GetText(strs.sb_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
|
||||
if (dmFailed)
|
||||
{
|
||||
toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
}
|
||||
|
||||
var toSend = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("☣ " + GetText(strs.sb_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
|
||||
if (dmFailed) toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
|
||||
await ctx.Channel.EmbedAsync(toSend);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.KickMembers)]
|
||||
[BotPerm(GuildPerm.KickMembers)]
|
||||
@@ -723,7 +688,8 @@ public partial class Administration
|
||||
public Task Kick(IGuildUser user, [Leftover] string msg = null)
|
||||
=> KickInternal(user, msg);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.KickMembers)]
|
||||
[BotPerm(GuildPerm.KickMembers)]
|
||||
@@ -733,7 +699,7 @@ public partial class Administration
|
||||
var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
||||
if (user is null)
|
||||
return;
|
||||
|
||||
|
||||
await KickInternal(user, msg);
|
||||
}
|
||||
|
||||
@@ -749,26 +715,25 @@ public partial class Administration
|
||||
await user.SendErrorAsync(_eb, GetText(strs.kickdm(Format.Bold(ctx.Guild.Name), msg)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
{
|
||||
dmFailed = true;
|
||||
}
|
||||
|
||||
await user.KickAsync((ctx.User.ToString() + " | " + msg).TrimTo(512));
|
||||
|
||||
var toSend = _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.kicked_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
|
||||
if (dmFailed)
|
||||
{
|
||||
toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
}
|
||||
|
||||
await user.KickAsync((ctx.User + " | " + msg).TrimTo(512));
|
||||
|
||||
var toSend = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.kicked_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
|
||||
if (dmFailed) toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
|
||||
await ctx.Channel.EmbedAsync(toSend);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
@@ -783,11 +748,11 @@ public partial class Administration
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
foreach (var userStr in userStrings)
|
||||
{
|
||||
if (ulong.TryParse(userStr, out var userId))
|
||||
{
|
||||
IUser user = await ctx.Guild.GetUserAsync(userId) ??
|
||||
await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
|
||||
IUser user = await ctx.Guild.GetUserAsync(userId)
|
||||
?? await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id,
|
||||
userId);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
@@ -800,14 +765,10 @@ public partial class Administration
|
||||
missing.Add(userStr);
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Hierachy checks only if the user is in the guild
|
||||
if (user is IGuildUser gu && !await CheckRoleHierarchy(gu))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (user is IGuildUser gu && !await CheckRoleHierarchy(gu)) return;
|
||||
|
||||
banning.Add(user);
|
||||
}
|
||||
@@ -815,41 +776,38 @@ public partial class Administration
|
||||
{
|
||||
missing.Add(userStr);
|
||||
}
|
||||
}
|
||||
|
||||
var missStr = string.Join("\n", missing);
|
||||
if (string.IsNullOrWhiteSpace(missStr))
|
||||
missStr = "-";
|
||||
|
||||
|
||||
var toSend = _eb.Create(ctx)
|
||||
.WithDescription(GetText(strs.mass_ban_in_progress(banning.Count)))
|
||||
.AddField(GetText(strs.invalid(missing.Count)), missStr)
|
||||
.WithPendingColor();
|
||||
.WithDescription(GetText(strs.mass_ban_in_progress(banning.Count)))
|
||||
.AddField(GetText(strs.invalid(missing.Count)), missStr)
|
||||
.WithPendingColor();
|
||||
|
||||
var banningMessage = await ctx.Channel.EmbedAsync(toSend);
|
||||
|
||||
foreach (var toBan in banning)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ctx.Guild.AddBanAsync(toBan.Id, 7, $"{ctx.User} | Massban");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error banning {User} user in {GuildId} server",
|
||||
toBan.Id,
|
||||
ctx.Guild.Id);
|
||||
Log.Warning(ex, "Error banning {User} user in {GuildId} server", toBan.Id, ctx.Guild.Id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await banningMessage.ModifyAsync(x => x.Embed = _eb.Create()
|
||||
.WithDescription(GetText(strs.mass_ban_completed(banning.Count())))
|
||||
.AddField(GetText(strs.invalid(missing.Count)), missStr)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
.WithDescription(
|
||||
GetText(strs.mass_ban_completed(banning.Count())))
|
||||
.AddField(GetText(strs.invalid(missing.Count)), missStr)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
[BotPerm(GuildPerm.BanMembers)]
|
||||
@@ -867,26 +825,37 @@ public partial class Administration
|
||||
|
||||
//send a message but don't wait for it
|
||||
var banningMessageTask = ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithDescription(GetText(strs.mass_kill_in_progress(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
.WithPendingColor());
|
||||
.WithDescription(
|
||||
GetText(strs.mass_kill_in_progress(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
.WithPendingColor());
|
||||
|
||||
//do the banning
|
||||
await Task.WhenAll(bans
|
||||
.Where(x => x.Id.HasValue)
|
||||
.Select(x => ctx.Guild.AddBanAsync(x.Id.Value, 7, x.Reason, new()
|
||||
{
|
||||
RetryMode = RetryMode.AlwaysRetry,
|
||||
})));
|
||||
await Task.WhenAll(bans.Where(x => x.Id.HasValue)
|
||||
.Select(x => ctx.Guild.AddBanAsync(x.Id.Value,
|
||||
7,
|
||||
x.Reason,
|
||||
new() { RetryMode = RetryMode.AlwaysRetry })));
|
||||
|
||||
//wait for the message and edit it
|
||||
var banningMessage = await banningMessageTask;
|
||||
|
||||
await banningMessage.ModifyAsync(x => x.Embed = _eb.Create()
|
||||
.WithDescription(GetText(strs.mass_kill_completed(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
.WithDescription(
|
||||
GetText(strs.mass_kill_completed(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
|
||||
public class WarnExpireOptions : INadekoCommandOptions
|
||||
{
|
||||
[Option('d', "delete", Default = false, HelpText = "Delete warnings instead of clearing them.")]
|
||||
public bool Delete { get; set; } = false;
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,23 +8,21 @@ public partial class Administration
|
||||
[Group]
|
||||
public class VcRoleCommands : NadekoSubmodule<VcRoleService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task VcRoleRm(ulong vcId)
|
||||
{
|
||||
if (_service.RemoveVcRole(ctx.Guild.Id, vcId))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.vcrole_removed(Format.Bold(vcId.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.vcrole_not_found);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
@@ -43,9 +41,7 @@ public partial class Administration
|
||||
if (role is null)
|
||||
{
|
||||
if (_service.RemoveVcRole(ctx.Guild.Id, vc.Id))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.vcrole_removed(Format.Bold(vc.Name)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -54,7 +50,8 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task VcRoleList()
|
||||
{
|
||||
@@ -63,22 +60,21 @@ public partial class Administration
|
||||
if (_service.VcRoles.TryGetValue(ctx.Guild.Id, out var roles))
|
||||
{
|
||||
if (!roles.Any())
|
||||
{
|
||||
text = GetText(strs.no_vcroles);
|
||||
}
|
||||
else
|
||||
{
|
||||
text = string.Join("\n", roles.Select(x =>
|
||||
$"{Format.Bold(guild.GetVoiceChannel(x.Key)?.Name ?? x.Key.ToString())} => {x.Value}"));
|
||||
}
|
||||
text = string.Join("\n",
|
||||
roles.Select(x
|
||||
=> $"{Format.Bold(guild.GetVoiceChannel(x.Key)?.Name ?? x.Key.ToString())} => {x.Value}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
text = GetText(strs.no_vcroles);
|
||||
}
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.vc_role_list))
|
||||
.WithDescription(text));
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.vc_role_list))
|
||||
.WithDescription(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,6 @@ public class ExportedExpr
|
||||
At = cr.AllowTarget,
|
||||
Ca = cr.ContainsAnywhere,
|
||||
Dm = cr.DmResponse,
|
||||
React = string.IsNullOrWhiteSpace(cr.Reactions)
|
||||
? null
|
||||
: cr.GetReactions(),
|
||||
React = string.IsNullOrWhiteSpace(cr.Reactions) ? null : cr.GetReactions()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,11 @@ namespace NadekoBot.Modules.CustomReactions;
|
||||
|
||||
public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
{
|
||||
public enum All
|
||||
{
|
||||
All
|
||||
}
|
||||
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IHttpClientFactory _clientFactory;
|
||||
|
||||
@@ -14,10 +19,12 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
_clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
private bool AdminInGuildOrOwnerInDm() => (ctx.Guild is null && _creds.IsOwner(ctx.User))
|
||||
|| (ctx.Guild != null && ((IGuildUser)ctx.User).GuildPermissions.Administrator);
|
||||
private bool AdminInGuildOrOwnerInDm()
|
||||
=> (ctx.Guild is null && _creds.IsOwner(ctx.User))
|
||||
|| (ctx.Guild != null && ((IGuildUser)ctx.User).GuildPermissions.Administrator);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task AddCustReact(string key, [Leftover] string message)
|
||||
{
|
||||
var channel = ctx.Channel as ITextChannel;
|
||||
@@ -32,22 +39,25 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
|
||||
var cr = await _service.AddAsync(ctx.Guild?.Id, key, message);
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.new_cust_react))
|
||||
.WithDescription($"#{cr.Id}")
|
||||
.AddField(GetText(strs.trigger), key)
|
||||
.AddField(GetText(strs.response), message.Length > 1024 ? GetText(strs.redacted_too_long) : message)
|
||||
);
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.new_cust_react))
|
||||
.WithDescription($"#{cr.Id}")
|
||||
.AddField(GetText(strs.trigger), key)
|
||||
.AddField(GetText(strs.response),
|
||||
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task EditCustReact(kwum id, [Leftover] string message)
|
||||
{
|
||||
var channel = ctx.Channel as ITextChannel;
|
||||
if (string.IsNullOrWhiteSpace(message) || id < 0)
|
||||
return;
|
||||
|
||||
if ((channel is null && !_creds.IsOwner(ctx.User)) || (channel != null && !((IGuildUser)ctx.User).GuildPermissions.Administrator))
|
||||
if ((channel is null && !_creds.IsOwner(ctx.User))
|
||||
|| (channel != null && !((IGuildUser)ctx.User).GuildPermissions.Administrator))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.insuff_perms);
|
||||
return;
|
||||
@@ -55,21 +65,19 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
|
||||
var cr = await _service.EditAsync(ctx.Guild?.Id, id, message);
|
||||
if (cr != null)
|
||||
{
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.edited_cust_react))
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), cr.Trigger)
|
||||
.AddField(GetText(strs.response), message.Length > 1024 ? GetText(strs.redacted_too_long) : message)
|
||||
);
|
||||
}
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.edited_cust_react))
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), cr.Trigger)
|
||||
.AddField(GetText(strs.response),
|
||||
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.edit_fail);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(1)]
|
||||
public async Task ListCustReact(int page = 1)
|
||||
{
|
||||
@@ -84,33 +92,29 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, pageFunc: curPage =>
|
||||
{
|
||||
var desc = customReactions.OrderBy(cr => cr.Trigger)
|
||||
.Skip(curPage * 20)
|
||||
.Take(20)
|
||||
.Select(cr => $"{(cr.ContainsAnywhere ? "🗯" : "◾")}" +
|
||||
$"{(cr.DmResponse ? "✉" : "◾")}" +
|
||||
$"{(cr.AutoDeleteTrigger ? "❌" : "◾")}" +
|
||||
$"`{(kwum) cr.Id}` {cr.Trigger}"
|
||||
+ (string.IsNullOrWhiteSpace(cr.Reactions)
|
||||
? string.Empty
|
||||
: " // " + string.Join(" ", cr.GetReactions())))
|
||||
.Join('\n');
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
var desc = customReactions.OrderBy(cr => cr.Trigger)
|
||||
.Skip(curPage * 20)
|
||||
.Take(20)
|
||||
.Select(cr => $"{(cr.ContainsAnywhere ? "🗯" : "◾")}"
|
||||
+ $"{(cr.DmResponse ? "✉" : "◾")}"
|
||||
+ $"{(cr.AutoDeleteTrigger ? "❌" : "◾")}"
|
||||
+ $"`{(kwum)cr.Id}` {cr.Trigger}"
|
||||
+ (string.IsNullOrWhiteSpace(cr.Reactions)
|
||||
? string.Empty
|
||||
: " // " + string.Join(" ", cr.GetReactions())))
|
||||
.Join('\n');
|
||||
|
||||
return _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.custom_reactions))
|
||||
.WithDescription(desc);
|
||||
|
||||
}, customReactions.Length, 20);
|
||||
return _eb.Create().WithOkColor().WithTitle(GetText(strs.custom_reactions)).WithDescription(desc);
|
||||
},
|
||||
customReactions.Length,
|
||||
20);
|
||||
}
|
||||
|
||||
public enum All
|
||||
{
|
||||
All
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task ShowCustReact(kwum id)
|
||||
{
|
||||
var found = _service.GetCustomReaction(ctx.Guild?.Id, id);
|
||||
@@ -120,17 +124,17 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
await ReplyErrorLocalizedAsync(strs.no_found_id);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response), found.Response.TrimTo(1000).Replace("](", "]\\("))
|
||||
);
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response),
|
||||
found.Response.TrimTo(1000).Replace("](", "]\\(")));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task DelCustReact(kwum id)
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
@@ -142,20 +146,18 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
var cr = await _service.DeleteAsync(ctx.Guild?.Id, id);
|
||||
|
||||
if (cr != null)
|
||||
{
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.deleted))
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), cr.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response), cr.Response.TrimTo(1024)));
|
||||
}
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.deleted))
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(GetText(strs.trigger), cr.Trigger.TrimTo(1024))
|
||||
.AddField(GetText(strs.response), cr.Response.TrimTo(1024)));
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.no_found_id);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task CrReact(kwum id, params string[] emojiStrs)
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
@@ -181,7 +183,6 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
var succ = new List<string>();
|
||||
foreach (var emojiStr in emojiStrs)
|
||||
{
|
||||
|
||||
var emote = emojiStr.ToIEmote();
|
||||
|
||||
// i should try adding these emojis right away to the message, to make sure the bot can react with these emojis. If it fails, skip that emoji
|
||||
@@ -197,7 +198,7 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
catch { }
|
||||
}
|
||||
|
||||
if(succ.Count == 0)
|
||||
if (succ.Count == 0)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.invalid_emojis);
|
||||
return;
|
||||
@@ -206,27 +207,32 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
await _service.SetCrReactions(ctx.Guild?.Id, id, succ);
|
||||
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()), string.Join(", ", succ.Select(x => x.ToString()))));
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()),
|
||||
string.Join(", ", succ.Select(x => x.ToString()))));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public Task CrCa(kwum id)
|
||||
=> InternalCrEdit(id, CustomReactionsService.CrField.ContainsAnywhere);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public Task CrDm(kwum id)
|
||||
=> InternalCrEdit(id, CustomReactionsService.CrField.DmResponse);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public Task CrAd(kwum id)
|
||||
=> InternalCrEdit(id, CustomReactionsService.CrField.AutoDelete);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public Task CrAt(kwum id)
|
||||
=> InternalCrEdit(id, CustomReactionsService.CrField.AllowTarget);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task CrsReload()
|
||||
{
|
||||
@@ -243,6 +249,7 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
await ReplyErrorLocalizedAsync(strs.insuff_perms);
|
||||
return;
|
||||
}
|
||||
|
||||
var (success, newVal) = await _service.ToggleCrOptionAsync(id, option);
|
||||
if (!success)
|
||||
{
|
||||
@@ -251,30 +258,30 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
}
|
||||
|
||||
if (newVal)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.option_enabled(Format.Code(option.ToString()), Format.Code(id.ToString())));
|
||||
}
|
||||
await ReplyConfirmLocalizedAsync(strs.option_enabled(Format.Code(option.ToString()),
|
||||
Format.Code(id.ToString())));
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.option_disabled(Format.Code(option.ToString()), Format.Code(id.ToString())));
|
||||
}
|
||||
await ReplyConfirmLocalizedAsync(strs.option_disabled(Format.Code(option.ToString()),
|
||||
Format.Code(id.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task CrClear()
|
||||
{
|
||||
if (await PromptUserConfirmAsync(_eb.Create()
|
||||
.WithTitle("Custom reaction clear")
|
||||
.WithDescription("This will delete all custom reactions on this server.")))
|
||||
.WithTitle("Custom reaction clear")
|
||||
.WithDescription("This will delete all custom reactions on this server.")))
|
||||
{
|
||||
var count = _service.DeleteAllCustomReactions(ctx.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.cleared(count));
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task CrsExport()
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
@@ -282,19 +289,20 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
await ReplyErrorLocalizedAsync(strs.insuff_perms);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_ = ctx.Channel.TriggerTypingAsync();
|
||||
|
||||
var serialized = _service.ExportCrs(ctx.Guild?.Id);
|
||||
await using var stream = await serialized.ToStream();
|
||||
await ctx.Channel.SendFileAsync(stream, "crs-export.yml", text: null);
|
||||
await ctx.Channel.SendFileAsync(stream, "crs-export.yml");
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
#if GLOBAL_NADEKO
|
||||
[OwnerOnly]
|
||||
#endif
|
||||
public async Task CrsImport([Leftover]string input = null)
|
||||
public async Task CrsImport([Leftover] string input = null)
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
{
|
||||
@@ -331,7 +339,7 @@ public class CustomReactions : NadekoModule<CustomReactionsService>
|
||||
await ReplyErrorLocalizedAsync(strs.expr_import_invalid_data);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,13 @@ public static class CustomReactionExtensions
|
||||
private static string ResolveTriggerString(this string str, DiscordSocketClient client)
|
||||
=> str.Replace("%bot.mention%", client.CurrentUser.Mention, StringComparison.Ordinal);
|
||||
|
||||
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage ctx,
|
||||
DiscordSocketClient client, bool sanitize)
|
||||
public static async Task<IUserMessage> Send(
|
||||
this CustomReaction cr,
|
||||
IUserMessage ctx,
|
||||
DiscordSocketClient client,
|
||||
bool sanitize)
|
||||
{
|
||||
var channel = cr.DmResponse
|
||||
? await ctx.Author.CreateDMChannelAsync()
|
||||
: ctx.Channel;
|
||||
var channel = cr.DmResponse ? await ctx.Author.CreateDMChannelAsync() : ctx.Channel;
|
||||
|
||||
var trigger = cr.Trigger.ResolveTriggerString(client);
|
||||
var substringIndex = trigger.Length;
|
||||
@@ -32,11 +33,12 @@ public static class CustomReactionExtensions
|
||||
var canMentionEveryone = (ctx.Author as IGuildUser)?.GuildPermissions.MentionEveryone ?? true;
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild as SocketGuild, client)
|
||||
.WithOverride("%target%", () => canMentionEveryone
|
||||
? ctx.Content[substringIndex..].Trim()
|
||||
: ctx.Content[substringIndex..].Trim().SanitizeMentions(true))
|
||||
.Build();
|
||||
.WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild as SocketGuild, client)
|
||||
.WithOverride("%target%",
|
||||
() => canMentionEveryone
|
||||
? ctx.Content[substringIndex..].Trim()
|
||||
: ctx.Content[substringIndex..].Trim().SanitizeMentions(true))
|
||||
.Build();
|
||||
|
||||
var text = SmartText.CreateFrom(cr.Response);
|
||||
text = rep.Replace(text);
|
||||
@@ -62,7 +64,9 @@ public static class CustomReactionExtensions
|
||||
return WordPosition.End;
|
||||
}
|
||||
else if (str.isValidWordDivider(wordIndex - 1) && str.isValidWordDivider(wordIndex + word.Length))
|
||||
{
|
||||
return WordPosition.Middle;
|
||||
}
|
||||
|
||||
return WordPosition.None;
|
||||
}
|
||||
@@ -82,5 +86,5 @@ public enum WordPosition
|
||||
None,
|
||||
Start,
|
||||
Middle,
|
||||
End,
|
||||
}
|
||||
End
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Common.Yml;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.CustomReactions.Extensions;
|
||||
using NadekoBot.Modules.Permissions.Common;
|
||||
using NadekoBot.Modules.Permissions.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Yml;
|
||||
using NadekoBot.Db;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace NadekoBot.Modules.CustomReactions.Services;
|
||||
|
||||
@@ -20,16 +21,45 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
DmResponse,
|
||||
AllowTarget,
|
||||
ContainsAnywhere,
|
||||
Message,
|
||||
Message
|
||||
}
|
||||
|
||||
private const string MentionPh = "%bot.mention%";
|
||||
|
||||
private const string _prependExport =
|
||||
@"# Keys are triggers, Each key has a LIST of custom reactions in the following format:
|
||||
# - res: Response string
|
||||
# id: Alphanumeric id used for commands related to the custom reaction. (Note, when using .crsimport, a new id will be generated.)
|
||||
# react:
|
||||
# - <List
|
||||
# - of
|
||||
# - reactions>
|
||||
# at: Whether custom reaction allows targets (see .h .crat)
|
||||
# ca: Whether custom reaction expects trigger anywhere (see .h .crca)
|
||||
# dm: Whether custom reaction DMs the response (see .h .crdm)
|
||||
# ad: Whether custom reaction automatically deletes triggering message (see .h .crad)
|
||||
|
||||
";
|
||||
|
||||
private static readonly ISerializer _exportSerializer = new SerializerBuilder()
|
||||
.WithEventEmitter(args
|
||||
=> new MultilineScalarFlowStyleEmitter(args))
|
||||
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||
.WithIndentedSequences()
|
||||
.ConfigureDefaultValuesHandling(DefaultValuesHandling
|
||||
.OmitDefaults)
|
||||
.DisableAliases()
|
||||
.Build();
|
||||
|
||||
public int Priority
|
||||
=> 0;
|
||||
|
||||
private readonly object _gcrWriteLock = new();
|
||||
|
||||
private readonly TypedKey<CustomReaction> _gcrAddedKey = new("gcr.added");
|
||||
private readonly TypedKey<int> _gcrDeletedkey = new("gcr.deleted");
|
||||
private readonly TypedKey<CustomReaction> _gcrEditedKey = new("gcr.edited");
|
||||
private readonly TypedKey<bool> _crsReloadedKey = new("crs.reloaded");
|
||||
private const string MentionPh = "%bot.mention%";
|
||||
|
||||
// it is perfectly fine to have global customreactions as an array
|
||||
// 1. custom reactions are almost never added (compared to how many times they are being looped through)
|
||||
@@ -38,8 +68,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
private CustomReaction[] _globalReactions;
|
||||
private ConcurrentDictionary<ulong, CustomReaction[]> _newGuildReactions;
|
||||
|
||||
public int Priority => 0;
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly PermissionService _perms;
|
||||
@@ -52,9 +80,19 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
private readonly Random _rng;
|
||||
|
||||
public CustomReactionsService(PermissionService perms, DbService db, IBotStrings strings, Bot bot,
|
||||
DiscordSocketClient client, CommandHandler cmd, GlobalPermissionService gperm, CmdCdService cmdCds,
|
||||
IPubSub pubSub, IEmbedBuilderService eb)
|
||||
private bool ready;
|
||||
|
||||
public CustomReactionsService(
|
||||
PermissionService perms,
|
||||
DbService db,
|
||||
IBotStrings strings,
|
||||
Bot bot,
|
||||
DiscordSocketClient client,
|
||||
CommandHandler cmd,
|
||||
GlobalPermissionService gperm,
|
||||
CmdCdService cmdCds,
|
||||
IPubSub pubSub,
|
||||
IEmbedBuilderService eb)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
@@ -80,34 +118,31 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
private async Task ReloadInternal(IReadOnlyList<ulong> allGuildIds)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var guildItems = await uow.CustomReactions
|
||||
.AsNoTracking()
|
||||
.Where(x => allGuildIds.Contains(x.GuildId.Value))
|
||||
.ToListAsync();
|
||||
var guildItems = await uow.CustomReactions.AsNoTracking()
|
||||
.Where(x => allGuildIds.Contains(x.GuildId.Value))
|
||||
.ToListAsync();
|
||||
|
||||
_newGuildReactions = guildItems
|
||||
.GroupBy(k => k.GuildId!.Value)
|
||||
.ToDictionary(g => g.Key,
|
||||
g => g.Select(x =>
|
||||
{
|
||||
x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention);
|
||||
return x;
|
||||
}).ToArray())
|
||||
.ToConcurrent();
|
||||
_newGuildReactions = guildItems.GroupBy(k => k.GuildId!.Value)
|
||||
.ToDictionary(g => g.Key,
|
||||
g => g.Select(x =>
|
||||
{
|
||||
x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention);
|
||||
return x;
|
||||
})
|
||||
.ToArray())
|
||||
.ToConcurrent();
|
||||
|
||||
lock (_gcrWriteLock)
|
||||
{
|
||||
var globalItems = uow
|
||||
.CustomReactions
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == null || x.GuildId == 0)
|
||||
.AsEnumerable()
|
||||
.Select(x =>
|
||||
{
|
||||
x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention);
|
||||
return x;
|
||||
})
|
||||
.ToArray();
|
||||
var globalItems = uow.CustomReactions.AsNoTracking()
|
||||
.Where(x => x.GuildId == null || x.GuildId == 0)
|
||||
.AsEnumerable()
|
||||
.Select(x =>
|
||||
{
|
||||
x.Trigger = x.Trigger.Replace(MentionPh, _bot.Mention);
|
||||
return x;
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
_globalReactions = globalItems;
|
||||
}
|
||||
@@ -115,176 +150,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
ready = true;
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
public Task OnReadyAsync()
|
||||
=> ReloadInternal(_bot.GetCurrentGuildIds());
|
||||
|
||||
private ValueTask OnCrsShouldReload(bool _) => new(ReloadInternal(_bot.GetCurrentGuildIds()));
|
||||
|
||||
private ValueTask OnGcrAdded(CustomReaction c)
|
||||
{
|
||||
lock (_gcrWriteLock)
|
||||
{
|
||||
var newGlobalReactions = new CustomReaction[_globalReactions.Length + 1];
|
||||
Array.Copy(_globalReactions, newGlobalReactions, _globalReactions.Length);
|
||||
newGlobalReactions[_globalReactions.Length] = c;
|
||||
_globalReactions = newGlobalReactions;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private ValueTask OnGcrEdited(CustomReaction c)
|
||||
{
|
||||
lock (_gcrWriteLock)
|
||||
{
|
||||
for (var i = 0; i < _globalReactions.Length; i++)
|
||||
{
|
||||
if (_globalReactions[i].Id == c.Id)
|
||||
{
|
||||
_globalReactions[i] = c;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
// if edited cr is not found?!
|
||||
// add it
|
||||
OnGcrAdded(c);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private ValueTask OnGcrDeleted(int id)
|
||||
{
|
||||
lock (_gcrWriteLock)
|
||||
{
|
||||
var newGlobalReactions = DeleteInternal(_globalReactions, id, out _);
|
||||
_globalReactions = newGlobalReactions;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public Task TriggerReloadCustomReactions()
|
||||
=> _pubSub.Pub(_crsReloadedKey, true);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Client Event Handlers
|
||||
|
||||
private Task OnLeftGuild(SocketGuild arg)
|
||||
{
|
||||
_newGuildReactions.TryRemove(arg.Id, out _);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task OnJoinedGuild(GuildConfig gc)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var crs = await uow
|
||||
.CustomReactions
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == gc.GuildId)
|
||||
.ToArrayAsync();
|
||||
|
||||
_newGuildReactions[gc.GuildId] = crs;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Basic Operations
|
||||
|
||||
public async Task<CustomReaction> AddAsync(ulong? guildId, string key, string message)
|
||||
{
|
||||
key = key.ToLowerInvariant();
|
||||
var cr = new CustomReaction()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Trigger = key,
|
||||
Response = message,
|
||||
};
|
||||
|
||||
if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
cr.AllowTarget = true;
|
||||
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
uow.CustomReactions.Add(cr);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await AddInternalAsync(guildId, cr);
|
||||
|
||||
return cr;
|
||||
}
|
||||
|
||||
public async Task<CustomReaction> EditAsync(ulong? guildId, int id, string message)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var cr = uow.CustomReactions.GetById(id);
|
||||
|
||||
if (cr is null || cr.GuildId != guildId)
|
||||
return null;
|
||||
|
||||
// disable allowtarget if message had target, but it was removed from it
|
||||
if (!message.Contains("%target%", StringComparison.OrdinalIgnoreCase)
|
||||
&& cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cr.AllowTarget = false;
|
||||
}
|
||||
|
||||
cr.Response = message;
|
||||
|
||||
// enable allow target if message is edited to contain target
|
||||
if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
cr.AllowTarget = true;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
await UpdateInternalAsync(guildId, cr);
|
||||
|
||||
return cr;
|
||||
}
|
||||
|
||||
|
||||
public async Task<CustomReaction> DeleteAsync(ulong? guildId, int id)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toDelete = uow.CustomReactions.GetById(id);
|
||||
|
||||
if (toDelete is null)
|
||||
return null;
|
||||
|
||||
if ((toDelete.IsGlobal() && guildId is null) || guildId == toDelete.GuildId)
|
||||
{
|
||||
uow.CustomReactions.Remove(toDelete);
|
||||
await uow.SaveChangesAsync();
|
||||
await DeleteInternalAsync(guildId, id);
|
||||
return toDelete;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public CustomReaction[] GetCustomReactionsFor(ulong? maybeGuildId)
|
||||
{
|
||||
if (maybeGuildId is { } guildId)
|
||||
{
|
||||
return _newGuildReactions.TryGetValue(guildId, out var crs)
|
||||
? crs
|
||||
: Array.Empty<CustomReaction>();
|
||||
}
|
||||
|
||||
return _globalReactions;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool ready;
|
||||
|
||||
private CustomReaction TryGetCustomReaction(IUserMessage umsg)
|
||||
{
|
||||
if (!ready)
|
||||
@@ -294,7 +159,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
return null;
|
||||
|
||||
var content = umsg.Content.Trim().ToLowerInvariant();
|
||||
|
||||
|
||||
if (_newGuildReactions.TryGetValue(channel.Guild.Id, out var reactions) && reactions.Length > 0)
|
||||
{
|
||||
var cr = MatchCustomReactions(content, reactions);
|
||||
@@ -325,10 +190,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
var wp = content.GetWordPosition(trigger);
|
||||
|
||||
// if it is, then that's valid
|
||||
if (wp != WordPosition.None)
|
||||
{
|
||||
result.Add(cr);
|
||||
}
|
||||
if (wp != WordPosition.None) result.Add(cr);
|
||||
|
||||
// if it's not, then it cant' work under any circumstance,
|
||||
// because content is greater than the trigger length
|
||||
@@ -338,11 +200,10 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
|
||||
// if CA is disabled, and CR has AllowTarget, then the
|
||||
// content has to start with the trigger followed by a space
|
||||
if (cr.AllowTarget && content.StartsWith(trigger, StringComparison.OrdinalIgnoreCase)
|
||||
&& content[trigger.Length] == ' ')
|
||||
{
|
||||
if (cr.AllowTarget
|
||||
&& content.StartsWith(trigger, StringComparison.OrdinalIgnoreCase)
|
||||
&& content[trigger.Length] == ' ')
|
||||
result.Add(cr);
|
||||
}
|
||||
}
|
||||
else if (content.Length < cr.Trigger.Length)
|
||||
{
|
||||
@@ -353,10 +214,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
{
|
||||
// if input length is the same as trigger length
|
||||
// reaction can only trigger if the strings are equal
|
||||
if (content.SequenceEqual(cr.Trigger))
|
||||
{
|
||||
result.Add(cr);
|
||||
}
|
||||
if (content.SequenceEqual(cr.Trigger)) result.Add(cr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +224,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
var cancelled = result.FirstOrDefault(x => x.Response == "-");
|
||||
if (cancelled is not null)
|
||||
return cancelled;
|
||||
|
||||
|
||||
return result[_rng.Next(0, result.Count)];
|
||||
}
|
||||
|
||||
@@ -377,34 +235,33 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
|
||||
if (cr is null || cr.Response == "-")
|
||||
return false;
|
||||
|
||||
if(await _cmdCds.TryBlock(guild, msg.Author, cr.Trigger))
|
||||
|
||||
if (await _cmdCds.TryBlock(guild, msg.Author, cr.Trigger))
|
||||
return false;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (_gperm.BlockedModules.Contains("ActualCustomReactions"))
|
||||
{
|
||||
Log.Information("User {UserName} [{UserId}] tried to use a custom reaction but 'ActualCustomReactions' are globally disabled.",
|
||||
Log.Information(
|
||||
"User {UserName} [{UserId}] tried to use a custom reaction but 'ActualCustomReactions' are globally disabled.",
|
||||
msg.Author.ToString(),
|
||||
msg.Author.Id);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (guild is SocketGuild sg)
|
||||
{
|
||||
var pc = _perms.GetCacheFor(guild.Id);
|
||||
if (!pc.Permissions.CheckPermissions(msg, cr.Trigger, "ActualCustomReactions",
|
||||
out var index))
|
||||
if (!pc.Permissions.CheckPermissions(msg, cr.Trigger, "ActualCustomReactions", out var index))
|
||||
{
|
||||
if (pc.Verbose)
|
||||
{
|
||||
var returnMsg = _strings.GetText(
|
||||
strs.perm_prevent(index + 1,
|
||||
var returnMsg = _strings.GetText(strs.perm_prevent(index + 1,
|
||||
Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg))),
|
||||
sg.Id);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
await msg.Channel.SendErrorAsync(_eb, returnMsg);
|
||||
@@ -431,7 +288,8 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log.Warning("Unable to add reactions to message {Message} in server {GuildId}", sentMsg.Id,
|
||||
Log.Warning("Unable to add reactions to message {Message} in server {GuildId}",
|
||||
sentMsg.Id,
|
||||
cr.GuildId);
|
||||
break;
|
||||
}
|
||||
@@ -440,7 +298,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
}
|
||||
|
||||
if (cr.AutoDeleteTrigger)
|
||||
{
|
||||
try
|
||||
{
|
||||
await msg.DeleteAsync();
|
||||
@@ -448,7 +305,6 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Log.Information("s: {GuildId} c: {ChannelId} u: {UserId} | {UserName} executed expression {Expr}",
|
||||
guild.Id,
|
||||
@@ -456,7 +312,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
msg.Author.Id,
|
||||
msg.Author.ToString(),
|
||||
cr.Trigger);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -493,31 +349,24 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
private void UpdateInternal(ulong? maybeGuildId, CustomReaction cr)
|
||||
{
|
||||
if (maybeGuildId is { } guildId)
|
||||
{
|
||||
_newGuildReactions.AddOrUpdate(guildId, new[] {cr},
|
||||
_newGuildReactions.AddOrUpdate(guildId,
|
||||
new[] { cr },
|
||||
(key, old) =>
|
||||
{
|
||||
var newArray = old.ToArray();
|
||||
for (var i = 0; i < newArray.Length; i++)
|
||||
{
|
||||
if (newArray[i].Id == cr.Id)
|
||||
newArray[i] = cr;
|
||||
}
|
||||
return newArray;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_gcrWriteLock)
|
||||
{
|
||||
var crs = _globalReactions;
|
||||
for (var i = 0; i < crs.Length; i++)
|
||||
{
|
||||
if (crs[i].Id == cr.Id)
|
||||
crs[i] = cr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task AddInternalAsync(ulong? maybeGuildId, CustomReaction cr)
|
||||
@@ -526,19 +375,13 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
cr.Trigger = cr.Trigger.Replace(MentionPh, _client.CurrentUser.Mention);
|
||||
|
||||
if (maybeGuildId is { } guildId)
|
||||
{
|
||||
_newGuildReactions.AddOrUpdate(guildId,
|
||||
new[] {cr},
|
||||
(key, old) => old.With(cr));
|
||||
}
|
||||
_newGuildReactions.AddOrUpdate(guildId, new[] { cr }, (key, old) => old.With(cr));
|
||||
else
|
||||
{
|
||||
return _pubSub.Pub(_gcrAddedKey, cr);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
private Task DeleteInternalAsync(ulong? maybeGuildId, int id)
|
||||
{
|
||||
if (maybeGuildId is { } guildId)
|
||||
@@ -546,17 +389,14 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
_newGuildReactions.AddOrUpdate(guildId,
|
||||
Array.Empty<CustomReaction>(),
|
||||
(key, old) => DeleteInternal(old, id, out _));
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
lock (_gcrWriteLock)
|
||||
{
|
||||
var cr = Array.Find(_globalReactions, item => item.Id == id);
|
||||
if (cr is not null)
|
||||
{
|
||||
return _pubSub.Pub(_gcrDeletedkey, cr.Id);
|
||||
}
|
||||
if (cr is not null) return _pubSub.Pub(_gcrDeletedkey, cr.Id);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -567,7 +407,7 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
deleted = null;
|
||||
if (crs is null || crs.Count == 0)
|
||||
return crs as CustomReaction[] ?? crs?.ToArray();
|
||||
|
||||
|
||||
var newCrs = new CustomReaction[crs.Count - 1];
|
||||
for (int i = 0, k = 0; i < crs.Count; i++, k++)
|
||||
{
|
||||
@@ -636,13 +476,13 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
|
||||
return cr;
|
||||
}
|
||||
|
||||
|
||||
public int DeleteAllCustomReactions(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var count = uow.CustomReactions.ClearFromGuild(guildId);
|
||||
uow.SaveChanges();
|
||||
|
||||
|
||||
_newGuildReactions.TryRemove(guildId, out _);
|
||||
|
||||
return count;
|
||||
@@ -655,39 +495,13 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
return cr != null;
|
||||
}
|
||||
|
||||
private static readonly ISerializer _exportSerializer = new SerializerBuilder()
|
||||
.WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
|
||||
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
||||
.WithIndentedSequences()
|
||||
.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults)
|
||||
.DisableAliases()
|
||||
.Build();
|
||||
|
||||
private const string _prependExport =
|
||||
@"# Keys are triggers, Each key has a LIST of custom reactions in the following format:
|
||||
# - res: Response string
|
||||
# id: Alphanumeric id used for commands related to the custom reaction. (Note, when using .crsimport, a new id will be generated.)
|
||||
# react:
|
||||
# - <List
|
||||
# - of
|
||||
# - reactions>
|
||||
# at: Whether custom reaction allows targets (see .h .crat)
|
||||
# ca: Whether custom reaction expects trigger anywhere (see .h .crca)
|
||||
# dm: Whether custom reaction DMs the response (see .h .crdm)
|
||||
# ad: Whether custom reaction automatically deletes triggering message (see .h .crad)
|
||||
|
||||
";
|
||||
public string ExportCrs(ulong? guildId)
|
||||
{
|
||||
var crs = GetCustomReactionsFor(guildId);
|
||||
|
||||
var crsDict = crs
|
||||
.GroupBy(x => x.Trigger)
|
||||
.ToDictionary(x => x.Key, x => x.Select(ExportedExpr.FromModel));
|
||||
|
||||
return _prependExport + _exportSerializer
|
||||
.Serialize(crsDict)
|
||||
.UnescapeUnicodeCodePoints();
|
||||
var crsDict = crs.GroupBy(x => x.Trigger).ToDictionary(x => x.Key, x => x.Select(ExportedExpr.FromModel));
|
||||
|
||||
return _prependExport + _exportSerializer.Serialize(crsDict).UnescapeUnicodeCodePoints();
|
||||
}
|
||||
|
||||
public async Task<bool> ImportCrsAsync(ulong? guildId, string input)
|
||||
@@ -708,23 +522,174 @@ public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
|
||||
foreach (var entry in data)
|
||||
{
|
||||
var trigger = entry.Key;
|
||||
await uow.CustomReactions.AddRangeAsync(entry.Value
|
||||
.Where(cr => !string.IsNullOrWhiteSpace(cr.Res))
|
||||
.Select(cr => new CustomReaction()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Response = cr.Res,
|
||||
Reactions = cr.React?.Join("@@@"),
|
||||
Trigger = trigger,
|
||||
AllowTarget = cr.At,
|
||||
ContainsAnywhere = cr.Ca,
|
||||
DmResponse = cr.Dm,
|
||||
AutoDeleteTrigger = cr.Ad,
|
||||
}));
|
||||
await uow.CustomReactions.AddRangeAsync(entry.Value.Where(cr => !string.IsNullOrWhiteSpace(cr.Res))
|
||||
.Select(cr => new CustomReaction
|
||||
{
|
||||
GuildId = guildId,
|
||||
Response = cr.Res,
|
||||
Reactions = cr.React?.Join("@@@"),
|
||||
Trigger = trigger,
|
||||
AllowTarget = cr.At,
|
||||
ContainsAnywhere = cr.Ca,
|
||||
DmResponse = cr.Dm,
|
||||
AutoDeleteTrigger = cr.Ad
|
||||
}));
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
await TriggerReloadCustomReactions();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
public Task OnReadyAsync()
|
||||
=> ReloadInternal(_bot.GetCurrentGuildIds());
|
||||
|
||||
private ValueTask OnCrsShouldReload(bool _)
|
||||
=> new(ReloadInternal(_bot.GetCurrentGuildIds()));
|
||||
|
||||
private ValueTask OnGcrAdded(CustomReaction c)
|
||||
{
|
||||
lock (_gcrWriteLock)
|
||||
{
|
||||
var newGlobalReactions = new CustomReaction[_globalReactions.Length + 1];
|
||||
Array.Copy(_globalReactions, newGlobalReactions, _globalReactions.Length);
|
||||
newGlobalReactions[_globalReactions.Length] = c;
|
||||
_globalReactions = newGlobalReactions;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private ValueTask OnGcrEdited(CustomReaction c)
|
||||
{
|
||||
lock (_gcrWriteLock)
|
||||
{
|
||||
for (var i = 0; i < _globalReactions.Length; i++)
|
||||
if (_globalReactions[i].Id == c.Id)
|
||||
{
|
||||
_globalReactions[i] = c;
|
||||
return default;
|
||||
}
|
||||
|
||||
// if edited cr is not found?!
|
||||
// add it
|
||||
OnGcrAdded(c);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private ValueTask OnGcrDeleted(int id)
|
||||
{
|
||||
lock (_gcrWriteLock)
|
||||
{
|
||||
var newGlobalReactions = DeleteInternal(_globalReactions, id, out _);
|
||||
_globalReactions = newGlobalReactions;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public Task TriggerReloadCustomReactions()
|
||||
=> _pubSub.Pub(_crsReloadedKey, true);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Client Event Handlers
|
||||
|
||||
private Task OnLeftGuild(SocketGuild arg)
|
||||
{
|
||||
_newGuildReactions.TryRemove(arg.Id, out _);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task OnJoinedGuild(GuildConfig gc)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var crs = await uow.CustomReactions.AsNoTracking().Where(x => x.GuildId == gc.GuildId).ToArrayAsync();
|
||||
|
||||
_newGuildReactions[gc.GuildId] = crs;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Basic Operations
|
||||
|
||||
public async Task<CustomReaction> AddAsync(ulong? guildId, string key, string message)
|
||||
{
|
||||
key = key.ToLowerInvariant();
|
||||
var cr = new CustomReaction { GuildId = guildId, Trigger = key, Response = message };
|
||||
|
||||
if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
cr.AllowTarget = true;
|
||||
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
uow.CustomReactions.Add(cr);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await AddInternalAsync(guildId, cr);
|
||||
|
||||
return cr;
|
||||
}
|
||||
|
||||
public async Task<CustomReaction> EditAsync(ulong? guildId, int id, string message)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var cr = uow.CustomReactions.GetById(id);
|
||||
|
||||
if (cr is null || cr.GuildId != guildId)
|
||||
return null;
|
||||
|
||||
// disable allowtarget if message had target, but it was removed from it
|
||||
if (!message.Contains("%target%", StringComparison.OrdinalIgnoreCase)
|
||||
&& cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
cr.AllowTarget = false;
|
||||
|
||||
cr.Response = message;
|
||||
|
||||
// enable allow target if message is edited to contain target
|
||||
if (cr.Response.Contains("%target%", StringComparison.OrdinalIgnoreCase))
|
||||
cr.AllowTarget = true;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
await UpdateInternalAsync(guildId, cr);
|
||||
|
||||
return cr;
|
||||
}
|
||||
|
||||
|
||||
public async Task<CustomReaction> DeleteAsync(ulong? guildId, int id)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toDelete = uow.CustomReactions.GetById(id);
|
||||
|
||||
if (toDelete is null)
|
||||
return null;
|
||||
|
||||
if ((toDelete.IsGlobal() && guildId is null) || guildId == toDelete.GuildId)
|
||||
{
|
||||
uow.CustomReactions.Remove(toDelete);
|
||||
await uow.SaveChangesAsync();
|
||||
await DeleteInternalAsync(guildId, id);
|
||||
return toDelete;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public CustomReaction[] GetCustomReactionsFor(ulong? maybeGuildId)
|
||||
{
|
||||
if (maybeGuildId is { } guildId)
|
||||
return _newGuildReactions.TryGetValue(guildId, out var crs) ? crs : Array.Empty<CustomReaction>();
|
||||
|
||||
return _globalReactions;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Games.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
@@ -17,17 +17,22 @@ public partial class Gambling
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly GamesConfigService _gamesConf;
|
||||
|
||||
public AnimalRacingCommands(ICurrencyService cs, DiscordSocketClient client,
|
||||
GamblingConfigService gamblingConf, GamesConfigService gamesConf) : base(gamblingConf)
|
||||
private IUserMessage raceMessage;
|
||||
|
||||
public AnimalRacingCommands(
|
||||
ICurrencyService cs,
|
||||
DiscordSocketClient client,
|
||||
GamblingConfigService gamblingConf,
|
||||
GamesConfigService gamesConf)
|
||||
: base(gamblingConf)
|
||||
{
|
||||
_cs = cs;
|
||||
_client = client;
|
||||
_gamesConf = gamesConf;
|
||||
}
|
||||
|
||||
private IUserMessage raceMessage = null;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NadekoOptionsAttribute(typeof(RaceOptions))]
|
||||
public Task Race(params string[] args)
|
||||
@@ -41,6 +46,7 @@ public partial class Gambling
|
||||
ar.Initialize();
|
||||
|
||||
var count = 0;
|
||||
|
||||
Task _client_MessageReceived(SocketMessage arg)
|
||||
{
|
||||
var _ = Task.Run(() =>
|
||||
@@ -48,12 +54,8 @@ public partial class Gambling
|
||||
try
|
||||
{
|
||||
if (arg.Channel.Id == ctx.Channel.Id)
|
||||
{
|
||||
if (ar.CurrentPhase == AnimalRace.Phase.Running && ++count % 9 == 0)
|
||||
{
|
||||
raceMessage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
@@ -66,18 +68,12 @@ public partial class Gambling
|
||||
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
|
||||
var winner = race.FinishedUsers[0];
|
||||
if (race.FinishedUsers[0].Bet > 0)
|
||||
{
|
||||
return SendConfirmAsync(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_won_money(
|
||||
Format.Bold(winner.Username),
|
||||
GetText(strs.animal_race_won_money(Format.Bold(winner.Username),
|
||||
winner.Animal.Icon,
|
||||
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return SendConfirmAsync(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)));
|
||||
}
|
||||
return SendConfirmAsync(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)));
|
||||
}
|
||||
|
||||
ar.OnStartingFailed += Ar_OnStartingFailed;
|
||||
@@ -86,7 +82,8 @@ public partial class Gambling
|
||||
ar.OnStarted += Ar_OnStarted;
|
||||
_client.MessageReceived += _client_MessageReceived;
|
||||
|
||||
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_starting(options.StartTime)),
|
||||
return SendConfirmAsync(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_starting(options.StartTime)),
|
||||
footer: GetText(strs.animal_race_join_instr(Prefix)));
|
||||
}
|
||||
|
||||
@@ -94,14 +91,14 @@ public partial class Gambling
|
||||
{
|
||||
if (race.Users.Count == race.MaxUsers)
|
||||
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full));
|
||||
else
|
||||
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_starting_with_x(race.Users.Count)));
|
||||
return SendConfirmAsync(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_starting_with_x(race.Users.Count)));
|
||||
}
|
||||
|
||||
private async Task Ar_OnStateUpdate(AnimalRace race)
|
||||
{
|
||||
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
|
||||
{String.Join("\n", race.Users.Select(p =>
|
||||
{string.Join("\n", race.Users.Select(p =>
|
||||
{
|
||||
var index = race.FinishedUsers.IndexOf(p);
|
||||
var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}";
|
||||
@@ -115,10 +112,10 @@ public partial class Gambling
|
||||
raceMessage = await SendConfirmAsync(text);
|
||||
else
|
||||
await msg.ModifyAsync(x => x.Embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(text)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(text)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
|
||||
private Task Ar_OnStartingFailed(AnimalRace race)
|
||||
@@ -127,7 +124,8 @@ public partial class Gambling
|
||||
return ReplyErrorLocalizedAsync(strs.animal_race_failed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task JoinRace(ShmartNumber amount = default)
|
||||
{
|
||||
@@ -139,11 +137,14 @@ public partial class Gambling
|
||||
await ReplyErrorLocalizedAsync(strs.race_not_exist);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var user = await ar.JoinRace(ctx.User.Id, ctx.User.ToString(), amount);
|
||||
if (amount > 0)
|
||||
await SendConfirmAsync(GetText(strs.animal_race_join_bet(ctx.User.Mention, user.Animal.Icon, amount + CurrencySign)));
|
||||
await SendConfirmAsync(GetText(strs.animal_race_join_bet(ctx.User.Mention,
|
||||
user.Animal.Icon,
|
||||
amount + CurrencySign)));
|
||||
else
|
||||
await SendConfirmAsync(GetText(strs.animal_race_join(ctx.User.Mention, user.Animal.Icon)));
|
||||
}
|
||||
@@ -169,4 +170,4 @@ public partial class Gambling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,25 +9,26 @@ public partial class Gambling
|
||||
{
|
||||
public class BlackJackCommands : GamblingSubmodule<BlackJackService>
|
||||
{
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
private IUserMessage _msg;
|
||||
|
||||
public enum BjAction
|
||||
{
|
||||
Hit = int.MinValue,
|
||||
Stand,
|
||||
Double,
|
||||
Double
|
||||
}
|
||||
|
||||
public BlackJackCommands(ICurrencyService cs, DbService db,
|
||||
GamblingConfigService gamblingConf) : base(gamblingConf)
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
private IUserMessage _msg;
|
||||
|
||||
public BlackJackCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConf)
|
||||
: base(gamblingConf)
|
||||
{
|
||||
_cs = cs;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task BlackJack(ShmartNumber amount)
|
||||
{
|
||||
@@ -44,6 +45,7 @@ public partial class Gambling
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
bj.StateUpdated += Bj_StateUpdated;
|
||||
bj.GameEnded += Bj_GameEnded;
|
||||
bj.Start();
|
||||
@@ -55,9 +57,8 @@ public partial class Gambling
|
||||
if (await bj.Join(ctx.User, amount))
|
||||
await ReplyConfirmLocalizedAsync(strs.bj_joined);
|
||||
else
|
||||
{
|
||||
Log.Information($"{ctx.User} can't join a blackjack game as it's in " + bj.State.ToString() + " state already.");
|
||||
}
|
||||
Log.Information(
|
||||
$"{ctx.User} can't join a blackjack game as it's in " + bj.State + " state already.");
|
||||
}
|
||||
|
||||
await ctx.Message.DeleteAsync();
|
||||
@@ -93,14 +94,11 @@ public partial class Gambling
|
||||
var cStr = string.Concat(c.Select(x => x[..^1] + " "));
|
||||
cStr += "\n" + string.Concat(c.Select(x => x.Last() + " "));
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("BlackJack")
|
||||
.AddField($"{dealerIcon} Dealer's Hand | Value: {bj.Dealer.GetHandValue()}", cStr);
|
||||
.WithOkColor()
|
||||
.WithTitle("BlackJack")
|
||||
.AddField($"{dealerIcon} Dealer's Hand | Value: {bj.Dealer.GetHandValue()}", cStr);
|
||||
|
||||
if (bj.CurrentUser != null)
|
||||
{
|
||||
embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser.ToString()}");
|
||||
}
|
||||
if (bj.CurrentUser != null) embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser}");
|
||||
|
||||
foreach (var p in bj.Players)
|
||||
{
|
||||
@@ -111,37 +109,42 @@ public partial class Gambling
|
||||
if (bj.State == Blackjack.GameState.Ended)
|
||||
{
|
||||
if (p.State == User.UserState.Lost)
|
||||
{
|
||||
full = "❌ " + full;
|
||||
}
|
||||
else
|
||||
{
|
||||
full = "✅ " + full;
|
||||
}
|
||||
}
|
||||
else if (p == bj.CurrentUser)
|
||||
{
|
||||
full = "▶ " + full;
|
||||
}
|
||||
else if (p.State == User.UserState.Stand)
|
||||
{
|
||||
full = "⏹ " + full;
|
||||
}
|
||||
else if (p.State == User.UserState.Bust)
|
||||
{
|
||||
full = "💥 " + full;
|
||||
}
|
||||
else if (p.State == User.UserState.Blackjack)
|
||||
{
|
||||
full = "💰 " + full;
|
||||
}
|
||||
|
||||
embed.AddField(full, cStr);
|
||||
}
|
||||
|
||||
_msg = await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private string UserToString(User x)
|
||||
{
|
||||
var playerName = x.State == User.UserState.Bust ?
|
||||
Format.Strikethrough(x.DiscordUser.ToString().TrimTo(30)) :
|
||||
x.DiscordUser.ToString();
|
||||
var playerName = x.State == User.UserState.Bust
|
||||
? Format.Strikethrough(x.DiscordUser.ToString().TrimTo(30))
|
||||
: x.DiscordUser.ToString();
|
||||
|
||||
var hand = $"{string.Concat(x.Cards.Select(y => "〖" + y.GetEmojiString() + "〗"))}";
|
||||
|
||||
@@ -149,17 +152,23 @@ public partial class Gambling
|
||||
return $"{playerName} | Bet: {x.Bet}\n";
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Hit() => InternalBlackJack(BjAction.Hit);
|
||||
public Task Hit()
|
||||
=> InternalBlackJack(BjAction.Hit);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Stand() => InternalBlackJack(BjAction.Stand);
|
||||
public Task Stand()
|
||||
=> InternalBlackJack(BjAction.Stand);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Double() => InternalBlackJack(BjAction.Double);
|
||||
public Task Double()
|
||||
=> InternalBlackJack(BjAction.Double);
|
||||
|
||||
public async Task InternalBlackJack(BjAction a)
|
||||
{
|
||||
@@ -171,14 +180,10 @@ public partial class Gambling
|
||||
else if (a == BjAction.Stand)
|
||||
await bj.Stand(ctx.User);
|
||||
else if (a == BjAction.Double)
|
||||
{
|
||||
if (!await bj.Double(ctx.User))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.Message.DeleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,34 +10,36 @@ public sealed class AnimalRace : IDisposable
|
||||
{
|
||||
WaitingForPlayers,
|
||||
Running,
|
||||
Ended,
|
||||
Ended
|
||||
}
|
||||
|
||||
public Phase CurrentPhase { get; private set; } = Phase.WaitingForPlayers;
|
||||
|
||||
public event Func<AnimalRace, Task> OnStarted = delegate { return Task.CompletedTask; };
|
||||
public event Func<AnimalRace, Task> OnStartingFailed = delegate { return Task.CompletedTask; };
|
||||
public event Func<AnimalRace, Task> OnStateUpdate = delegate { return Task.CompletedTask; };
|
||||
public event Func<AnimalRace, Task> OnEnded = delegate { return Task.CompletedTask; };
|
||||
|
||||
public IReadOnlyCollection<AnimalRacingUser> Users => _users.ToList();
|
||||
public Phase CurrentPhase { get; private set; } = Phase.WaitingForPlayers;
|
||||
|
||||
public IReadOnlyCollection<AnimalRacingUser> Users
|
||||
=> _users.ToList();
|
||||
|
||||
public List<AnimalRacingUser> FinishedUsers { get; } = new();
|
||||
public int MaxUsers { get; }
|
||||
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
private readonly HashSet<AnimalRacingUser> _users = new();
|
||||
private readonly ICurrencyService _currency;
|
||||
private readonly RaceOptions _options;
|
||||
private readonly Queue<RaceAnimal> _animalsQueue;
|
||||
public int MaxUsers { get; }
|
||||
|
||||
public AnimalRace(RaceOptions options, ICurrencyService currency, IEnumerable<RaceAnimal> availableAnimals)
|
||||
{
|
||||
this._currency = currency;
|
||||
this._options = options;
|
||||
this._animalsQueue = new(availableAnimals);
|
||||
this.MaxUsers = _animalsQueue.Count;
|
||||
_currency = currency;
|
||||
_options = options;
|
||||
_animalsQueue = new(availableAnimals);
|
||||
MaxUsers = _animalsQueue.Count;
|
||||
|
||||
if (this._animalsQueue.Count == 0)
|
||||
if (_animalsQueue.Count == 0)
|
||||
CurrentPhase = Phase.Ended;
|
||||
}
|
||||
|
||||
@@ -99,10 +101,8 @@ public sealed class AnimalRace : IDisposable
|
||||
if (_users.Count <= 1)
|
||||
{
|
||||
foreach (var user in _users)
|
||||
{
|
||||
if (user.Bet > 0)
|
||||
await _currency.AddAsync(user.UserId, "Race refund", user.Bet);
|
||||
}
|
||||
|
||||
var _sf = OnStartingFailed?.Invoke(this);
|
||||
CurrentPhase = Phase.Ended;
|
||||
@@ -122,8 +122,7 @@ public sealed class AnimalRace : IDisposable
|
||||
user.Progress = 60;
|
||||
}
|
||||
|
||||
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x))
|
||||
.Shuffle();
|
||||
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x)).Shuffle();
|
||||
|
||||
FinishedUsers.AddRange(finished);
|
||||
|
||||
@@ -132,7 +131,9 @@ public sealed class AnimalRace : IDisposable
|
||||
}
|
||||
|
||||
if (FinishedUsers[0].Bet > 0)
|
||||
await _currency.AddAsync(FinishedUsers[0].UserId, "Won a Race", FinishedUsers[0].Bet * (_users.Count - 1));
|
||||
await _currency.AddAsync(FinishedUsers[0].UserId,
|
||||
"Won a Race",
|
||||
FinishedUsers[0].Bet * (_users.Count - 1));
|
||||
|
||||
var _ended = OnEnded?.Invoke(this);
|
||||
});
|
||||
@@ -148,4 +149,4 @@ public sealed class AnimalRace : IDisposable
|
||||
_locker.Dispose();
|
||||
_users.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,16 +13,14 @@ public class AnimalRacingUser
|
||||
|
||||
public AnimalRacingUser(string username, ulong userId, long bet)
|
||||
{
|
||||
this.Bet = bet;
|
||||
this.Username = username;
|
||||
this.UserId = userId;
|
||||
Bet = bet;
|
||||
Username = username;
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is AnimalRacingUser x
|
||||
? x.UserId == this.UserId
|
||||
: false;
|
||||
=> obj is AnimalRacingUser x ? x.UserId == UserId : false;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> this.UserId.GetHashCode();
|
||||
}
|
||||
=> UserId.GetHashCode();
|
||||
}
|
||||
@@ -5,14 +5,15 @@ public class AlreadyJoinedException : Exception
|
||||
{
|
||||
public AlreadyJoinedException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AlreadyJoinedException(string message) : base(message)
|
||||
public AlreadyJoinedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AlreadyJoinedException(string message, Exception innerException) : base(message, innerException)
|
||||
public AlreadyJoinedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,13 @@ public class AlreadyStartedException : Exception
|
||||
{
|
||||
}
|
||||
|
||||
public AlreadyStartedException(string message) : base(message)
|
||||
public AlreadyStartedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AlreadyStartedException(string message, Exception innerException) : base(message, innerException)
|
||||
public AlreadyStartedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,13 @@ public class AnimalRaceFullException : Exception
|
||||
{
|
||||
}
|
||||
|
||||
public AnimalRaceFullException(string message) : base(message)
|
||||
public AnimalRaceFullException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AnimalRaceFullException(string message, Exception innerException) : base(message, innerException)
|
||||
public AnimalRaceFullException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,13 @@ public class NotEnoughFundsException : Exception
|
||||
{
|
||||
}
|
||||
|
||||
public NotEnoughFundsException(string message) : base(message)
|
||||
public NotEnoughFundsException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public NotEnoughFundsException(string message, Exception innerException) : base(message, innerException)
|
||||
public NotEnoughFundsException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public class RaceOptions : INadekoCommandOptions
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (this.StartTime is < 10 or > 120)
|
||||
this.StartTime = 20;
|
||||
if (StartTime is < 10 or > 120)
|
||||
StartTime = 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,9 @@ namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class Betroll
|
||||
{
|
||||
public class Result
|
||||
{
|
||||
public int Roll { get; set; }
|
||||
public float Multiplier { get; set; }
|
||||
public int Threshold { get; set; }
|
||||
}
|
||||
|
||||
|
||||
private readonly IOrderedEnumerable<BetRollPair> _thresholdPairs;
|
||||
private readonly Random _rng;
|
||||
|
||||
|
||||
public Betroll(BetRollConfig settings)
|
||||
{
|
||||
_thresholdPairs = settings.Pairs.OrderByDescending(x => x.WhenAbove);
|
||||
@@ -26,19 +18,15 @@ public class Betroll
|
||||
|
||||
var pair = _thresholdPairs.FirstOrDefault(x => x.WhenAbove < roll);
|
||||
if (pair is null)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Multiplier = 0,
|
||||
Roll = roll,
|
||||
};
|
||||
}
|
||||
return new() { Multiplier = 0, Roll = roll };
|
||||
|
||||
return new()
|
||||
{
|
||||
Multiplier = pair.MultiplyBy,
|
||||
Roll = roll,
|
||||
Threshold = pair.WhenAbove,
|
||||
};
|
||||
return new() { Multiplier = pair.MultiplyBy, Roll = roll, Threshold = pair.WhenAbove };
|
||||
}
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
public int Roll { get; set; }
|
||||
public float Multiplier { get; set; }
|
||||
public int Threshold { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,13 @@ public class Blackjack
|
||||
Ended
|
||||
}
|
||||
|
||||
private Deck Deck { get; set; } = new QuadDeck();
|
||||
public event Func<Blackjack, Task> StateUpdated;
|
||||
public event Func<Blackjack, Task> GameEnded;
|
||||
|
||||
private Deck Deck { get; } = new QuadDeck();
|
||||
public Dealer Dealer { get; set; }
|
||||
|
||||
|
||||
|
||||
public List<User> Players { get; set; } = new();
|
||||
public GameState State { get; set; } = GameState.Starting;
|
||||
public User CurrentUser { get; private set; }
|
||||
@@ -22,9 +25,6 @@ public class Blackjack
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
|
||||
public event Func<Blackjack, Task> StateUpdated;
|
||||
public event Func<Blackjack, Task> GameEnded;
|
||||
|
||||
private readonly SemaphoreSlim locker = new(1, 1);
|
||||
|
||||
public Blackjack(ICurrencyService cs, DbService db)
|
||||
@@ -54,6 +54,7 @@ public class Blackjack
|
||||
{
|
||||
locker.Release();
|
||||
}
|
||||
|
||||
await PrintState();
|
||||
//if no users joined the game, end it
|
||||
if (!Players.Any())
|
||||
@@ -62,6 +63,7 @@ public class Blackjack
|
||||
var end = GameEnded?.Invoke(this);
|
||||
return;
|
||||
}
|
||||
|
||||
//give 1 card to the dealer and 2 to each player
|
||||
Dealer.Cards.Add(Deck.Draw());
|
||||
foreach (var usr in Players)
|
||||
@@ -72,15 +74,15 @@ public class Blackjack
|
||||
if (usr.GetHandValue() == 21)
|
||||
usr.State = User.UserState.Blackjack;
|
||||
}
|
||||
|
||||
//go through all users and ask them what they want to do
|
||||
foreach (var usr in Players.Where(x => !x.Done))
|
||||
{
|
||||
while (!usr.Done)
|
||||
{
|
||||
Log.Information($"Waiting for {usr.DiscordUser}'s move");
|
||||
await PromptUserMove(usr);
|
||||
}
|
||||
}
|
||||
|
||||
await PrintState();
|
||||
State = GameState.Ended;
|
||||
await Task.Delay(2500);
|
||||
@@ -106,10 +108,7 @@ public class Blackjack
|
||||
// either wait for the user to make an action and
|
||||
// if he doesn't - stand
|
||||
var finished = await Task.WhenAny(pause, _currentUserMove.Task);
|
||||
if (finished == pause)
|
||||
{
|
||||
await Stand(usr);
|
||||
}
|
||||
if (finished == pause) await Stand(usr);
|
||||
CurrentUser = null;
|
||||
_currentUserMove = null;
|
||||
}
|
||||
@@ -125,10 +124,7 @@ public class Blackjack
|
||||
if (Players.Count >= 5)
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true)) return false;
|
||||
|
||||
Players.Add(new(user, bet));
|
||||
var _ = PrintState();
|
||||
@@ -146,7 +142,7 @@ public class Blackjack
|
||||
|
||||
if (cu != null && cu.DiscordUser == u)
|
||||
return await Stand(cu);
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -175,7 +171,8 @@ public class Blackjack
|
||||
{
|
||||
var hw = Dealer.GetHandValue();
|
||||
while (hw < 17
|
||||
|| (hw == 17 && Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10))// hit on soft 17
|
||||
|| (hw == 17
|
||||
&& Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10)) // hit on soft 17
|
||||
{
|
||||
/* Dealer has
|
||||
A 6
|
||||
@@ -201,37 +198,23 @@ public class Blackjack
|
||||
}
|
||||
|
||||
if (hw > 21)
|
||||
{
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
if (usr.State is User.UserState.Stand or User.UserState.Blackjack)
|
||||
usr.State = User.UserState.Won;
|
||||
else
|
||||
usr.State = User.UserState.Lost;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
if (usr.State == User.UserState.Blackjack)
|
||||
usr.State = User.UserState.Won;
|
||||
else if (usr.State == User.UserState.Stand)
|
||||
usr.State = hw < usr.GetHandValue()
|
||||
? User.UserState.Won
|
||||
: User.UserState.Lost;
|
||||
usr.State = hw < usr.GetHandValue() ? User.UserState.Won : User.UserState.Lost;
|
||||
else
|
||||
usr.State = User.UserState.Lost;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
if (usr.State is User.UserState.Won or User.UserState.Blackjack)
|
||||
{
|
||||
await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, gamble: true);
|
||||
}
|
||||
}
|
||||
await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, true);
|
||||
}
|
||||
|
||||
public async Task<bool> Double(IUser u)
|
||||
@@ -240,7 +223,7 @@ public class Blackjack
|
||||
|
||||
if (cu != null && cu.DiscordUser == u)
|
||||
return await Double(cu);
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -263,20 +246,14 @@ public class Blackjack
|
||||
u.Cards.Add(Deck.Draw());
|
||||
|
||||
if (u.GetHandValue() == 21)
|
||||
{
|
||||
//blackjack
|
||||
u.State = User.UserState.Blackjack;
|
||||
}
|
||||
else if (u.GetHandValue() > 21)
|
||||
{
|
||||
// user busted
|
||||
u.State = User.UserState.Bust;
|
||||
}
|
||||
else
|
||||
{
|
||||
//with double you just get one card, and then you're done
|
||||
u.State = User.UserState.Stand;
|
||||
}
|
||||
_currentUserMove.TrySetResult(true);
|
||||
|
||||
return true;
|
||||
@@ -311,19 +288,12 @@ public class Blackjack
|
||||
u.Cards.Add(Deck.Draw());
|
||||
|
||||
if (u.GetHandValue() == 21)
|
||||
{
|
||||
//blackjack
|
||||
u.State = User.UserState.Blackjack;
|
||||
}
|
||||
else if (u.GetHandValue() > 21)
|
||||
{
|
||||
// user busted
|
||||
u.State = User.UserState.Bust;
|
||||
}
|
||||
else
|
||||
{
|
||||
//you can hit or stand again
|
||||
}
|
||||
|
||||
_currentUserMove.TrySetResult(true);
|
||||
|
||||
return true;
|
||||
@@ -340,4 +310,4 @@ public class Blackjack
|
||||
return Task.CompletedTask;
|
||||
return StateUpdated.Invoke(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,7 @@ public abstract class Player
|
||||
// reduce the value by 10 until it drops below 22
|
||||
// (emulating the fact that ace is either a 1 or a 11)
|
||||
var i = Cards.Count(x => x.Number == 1);
|
||||
while (val > 21 && i-- > 0)
|
||||
{
|
||||
val -= 10;
|
||||
}
|
||||
while (val > 21 && i-- > 0) val -= 10;
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -26,7 +23,6 @@ public abstract class Player
|
||||
|
||||
public class Dealer : Player
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class User : Player
|
||||
@@ -41,17 +37,19 @@ public class User : Player
|
||||
Lost
|
||||
}
|
||||
|
||||
public UserState State { get; set; } = UserState.Waiting;
|
||||
public long Bet { get; set; }
|
||||
public IUser DiscordUser { get; }
|
||||
|
||||
public bool Done
|
||||
=> State != UserState.Waiting;
|
||||
|
||||
public User(IUser user, long bet)
|
||||
{
|
||||
if (bet <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bet));
|
||||
|
||||
this.Bet = bet;
|
||||
this.DiscordUser = user;
|
||||
Bet = bet;
|
||||
DiscordUser = user;
|
||||
}
|
||||
|
||||
public UserState State { get; set; } = UserState.Waiting;
|
||||
public long Bet { get; set; }
|
||||
public IUser DiscordUser { get; }
|
||||
public bool Done => State != UserState.Waiting;
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,18 @@ namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class CurrencyRaffleGame
|
||||
{
|
||||
public enum Type {
|
||||
public enum Type
|
||||
{
|
||||
Mixed,
|
||||
Normal
|
||||
}
|
||||
|
||||
public class User
|
||||
{
|
||||
public IUser DiscordUser { get; set; }
|
||||
public long Amount { get; set; }
|
||||
public IEnumerable<User> Users
|
||||
=> _users;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> DiscordUser.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is User u
|
||||
? u.DiscordUser == DiscordUser
|
||||
: false;
|
||||
}
|
||||
public Type GameType { get; }
|
||||
|
||||
private readonly HashSet<User> _users = new();
|
||||
public IEnumerable<User> Users => _users;
|
||||
public Type GameType { get; }
|
||||
|
||||
public CurrencyRaffleGame(Type type)
|
||||
=> GameType = type;
|
||||
@@ -33,19 +23,12 @@ public class CurrencyRaffleGame
|
||||
{
|
||||
// if game type is normal, and someone already joined the game
|
||||
// (that's the user who created it)
|
||||
if (GameType == Type.Normal && _users.Count > 0 &&
|
||||
_users.First().Amount != amount)
|
||||
if (GameType == Type.Normal && _users.Count > 0 && _users.First().Amount != amount)
|
||||
return false;
|
||||
|
||||
if (!_users.Add(new()
|
||||
{
|
||||
DiscordUser = usr,
|
||||
Amount = amount,
|
||||
}))
|
||||
{
|
||||
if (!_users.Add(new() { DiscordUser = usr, Amount = amount }))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -67,4 +50,16 @@ public class CurrencyRaffleGame
|
||||
var usrs = _users.ToArray();
|
||||
return usrs[rng.Next(0, usrs.Length)];
|
||||
}
|
||||
}
|
||||
|
||||
public class User
|
||||
{
|
||||
public IUser DiscordUser { get; set; }
|
||||
public long Amount { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
=> DiscordUser.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is User u ? u.DiscordUser == DiscordUser : false;
|
||||
}
|
||||
}
|
||||
@@ -7,21 +7,28 @@ public class QuadDeck : Deck
|
||||
{
|
||||
CardPool = new(52 * 4);
|
||||
for (var j = 1; j < 14; j++)
|
||||
for (var i = 1; i < 5; i++)
|
||||
{
|
||||
for (var i = 1; i < 5; i++)
|
||||
{
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
}
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Deck
|
||||
{
|
||||
private static readonly Dictionary<int, string> _cardNames = new() {
|
||||
public enum CardSuit
|
||||
{
|
||||
Spades = 1,
|
||||
Hearts = 2,
|
||||
Diamonds = 3,
|
||||
Clubs = 4
|
||||
}
|
||||
|
||||
private static readonly Dictionary<int, string> _cardNames = new()
|
||||
{
|
||||
{ 1, "Ace" },
|
||||
{ 2, "Two" },
|
||||
{ 3, "Three" },
|
||||
@@ -36,171 +43,50 @@ public class Deck
|
||||
{ 12, "Queen" },
|
||||
{ 13, "King" }
|
||||
};
|
||||
|
||||
private static Dictionary<string, Func<List<Card>, bool>> _handValues;
|
||||
|
||||
|
||||
public enum CardSuit
|
||||
{
|
||||
Spades = 1,
|
||||
Hearts = 2,
|
||||
Diamonds = 3,
|
||||
Clubs = 4
|
||||
}
|
||||
|
||||
public class Card : IComparable
|
||||
{
|
||||
public CardSuit Suit { get; }
|
||||
public int Number { get; }
|
||||
|
||||
public string FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
var str = string.Empty;
|
||||
|
||||
if (Number is <= 10 and > 1)
|
||||
{
|
||||
str += "_" + Number;
|
||||
}
|
||||
else
|
||||
{
|
||||
str += GetValueText().ToLowerInvariant();
|
||||
}
|
||||
return str + "_of_" + Suit.ToString().ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
public Card(CardSuit s, int cardNum)
|
||||
{
|
||||
this.Suit = s;
|
||||
this.Number = cardNum;
|
||||
}
|
||||
|
||||
public string GetValueText() => _cardNames[Number];
|
||||
|
||||
public override string ToString() => _cardNames[Number] + " Of " + Suit;
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is not Card card) return 0;
|
||||
return this.Number - card.Number;
|
||||
}
|
||||
|
||||
public static Card Parse(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
if (input.Length != 2
|
||||
|| !_numberCharToNumber.TryGetValue(input[0], out var n)
|
||||
|| !_suitCharToSuit.TryGetValue(input[1].ToString(), out var s))
|
||||
{
|
||||
throw new ArgumentException("Invalid input", nameof(input));
|
||||
}
|
||||
|
||||
return new(s, n);
|
||||
}
|
||||
|
||||
public string GetEmojiString()
|
||||
{
|
||||
var str = string.Empty;
|
||||
|
||||
str += _regIndicators[this.Number - 1];
|
||||
str += _suitToSuitChar[this.Suit];
|
||||
|
||||
return str;
|
||||
}
|
||||
private readonly string[] _regIndicators = new[]
|
||||
{
|
||||
"🇦",
|
||||
":two:",
|
||||
":three:",
|
||||
":four:",
|
||||
":five:",
|
||||
":six:",
|
||||
":seven:",
|
||||
":eight:",
|
||||
":nine:",
|
||||
":keycap_ten:",
|
||||
"🇯",
|
||||
"🇶",
|
||||
"🇰"
|
||||
};
|
||||
private static readonly IReadOnlyDictionary<CardSuit, string> _suitToSuitChar = new Dictionary<CardSuit, string>
|
||||
{
|
||||
{CardSuit.Diamonds, "♦"},
|
||||
{CardSuit.Clubs, "♣"},
|
||||
{CardSuit.Spades, "♠"},
|
||||
{CardSuit.Hearts, "♥"},
|
||||
};
|
||||
private static readonly IReadOnlyDictionary<string, CardSuit> _suitCharToSuit = new Dictionary<string, CardSuit>
|
||||
{
|
||||
{"♦", CardSuit.Diamonds },
|
||||
{"d", CardSuit.Diamonds },
|
||||
{"♣", CardSuit.Clubs },
|
||||
{"c", CardSuit.Clubs },
|
||||
{"♠", CardSuit.Spades },
|
||||
{"s", CardSuit.Spades },
|
||||
{"♥", CardSuit.Hearts },
|
||||
{"h", CardSuit.Hearts },
|
||||
};
|
||||
private static readonly IReadOnlyDictionary<char, int> _numberCharToNumber = new Dictionary<char, int>()
|
||||
{
|
||||
{'a', 1 },
|
||||
{'2', 2 },
|
||||
{'3', 3 },
|
||||
{'4', 4 },
|
||||
{'5', 5 },
|
||||
{'6', 6 },
|
||||
{'7', 7 },
|
||||
{'8', 8 },
|
||||
{'9', 9 },
|
||||
{'t', 10 },
|
||||
{'j', 11 },
|
||||
{'q', 12 },
|
||||
{'k', 13 },
|
||||
};
|
||||
}
|
||||
|
||||
public List<Card> CardPool { get; set; }
|
||||
/// <summary>
|
||||
/// Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time.
|
||||
/// </summary>
|
||||
public Deck()
|
||||
=> RefillPool();
|
||||
private readonly Random _r = new NadekoRandom();
|
||||
|
||||
static Deck()
|
||||
=> InitHandValues();
|
||||
|
||||
/// <summary>
|
||||
/// Restart the game of blackjack. It will only refill the pool for now. Probably wont be used, unless you want to have only 1 bjg running at one time,
|
||||
/// then you will restart the same game every time.
|
||||
/// Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time.
|
||||
/// </summary>
|
||||
public void Restart() => RefillPool();
|
||||
public Deck()
|
||||
=> RefillPool();
|
||||
|
||||
/// <summary>
|
||||
/// Removes all cards from the pool and refills the pool with all of the possible cards. NOTE: I think this is too expensive.
|
||||
/// We should probably make it so it copies another premade list with all the cards, or something.
|
||||
/// Restart the game of blackjack. It will only refill the pool for now. Probably wont be used, unless you want to have
|
||||
/// only 1 bjg running at one time,
|
||||
/// then you will restart the same game every time.
|
||||
/// </summary>
|
||||
public void Restart()
|
||||
=> RefillPool();
|
||||
|
||||
/// <summary>
|
||||
/// Removes all cards from the pool and refills the pool with all of the possible cards. NOTE: I think this is too
|
||||
/// expensive.
|
||||
/// We should probably make it so it copies another premade list with all the cards, or something.
|
||||
/// </summary>
|
||||
protected virtual void RefillPool()
|
||||
{
|
||||
CardPool = new(52);
|
||||
//foreach suit
|
||||
for (var j = 1; j < 14; j++)
|
||||
{
|
||||
// and number
|
||||
for (var i = 1; i < 5; i++)
|
||||
{
|
||||
//generate a card of that suit and number and add it to the pool
|
||||
for (var i = 1; i < 5; i++)
|
||||
//generate a card of that suit and number and add it to the pool
|
||||
|
||||
// the pool will go from ace of spades,hears,diamonds,clubs all the way to the king of spades. hearts, ...
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
}
|
||||
}
|
||||
// the pool will go from ace of spades,hears,diamonds,clubs all the way to the king of spades. hearts, ...
|
||||
CardPool.Add(new((CardSuit)i, j));
|
||||
}
|
||||
private readonly Random _r = new NadekoRandom();
|
||||
|
||||
/// <summary>
|
||||
/// Take a card from the pool, you either take it from the top if the deck is shuffled, or from a random place if the deck is in the default order.
|
||||
/// Take a card from the pool, you either take it from the top if the deck is shuffled, or from a random place if the
|
||||
/// deck is in the default order.
|
||||
/// </summary>
|
||||
/// <returns>A card from the pool</returns>
|
||||
public Card Draw()
|
||||
@@ -221,8 +107,10 @@ public class Deck
|
||||
return c;
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuffles the deck. Use this if you want to take cards from the top of the deck, instead of randomly. See DrawACard method.
|
||||
/// Shuffles the deck. Use this if you want to take cards from the top of the deck, instead of randomly. See DrawACard
|
||||
/// method.
|
||||
/// </summary>
|
||||
private void Shuffle()
|
||||
{
|
||||
@@ -230,51 +118,79 @@ public class Deck
|
||||
var orderedPool = CardPool.Shuffle();
|
||||
CardPool ??= orderedPool.ToList();
|
||||
}
|
||||
public override string ToString() => string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine;
|
||||
|
||||
public override string ToString()
|
||||
=> string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine;
|
||||
|
||||
private static void InitHandValues()
|
||||
{
|
||||
bool HasPair(List<Card> cards) => cards.GroupBy(card => card.Number)
|
||||
.Count(group => group.Count() == 2) == 1;
|
||||
bool IsPair(List<Card> cards) => cards.GroupBy(card => card.Number)
|
||||
.Count(group => group.Count() == 3) == 0
|
||||
&& HasPair(cards);
|
||||
bool HasPair(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 1;
|
||||
}
|
||||
|
||||
bool IsTwoPair(List<Card> cards) => cards.GroupBy(card => card.Number)
|
||||
.Count(group => group.Count() == 2) == 2;
|
||||
bool IsPair(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Number).Count(group => group.Count() == 3) == 0 && HasPair(cards);
|
||||
}
|
||||
|
||||
bool IsTwoPair(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 2;
|
||||
}
|
||||
|
||||
bool IsStraight(List<Card> cards)
|
||||
{
|
||||
if (cards.GroupBy(card => card.Number).Count() != cards.Count())
|
||||
return false;
|
||||
var toReturn = cards.Max(card => card.Number)
|
||||
- cards.Min(card => card.Number) == 4;
|
||||
var toReturn = cards.Max(card => card.Number) - cards.Min(card => card.Number) == 4;
|
||||
if (toReturn || cards.All(c => c.Number != 1)) return toReturn;
|
||||
|
||||
var newCards = cards.Select(c => c.Number == 1 ? new(c.Suit, 14) : c);
|
||||
return newCards.Max(card => card.Number)
|
||||
- newCards.Min(card => card.Number) == 4;
|
||||
return newCards.Max(card => card.Number) - newCards.Min(card => card.Number) == 4;
|
||||
}
|
||||
|
||||
bool HasThreeOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
|
||||
.Any(group => group.Count() == 3);
|
||||
bool HasThreeOfKind(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Number).Any(group => group.Count() == 3);
|
||||
}
|
||||
|
||||
bool IsThreeOfKind(List<Card> cards) => HasThreeOfKind(cards) && !HasPair(cards);
|
||||
bool IsThreeOfKind(List<Card> cards)
|
||||
{
|
||||
return HasThreeOfKind(cards) && !HasPair(cards);
|
||||
}
|
||||
|
||||
bool IsFlush(List<Card> cards) => cards.GroupBy(card => card.Suit).Count() == 1;
|
||||
bool IsFlush(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Suit).Count() == 1;
|
||||
}
|
||||
|
||||
bool IsFourOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
|
||||
.Any(group => group.Count() == 4);
|
||||
bool IsFourOfKind(List<Card> cards)
|
||||
{
|
||||
return cards.GroupBy(card => card.Number).Any(group => group.Count() == 4);
|
||||
}
|
||||
|
||||
bool IsFullHouse(List<Card> cards) => HasPair(cards) && HasThreeOfKind(cards);
|
||||
bool IsFullHouse(List<Card> cards)
|
||||
{
|
||||
return HasPair(cards) && HasThreeOfKind(cards);
|
||||
}
|
||||
|
||||
bool HasStraightFlush(List<Card> cards) => IsFlush(cards) && IsStraight(cards);
|
||||
bool HasStraightFlush(List<Card> cards)
|
||||
{
|
||||
return IsFlush(cards) && IsStraight(cards);
|
||||
}
|
||||
|
||||
bool IsRoyalFlush(List<Card> cards) => cards.Min(card => card.Number) == 1 &&
|
||||
cards.Max(card => card.Number) == 13
|
||||
&& HasStraightFlush(cards);
|
||||
bool IsRoyalFlush(List<Card> cards)
|
||||
{
|
||||
return cards.Min(card => card.Number) == 1
|
||||
&& cards.Max(card => card.Number) == 13
|
||||
&& HasStraightFlush(cards);
|
||||
}
|
||||
|
||||
bool IsStraightFlush(List<Card> cards) => HasStraightFlush(cards) && !IsRoyalFlush(cards);
|
||||
bool IsStraightFlush(List<Card> cards)
|
||||
{
|
||||
return HasStraightFlush(cards) && !IsRoyalFlush(cards);
|
||||
}
|
||||
|
||||
_handValues = new()
|
||||
{
|
||||
@@ -294,10 +210,108 @@ public class Deck
|
||||
{
|
||||
if (_handValues is null)
|
||||
InitHandValues();
|
||||
foreach (var kvp in _handValues.Where(x => x.Value(cards)))
|
||||
{
|
||||
return kvp.Key;
|
||||
}
|
||||
foreach (var kvp in _handValues.Where(x => x.Value(cards))) return kvp.Key;
|
||||
return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetValueText() ?? cards.Max().GetValueText());
|
||||
}
|
||||
}
|
||||
|
||||
public class Card : IComparable
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<CardSuit, string> _suitToSuitChar = new Dictionary<CardSuit, string>
|
||||
{
|
||||
{ CardSuit.Diamonds, "♦" }, { CardSuit.Clubs, "♣" }, { CardSuit.Spades, "♠" }, { CardSuit.Hearts, "♥" }
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyDictionary<string, CardSuit> _suitCharToSuit = new Dictionary<string, CardSuit>
|
||||
{
|
||||
{ "♦", CardSuit.Diamonds },
|
||||
{ "d", CardSuit.Diamonds },
|
||||
{ "♣", CardSuit.Clubs },
|
||||
{ "c", CardSuit.Clubs },
|
||||
{ "♠", CardSuit.Spades },
|
||||
{ "s", CardSuit.Spades },
|
||||
{ "♥", CardSuit.Hearts },
|
||||
{ "h", CardSuit.Hearts }
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyDictionary<char, int> _numberCharToNumber = new Dictionary<char, int>
|
||||
{
|
||||
{ 'a', 1 },
|
||||
{ '2', 2 },
|
||||
{ '3', 3 },
|
||||
{ '4', 4 },
|
||||
{ '5', 5 },
|
||||
{ '6', 6 },
|
||||
{ '7', 7 },
|
||||
{ '8', 8 },
|
||||
{ '9', 9 },
|
||||
{ 't', 10 },
|
||||
{ 'j', 11 },
|
||||
{ 'q', 12 },
|
||||
{ 'k', 13 }
|
||||
};
|
||||
|
||||
public CardSuit Suit { get; }
|
||||
public int Number { get; }
|
||||
|
||||
public string FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
var str = string.Empty;
|
||||
|
||||
if (Number is <= 10 and > 1)
|
||||
str += "_" + Number;
|
||||
else
|
||||
str += GetValueText().ToLowerInvariant();
|
||||
return str + "_of_" + Suit.ToString().ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly string[] _regIndicators =
|
||||
{
|
||||
"🇦", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:", ":keycap_ten:",
|
||||
"🇯", "🇶", "🇰"
|
||||
};
|
||||
|
||||
public Card(CardSuit s, int cardNum)
|
||||
{
|
||||
Suit = s;
|
||||
Number = cardNum;
|
||||
}
|
||||
|
||||
public string GetValueText()
|
||||
=> _cardNames[Number];
|
||||
|
||||
public override string ToString()
|
||||
=> _cardNames[Number] + " Of " + Suit;
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is not Card card) return 0;
|
||||
return Number - card.Number;
|
||||
}
|
||||
|
||||
public static Card Parse(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
if (input.Length != 2
|
||||
|| !_numberCharToNumber.TryGetValue(input[0], out var n)
|
||||
|| !_suitCharToSuit.TryGetValue(input[1].ToString(), out var s))
|
||||
throw new ArgumentException("Invalid input", nameof(input));
|
||||
|
||||
return new(s, n);
|
||||
}
|
||||
|
||||
public string GetEmojiString()
|
||||
{
|
||||
var str = string.Empty;
|
||||
|
||||
str += _regIndicators[Number - 1];
|
||||
str += _suitToSuitChar[Suit];
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,21 @@ public class EventOptions : INadekoCommandOptions
|
||||
{
|
||||
[Option('a', "amount", Required = false, Default = 100, HelpText = "Amount of currency each user receives.")]
|
||||
public long Amount { get; set; } = 100;
|
||||
[Option('p', "pot-size", Required = false, Default = 0, HelpText = "The maximum amount of currency that can be rewarded. 0 means no limit.")]
|
||||
public long PotSize { get; set; } = 0;
|
||||
|
||||
[Option('p',
|
||||
"pot-size",
|
||||
Required = false,
|
||||
Default = 0,
|
||||
HelpText = "The maximum amount of currency that can be rewarded. 0 means no limit.")]
|
||||
public long PotSize { get; set; }
|
||||
|
||||
//[Option('t', "type", Required = false, Default = "reaction", HelpText = "Type of the event. reaction, gamestatus or joinserver.")]
|
||||
//public string TypeString { get; set; } = "reaction";
|
||||
[Option('d', "duration", Required = false, Default = 24, HelpText = "Number of hours the event should run for. Default 24.")]
|
||||
[Option('d',
|
||||
"duration",
|
||||
Required = false,
|
||||
Default = 24,
|
||||
HelpText = "Number of hours the event should run for. Default 24.")]
|
||||
public int Hours { get; set; } = 24;
|
||||
|
||||
|
||||
@@ -26,4 +36,4 @@ public class EventOptions : INadekoCommandOptions
|
||||
if (PotSize != 0 && PotSize < Amount)
|
||||
PotSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,37 +5,44 @@ namespace NadekoBot.Modules.Gambling.Common.Events;
|
||||
|
||||
public class GameStatusEvent : ICurrencyEvent
|
||||
{
|
||||
public event Func<ulong, Task> OnEnded;
|
||||
private long PotSize { get; set; }
|
||||
public bool Stopped { get; private set; }
|
||||
public bool PotEmptied { get; private set; }
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IGuild _guild;
|
||||
private IUserMessage _msg;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly long _amount;
|
||||
|
||||
private long PotSize { get; set; }
|
||||
public bool Stopped { get; private set; }
|
||||
public bool PotEmptied { get; private set; } = false;
|
||||
|
||||
private readonly Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> _embedFunc;
|
||||
private readonly bool _isPotLimited;
|
||||
private readonly ITextChannel _channel;
|
||||
private readonly ConcurrentHashSet<ulong> _awardedUsers = new();
|
||||
private readonly ConcurrentQueue<ulong> _toAward = new();
|
||||
private readonly Timer _t;
|
||||
private readonly Timer _timeout = null;
|
||||
private readonly Timer _timeout;
|
||||
private readonly EventOptions _opts;
|
||||
|
||||
private readonly string _code;
|
||||
|
||||
public event Func<ulong, Task> OnEnded;
|
||||
|
||||
private readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10)
|
||||
.Concat(Enumerable.Range(65, 26))
|
||||
.Concat(Enumerable.Range(97, 26))
|
||||
.Select(x => (char)x)
|
||||
.ToArray();
|
||||
.Concat(Enumerable.Range(65, 26))
|
||||
.Concat(Enumerable.Range(97, 26))
|
||||
.Select(x => (char)x)
|
||||
.ToArray();
|
||||
|
||||
public GameStatusEvent(DiscordSocketClient client, ICurrencyService cs,SocketGuild g, ITextChannel ch,
|
||||
EventOptions opt, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
|
||||
private readonly object stopLock = new();
|
||||
|
||||
private readonly object potLock = new();
|
||||
|
||||
public GameStatusEvent(
|
||||
DiscordSocketClient client,
|
||||
ICurrencyService cs,
|
||||
SocketGuild g,
|
||||
ITextChannel ch,
|
||||
EventOptions opt,
|
||||
Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
|
||||
{
|
||||
_client = client;
|
||||
_guild = g;
|
||||
@@ -51,9 +58,7 @@ public class GameStatusEvent : ICurrencyEvent
|
||||
|
||||
_t = new(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
|
||||
if (_opts.Hours > 0)
|
||||
{
|
||||
_timeout = new(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private void EventTimeout(object state)
|
||||
@@ -65,10 +70,7 @@ public class GameStatusEvent : ICurrencyEvent
|
||||
{
|
||||
var potEmpty = PotEmptied;
|
||||
var toAward = new List<ulong>();
|
||||
while (_toAward.TryDequeue(out var x))
|
||||
{
|
||||
toAward.Add(x);
|
||||
}
|
||||
while (_toAward.TryDequeue(out var x)) toAward.Add(x);
|
||||
|
||||
if (!toAward.Any())
|
||||
return;
|
||||
@@ -78,15 +80,14 @@ public class GameStatusEvent : ICurrencyEvent
|
||||
await _cs.AddBulkAsync(toAward,
|
||||
toAward.Select(x => "GameStatus Event"),
|
||||
toAward.Select(x => _amount),
|
||||
gamble: true);
|
||||
true);
|
||||
|
||||
if (_isPotLimited)
|
||||
{
|
||||
await _msg.ModifyAsync(m =>
|
||||
{
|
||||
m.Embed = GetEmbed(PotSize).Build();
|
||||
}, new() { RetryMode = RetryMode.AlwaysRetry });
|
||||
}
|
||||
{
|
||||
m.Embed = GetEmbed(PotSize).Build();
|
||||
},
|
||||
new() { RetryMode = RetryMode.AlwaysRetry });
|
||||
|
||||
Log.Information("Awarded {0} users {1} currency.{2}",
|
||||
toAward.Count,
|
||||
@@ -97,7 +98,6 @@ public class GameStatusEvent : ICurrencyEvent
|
||||
{
|
||||
var _ = StopEvent();
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -119,13 +119,9 @@ public class GameStatusEvent : ICurrencyEvent
|
||||
|
||||
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, Cacheable<IMessageChannel, ulong> cacheable)
|
||||
{
|
||||
if (msg.Id == _msg.Id)
|
||||
{
|
||||
await StopEvent();
|
||||
}
|
||||
if (msg.Id == _msg.Id) await StopEvent();
|
||||
}
|
||||
|
||||
private readonly object stopLock = new();
|
||||
public async Task StopEvent()
|
||||
{
|
||||
await Task.Yield();
|
||||
@@ -139,7 +135,12 @@ public class GameStatusEvent : ICurrencyEvent
|
||||
_client.SetGameAsync(null);
|
||||
_t.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
try { var _ = _msg.DeleteAsync(); } catch { }
|
||||
try
|
||||
{
|
||||
var _ = _msg.DeleteAsync();
|
||||
}
|
||||
catch { }
|
||||
|
||||
var os = OnEnded(_guild.Id);
|
||||
}
|
||||
}
|
||||
@@ -152,9 +153,7 @@ public class GameStatusEvent : ICurrencyEvent
|
||||
|| gu.IsBot // no bots
|
||||
|| msg.Content != _code // code has to be the same
|
||||
|| (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5) // no recently created accounts
|
||||
{
|
||||
return;
|
||||
}
|
||||
// there has to be money left in the pot
|
||||
// and the user wasn't rewarded
|
||||
if (_awardedUsers.Add(msg.Author.Id) && TryTakeFromPot())
|
||||
@@ -166,21 +165,16 @@ public class GameStatusEvent : ICurrencyEvent
|
||||
|
||||
try
|
||||
{
|
||||
await msg.DeleteAsync(new()
|
||||
{
|
||||
RetryMode = RetryMode.AlwaysFail
|
||||
});
|
||||
await msg.DeleteAsync(new() { RetryMode = RetryMode.AlwaysFail });
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private readonly object potLock = new();
|
||||
private bool TryTakeFromPot()
|
||||
{
|
||||
if (_isPotLimited)
|
||||
{
|
||||
lock (potLock)
|
||||
{
|
||||
if (PotSize < _amount)
|
||||
@@ -189,7 +183,7 @@ public class GameStatusEvent : ICurrencyEvent
|
||||
PotSize -= _amount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,4 @@ public interface ICurrencyEvent
|
||||
event Func<ulong, Task> OnEnded;
|
||||
Task StopEvent();
|
||||
Task StartEvent();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@ namespace NadekoBot.Modules.Gambling.Common.Events;
|
||||
|
||||
public class ReactionEvent : ICurrencyEvent
|
||||
{
|
||||
public event Func<ulong, Task> OnEnded;
|
||||
private long PotSize { get; set; }
|
||||
public bool Stopped { get; private set; }
|
||||
public bool PotEmptied { get; private set; }
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IGuild _guild;
|
||||
private IUserMessage _msg;
|
||||
@@ -12,25 +16,28 @@ public class ReactionEvent : ICurrencyEvent
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly long _amount;
|
||||
|
||||
private long PotSize { get; set; }
|
||||
public bool Stopped { get; private set; }
|
||||
public bool PotEmptied { get; private set; } = false;
|
||||
|
||||
private readonly Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> _embedFunc;
|
||||
private readonly bool _isPotLimited;
|
||||
private readonly ITextChannel _channel;
|
||||
private readonly ConcurrentHashSet<ulong> _awardedUsers = new();
|
||||
private readonly ConcurrentQueue<ulong> _toAward = new();
|
||||
private readonly Timer _t;
|
||||
private readonly Timer _timeout = null;
|
||||
private readonly Timer _timeout;
|
||||
private readonly bool _noRecentlyJoinedServer;
|
||||
private readonly EventOptions _opts;
|
||||
private readonly GamblingConfig _config;
|
||||
|
||||
public event Func<ulong, Task> OnEnded;
|
||||
private readonly object stopLock = new();
|
||||
|
||||
public ReactionEvent(DiscordSocketClient client, ICurrencyService cs,
|
||||
SocketGuild g, ITextChannel ch, EventOptions opt, GamblingConfig config,
|
||||
private readonly object potLock = new();
|
||||
|
||||
public ReactionEvent(
|
||||
DiscordSocketClient client,
|
||||
ICurrencyService cs,
|
||||
SocketGuild g,
|
||||
ITextChannel ch,
|
||||
EventOptions opt,
|
||||
GamblingConfig config,
|
||||
Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
|
||||
{
|
||||
_client = client;
|
||||
@@ -47,9 +54,7 @@ public class ReactionEvent : ICurrencyEvent
|
||||
|
||||
_t = new(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
|
||||
if (_opts.Hours > 0)
|
||||
{
|
||||
_timeout = new(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private void EventTimeout(object state)
|
||||
@@ -61,28 +66,21 @@ public class ReactionEvent : ICurrencyEvent
|
||||
{
|
||||
var potEmpty = PotEmptied;
|
||||
var toAward = new List<ulong>();
|
||||
while (_toAward.TryDequeue(out var x))
|
||||
{
|
||||
toAward.Add(x);
|
||||
}
|
||||
while (_toAward.TryDequeue(out var x)) toAward.Add(x);
|
||||
|
||||
if (!toAward.Any())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
await _cs.AddBulkAsync(toAward,
|
||||
toAward.Select(x => "Reaction Event"),
|
||||
toAward.Select(x => _amount),
|
||||
gamble: true);
|
||||
await _cs.AddBulkAsync(toAward, toAward.Select(x => "Reaction Event"), toAward.Select(x => _amount), true);
|
||||
|
||||
if (_isPotLimited)
|
||||
{
|
||||
await _msg.ModifyAsync(m =>
|
||||
{
|
||||
m.Embed = GetEmbed(PotSize).Build();
|
||||
}, new() { RetryMode = RetryMode.AlwaysRetry });
|
||||
}
|
||||
{
|
||||
m.Embed = GetEmbed(PotSize).Build();
|
||||
},
|
||||
new() { RetryMode = RetryMode.AlwaysRetry });
|
||||
|
||||
Log.Information("Awarded {0} users {1} currency.{2}",
|
||||
toAward.Count,
|
||||
@@ -103,13 +101,9 @@ public class ReactionEvent : ICurrencyEvent
|
||||
public async Task StartEvent()
|
||||
{
|
||||
if (Emote.TryParse(_config.Currency.Sign, out var emote))
|
||||
{
|
||||
_emote = emote;
|
||||
}
|
||||
else
|
||||
{
|
||||
_emote = new Emoji(_config.Currency.Sign);
|
||||
}
|
||||
_msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize));
|
||||
await _msg.AddReactionAsync(_emote);
|
||||
_client.MessageDeleted += OnMessageDeleted;
|
||||
@@ -122,13 +116,9 @@ public class ReactionEvent : ICurrencyEvent
|
||||
|
||||
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, Cacheable<IMessageChannel, ulong> cacheable)
|
||||
{
|
||||
if (msg.Id == _msg.Id)
|
||||
{
|
||||
await StopEvent();
|
||||
}
|
||||
if (msg.Id == _msg.Id) await StopEvent();
|
||||
}
|
||||
|
||||
private readonly object stopLock = new();
|
||||
public async Task StopEvent()
|
||||
{
|
||||
await Task.Yield();
|
||||
@@ -141,27 +131,37 @@ public class ReactionEvent : ICurrencyEvent
|
||||
_client.ReactionAdded -= HandleReaction;
|
||||
_t.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
try { var _ = _msg.DeleteAsync(); } catch { }
|
||||
try
|
||||
{
|
||||
var _ = _msg.DeleteAsync();
|
||||
}
|
||||
catch { }
|
||||
|
||||
var os = OnEnded(_guild.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleReaction(Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> cacheable, SocketReaction r)
|
||||
private Task HandleReaction(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> cacheable,
|
||||
SocketReaction r)
|
||||
{
|
||||
var _ = Task.Run(() =>
|
||||
{
|
||||
if (_emote.Name != r.Emote.Name)
|
||||
return;
|
||||
if ((r.User.IsSpecified ? r.User.Value : null) is not IGuildUser gu // no unknown users, as they could be bots, or alts
|
||||
if ((r.User.IsSpecified
|
||||
? r.User.Value
|
||||
: null) is not IGuildUser gu // no unknown users, as they could be bots, or alts
|
||||
|| msg.Id != _msg.Id // same message
|
||||
|| gu.IsBot // no bots
|
||||
|| (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5 // no recently created accounts
|
||||
|| (_noRecentlyJoinedServer && // if specified, no users who joined the server in the last 24h
|
||||
(gu.JoinedAt is null || (DateTime.UtcNow - gu.JoinedAt.Value).TotalDays < 1))) // and no users for who we don't know when they joined
|
||||
{
|
||||
|| (_noRecentlyJoinedServer
|
||||
&& // if specified, no users who joined the server in the last 24h
|
||||
(gu.JoinedAt is null
|
||||
|| (DateTime.UtcNow - gu.JoinedAt.Value).TotalDays
|
||||
< 1))) // and no users for who we don't know when they joined
|
||||
return;
|
||||
}
|
||||
// there has to be money left in the pot
|
||||
// and the user wasn't rewarded
|
||||
if (_awardedUsers.Add(r.UserId) && TryTakeFromPot())
|
||||
@@ -174,11 +174,9 @@ public class ReactionEvent : ICurrencyEvent
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private readonly object potLock = new();
|
||||
private bool TryTakeFromPot()
|
||||
{
|
||||
if (_isPotLimited)
|
||||
{
|
||||
lock (potLock)
|
||||
{
|
||||
if (PotSize < _amount)
|
||||
@@ -187,7 +185,7 @@ public class ReactionEvent : ICurrencyEvent
|
||||
PotSize -= _amount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,25 +3,13 @@ using Cloneable;
|
||||
using NadekoBot.Common.Yml;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using YamlDotNet.Serialization;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||
{
|
||||
public GamblingConfig()
|
||||
{
|
||||
BetRoll = new();
|
||||
WheelOfFortune = new();
|
||||
Waifu = new();
|
||||
Currency = new();
|
||||
BetFlip = new();
|
||||
Generation = new();
|
||||
Timely = new();
|
||||
Decay = new();
|
||||
Slots = new();
|
||||
}
|
||||
|
||||
[Comment(@"DO NOT CHANGE")]
|
||||
public int Version { get; set; } = 2;
|
||||
|
||||
@@ -60,13 +48,26 @@ Set 0 for unlimited")]
|
||||
[Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
|
||||
1 = 100 currency per $. Used almost exclusively on public nadeko.")]
|
||||
public decimal PatreonCurrencyPerCent { get; set; } = 1;
|
||||
|
||||
|
||||
[Comment(@"Currency reward per vote.
|
||||
This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting")]
|
||||
public long VoteReward { get; set; } = 100;
|
||||
|
||||
|
||||
[Comment(@"Slot config")]
|
||||
public SlotsConfig Slots { get; set; }
|
||||
|
||||
public GamblingConfig()
|
||||
{
|
||||
BetRoll = new();
|
||||
WheelOfFortune = new();
|
||||
Waifu = new();
|
||||
Currency = new();
|
||||
BetFlip = new();
|
||||
Generation = new();
|
||||
Timely = new();
|
||||
Decay = new();
|
||||
Slots = new();
|
||||
}
|
||||
}
|
||||
|
||||
public class CurrencyConfig
|
||||
@@ -108,8 +109,7 @@ Doesn't have to be ordered.")]
|
||||
public BetRollConfig()
|
||||
=> Pairs = new BetRollPair[]
|
||||
{
|
||||
new() { WhenAbove = 99, MultiplyBy = 10 },
|
||||
new() { WhenAbove = 90, MultiplyBy = 4 },
|
||||
new() { WhenAbove = 99, MultiplyBy = 10 }, new() { WhenAbove = 90, MultiplyBy = 4 },
|
||||
new() { WhenAbove = 66, MultiplyBy = 2 }
|
||||
};
|
||||
}
|
||||
@@ -162,17 +162,7 @@ public partial class WheelOfFortuneSettings
|
||||
public decimal[] Multipliers { get; set; }
|
||||
|
||||
public WheelOfFortuneSettings()
|
||||
=> Multipliers = new decimal[]
|
||||
{
|
||||
1.7M,
|
||||
1.5M,
|
||||
0.2M,
|
||||
0.1M,
|
||||
0.3M,
|
||||
0.5M,
|
||||
1.2M,
|
||||
2.4M,
|
||||
};
|
||||
=> Multipliers = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M };
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
@@ -263,17 +253,17 @@ Default 1 (meaning no effect)")]
|
||||
Default 0.95 (meaning 95%)
|
||||
Example: If a waifu is worth 1000, and she receives a gift worth 100, her new value will be 1095)")]
|
||||
public decimal GiftEffect { get; set; } = 0.95M;
|
||||
|
||||
|
||||
[Comment(@"What percentage of the value of the gift will a waifu lose when she's gifted a gift marked as 'negative'.
|
||||
Default 0.5 (meaning 50%)
|
||||
Example: If a waifu is worth 1000, and she receives a negative gift worth 100, her new value will be 950)")]
|
||||
public decimal NegativeGiftEffect { get; set; } = 0.50M;
|
||||
}
|
||||
|
||||
public sealed partial class SlotsConfig
|
||||
public sealed class SlotsConfig
|
||||
{
|
||||
[Comment(@"Hex value of the color which the numbers on the slot image will have.")]
|
||||
public Rgba32 CurrencyFontColor { get; set; } = SixLabors.ImageSharp.Color.Red;
|
||||
public Rgba32 CurrencyFontColor { get; set; } = Color.Red;
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
@@ -282,16 +272,19 @@ public sealed partial class WaifuItemModel
|
||||
public string ItemEmoji { get; set; }
|
||||
public int Price { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
|
||||
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)]
|
||||
public bool Negative { get; set; }
|
||||
|
||||
public WaifuItemModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public WaifuItemModel(string itemEmoji, int price, string name, bool negative = false)
|
||||
|
||||
public WaifuItemModel(
|
||||
string itemEmoji,
|
||||
int price,
|
||||
string name,
|
||||
bool negative = false)
|
||||
{
|
||||
ItemEmoji = itemEmoji;
|
||||
Price = price;
|
||||
@@ -300,13 +293,13 @@ public sealed partial class WaifuItemModel
|
||||
}
|
||||
|
||||
|
||||
public override string ToString() => Name;
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
|
||||
|
||||
[Cloneable]
|
||||
public sealed partial class BetRollPair
|
||||
{
|
||||
public int WhenAbove { get; set; }
|
||||
public float MultiplyBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,4 @@ public enum GamblingError
|
||||
{
|
||||
None,
|
||||
NotEnough
|
||||
}
|
||||
}
|
||||
@@ -5,57 +5,55 @@ namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public abstract class GamblingModule<TService> : NadekoModule<TService>
|
||||
{
|
||||
protected GamblingConfig _config
|
||||
=> _lazyConfig.Value;
|
||||
|
||||
protected string CurrencySign
|
||||
=> _config.Currency.Sign;
|
||||
|
||||
protected string CurrencyName
|
||||
=> _config.Currency.Name;
|
||||
|
||||
private readonly Lazy<GamblingConfig> _lazyConfig;
|
||||
protected GamblingConfig _config => _lazyConfig.Value;
|
||||
protected string CurrencySign => _config.Currency.Sign;
|
||||
protected string CurrencyName => _config.Currency.Name;
|
||||
|
||||
|
||||
protected GamblingModule(GamblingConfigService gambService)
|
||||
=> _lazyConfig = new(() => gambService.Data);
|
||||
|
||||
private async Task<bool> InternalCheckBet(long amount)
|
||||
{
|
||||
if (amount < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (amount < 1) return false;
|
||||
if (amount < _config.MinBet)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.min_bet_limit(
|
||||
Format.Bold(_config.MinBet.ToString()) + CurrencySign));
|
||||
await ReplyErrorLocalizedAsync(strs.min_bet_limit(Format.Bold(_config.MinBet.ToString()) + CurrencySign));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_config.MaxBet > 0 && amount > _config.MaxBet)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.max_bet_limit(
|
||||
Format.Bold(_config.MaxBet.ToString()) + CurrencySign));
|
||||
await ReplyErrorLocalizedAsync(strs.max_bet_limit(Format.Bold(_config.MaxBet.ToString()) + CurrencySign));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Task<bool> CheckBetMandatory(long amount)
|
||||
{
|
||||
if (amount < 1)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
if (amount < 1) return Task.FromResult(false);
|
||||
return InternalCheckBet(amount);
|
||||
}
|
||||
|
||||
protected Task<bool> CheckBetOptional(long amount)
|
||||
{
|
||||
if (amount == 0)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
if (amount == 0) return Task.FromResult(true);
|
||||
return InternalCheckBet(amount);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
|
||||
{
|
||||
protected GamblingSubmodule(GamblingConfigService gamblingConfService) : base(gamblingConfService)
|
||||
protected GamblingSubmodule(GamblingConfigService gamblingConfService)
|
||||
: base(gamblingConfService)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,4 @@ public class Payout
|
||||
{
|
||||
public string User { get; set; }
|
||||
public int Amount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,64 +3,72 @@ namespace NadekoBot.Modules.Gambling.Common;
|
||||
|
||||
public class RollDuelGame
|
||||
{
|
||||
public ulong P1 { get; }
|
||||
public ulong P2 { get; }
|
||||
|
||||
private readonly ulong _botId;
|
||||
|
||||
public long Amount { get; }
|
||||
|
||||
private readonly ICurrencyService _cs;
|
||||
public enum Reason
|
||||
{
|
||||
Normal,
|
||||
NoFunds,
|
||||
Timeout
|
||||
}
|
||||
|
||||
public enum State
|
||||
{
|
||||
Waiting,
|
||||
Running,
|
||||
Ended,
|
||||
Ended
|
||||
}
|
||||
|
||||
public enum Reason
|
||||
{
|
||||
Normal,
|
||||
NoFunds,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
private readonly Timer _timeoutTimer;
|
||||
private readonly NadekoRandom _rng = new();
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
|
||||
public event Func<RollDuelGame, Task> OnGameTick;
|
||||
public event Func<RollDuelGame, Reason, Task> OnEnded;
|
||||
|
||||
public ulong P1 { get; }
|
||||
public ulong P2 { get; }
|
||||
|
||||
public long Amount { get; }
|
||||
|
||||
public List<(int, int)> Rolls { get; } = new();
|
||||
public State CurrentState { get; private set; }
|
||||
public ulong Winner { get; private set; }
|
||||
|
||||
public RollDuelGame(ICurrencyService cs, ulong botId, ulong p1, ulong p2, long amount)
|
||||
private readonly ulong _botId;
|
||||
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
private readonly Timer _timeoutTimer;
|
||||
private readonly NadekoRandom _rng = new();
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
|
||||
public RollDuelGame(
|
||||
ICurrencyService cs,
|
||||
ulong botId,
|
||||
ulong p1,
|
||||
ulong p2,
|
||||
long amount)
|
||||
{
|
||||
this.P1 = p1;
|
||||
this.P2 = p2;
|
||||
this._botId = botId;
|
||||
this.Amount = amount;
|
||||
P1 = p1;
|
||||
P2 = p2;
|
||||
_botId = botId;
|
||||
Amount = amount;
|
||||
_cs = cs;
|
||||
|
||||
_timeoutTimer = new(async delegate
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (CurrentState != State.Waiting)
|
||||
return;
|
||||
CurrentState = State.Ended;
|
||||
await (OnEnded?.Invoke(this, Reason.Timeout));
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
}, null, TimeSpan.FromSeconds(15), TimeSpan.FromMilliseconds(-1));
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (CurrentState != State.Waiting)
|
||||
return;
|
||||
CurrentState = State.Ended;
|
||||
await OnEnded?.Invoke(this, Reason.Timeout);
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromSeconds(15),
|
||||
TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
|
||||
public async Task StartGame()
|
||||
@@ -78,16 +86,17 @@ public class RollDuelGame
|
||||
_locker.Release();
|
||||
}
|
||||
|
||||
if(!await _cs.RemoveAsync(P1, "Roll Duel", Amount))
|
||||
if (!await _cs.RemoveAsync(P1, "Roll Duel", Amount))
|
||||
{
|
||||
await (OnEnded?.Invoke(this, Reason.NoFunds));
|
||||
await OnEnded?.Invoke(this, Reason.NoFunds);
|
||||
CurrentState = State.Ended;
|
||||
return;
|
||||
}
|
||||
if(!await _cs.RemoveAsync(P2, "Roll Duel", Amount))
|
||||
|
||||
if (!await _cs.RemoveAsync(P2, "Roll Duel", Amount))
|
||||
{
|
||||
await _cs.AddAsync(P1, "Roll Duel - refund", Amount);
|
||||
await (OnEnded?.Invoke(this, Reason.NoFunds));
|
||||
await OnEnded?.Invoke(this, Reason.NoFunds);
|
||||
CurrentState = State.Ended;
|
||||
return;
|
||||
}
|
||||
@@ -101,26 +110,25 @@ public class RollDuelGame
|
||||
if (n1 != n2)
|
||||
{
|
||||
if (n1 > n2)
|
||||
{
|
||||
Winner = P1;
|
||||
}
|
||||
Winner = P1;
|
||||
else
|
||||
{
|
||||
Winner = P2;
|
||||
}
|
||||
var won = (long)(Amount * 2 * 0.98f);
|
||||
await _cs.AddAsync(Winner, "Roll Duel win", won);
|
||||
|
||||
await _cs.AddAsync(_botId, "Roll Duel fee", (Amount * 2) - won);
|
||||
}
|
||||
try { await (OnGameTick?.Invoke(this)); } catch { }
|
||||
|
||||
try { await OnGameTick?.Invoke(this); }
|
||||
catch { }
|
||||
|
||||
await Task.Delay(2500);
|
||||
if (n1 != n2)
|
||||
break;
|
||||
}
|
||||
while (true);
|
||||
} while (true);
|
||||
|
||||
CurrentState = State.Ended;
|
||||
await (OnEnded?.Invoke(this, Reason.Normal));
|
||||
await OnEnded?.Invoke(this, Reason.Normal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,4 +136,4 @@ public struct RollDuelChallenge
|
||||
{
|
||||
public ulong Player1 { get; set; }
|
||||
public ulong Player2 { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,27 +3,11 @@ namespace NadekoBot.Modules.Gambling.Common.Slot;
|
||||
|
||||
public class SlotGame
|
||||
{
|
||||
public class Result
|
||||
{
|
||||
public float Multiplier { get; }
|
||||
public int[] Rolls { get; }
|
||||
|
||||
public Result(float multiplier, int[] rolls)
|
||||
{
|
||||
Multiplier = multiplier;
|
||||
Rolls = rolls;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Random _rng = new NadekoRandom();
|
||||
|
||||
public SlotGame()
|
||||
{
|
||||
}
|
||||
|
||||
public Result Spin()
|
||||
{
|
||||
var rolls = new int[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) };
|
||||
var rolls = new[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) };
|
||||
var multi = 0;
|
||||
|
||||
if (rolls.All(x => x == 5))
|
||||
@@ -37,4 +21,16 @@ public class SlotGame
|
||||
|
||||
return new(multi, rolls);
|
||||
}
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
public float Multiplier { get; }
|
||||
public int[] Rolls { get; }
|
||||
|
||||
public Result(float multiplier, int[] rolls)
|
||||
{
|
||||
Multiplier = multiplier;
|
||||
Rolls = rolls;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,4 @@ public class SlotResponse
|
||||
public long Won { get; set; }
|
||||
public List<int> Rolls { get; set; } = new();
|
||||
public GamblingError Error { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,4 @@ public enum AffinityTitle
|
||||
Sloot,
|
||||
Depraved,
|
||||
Harlot
|
||||
}
|
||||
}
|
||||
@@ -14,5 +14,5 @@ public enum ClaimTitle
|
||||
Veteran,
|
||||
Incubis,
|
||||
Harem_King,
|
||||
Harem_God,
|
||||
}
|
||||
Harem_God
|
||||
}
|
||||
@@ -7,4 +7,4 @@ public enum DivorceResult
|
||||
SucessWithPenalty,
|
||||
NotYourWife,
|
||||
Cooldown
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,4 @@ public enum WaifuClaimResult
|
||||
Success,
|
||||
NotEnoughFunds,
|
||||
InsufficientAmount
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,17 @@ namespace NadekoBot.Modules.Gambling.Common.WheelOfFortune;
|
||||
|
||||
public class WheelOfFortuneGame
|
||||
{
|
||||
public class Result
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public long Amount { get; set; }
|
||||
}
|
||||
|
||||
private readonly NadekoRandom _rng;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly long _bet;
|
||||
private readonly GamblingConfig _config;
|
||||
private readonly ulong _userId;
|
||||
|
||||
public WheelOfFortuneGame(ulong userId, long bet, GamblingConfig config, ICurrencyService cs)
|
||||
public WheelOfFortuneGame(
|
||||
ulong userId,
|
||||
long bet,
|
||||
GamblingConfig config,
|
||||
ICurrencyService cs)
|
||||
{
|
||||
_rng = new();
|
||||
_cs = cs;
|
||||
@@ -31,12 +29,14 @@ public class WheelOfFortuneGame
|
||||
var amount = (long)(_bet * _config.WheelOfFortune.Multipliers[result]);
|
||||
|
||||
if (amount > 0)
|
||||
await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, gamble: true);
|
||||
await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, true);
|
||||
|
||||
return new()
|
||||
{
|
||||
Index = result,
|
||||
Amount = amount,
|
||||
};
|
||||
return new() { Index = result, Amount = amount };
|
||||
}
|
||||
}
|
||||
|
||||
public class Result
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public long Amount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -6,53 +6,54 @@ namespace NadekoBot.Modules.Gambling.Common.Connect4;
|
||||
|
||||
public sealed class Connect4Game : IDisposable
|
||||
{
|
||||
public enum Field //temporary most likely
|
||||
{
|
||||
Empty,
|
||||
P1,
|
||||
P2
|
||||
}
|
||||
|
||||
public enum Phase
|
||||
{
|
||||
Joining, // waiting for second player to join
|
||||
P1Move,
|
||||
P2Move,
|
||||
Ended,
|
||||
}
|
||||
|
||||
public enum Field //temporary most likely
|
||||
{
|
||||
Empty,
|
||||
P1,
|
||||
P2,
|
||||
Ended
|
||||
}
|
||||
|
||||
public enum Result
|
||||
{
|
||||
Draw,
|
||||
CurrentPlayerWon,
|
||||
OtherPlayerWon,
|
||||
OtherPlayerWon
|
||||
}
|
||||
|
||||
public const int NumberOfColumns = 7;
|
||||
public const int NumberOfRows = 6;
|
||||
|
||||
public Phase CurrentPhase { get; private set; } = Phase.Joining;
|
||||
|
||||
//state is bottom to top, left to right
|
||||
private readonly Field[] _gameState = new Field[NumberOfRows * NumberOfColumns];
|
||||
private readonly (ulong UserId, string Username)?[] _players = new(ulong, string)?[2];
|
||||
|
||||
public ImmutableArray<Field> GameState => _gameState.ToImmutableArray();
|
||||
public ImmutableArray<(ulong UserId, string Username)?> Players => _players.ToImmutableArray();
|
||||
|
||||
public (ulong UserId, string Username) CurrentPlayer => CurrentPhase == Phase.P1Move
|
||||
? _players[0].Value
|
||||
: _players[1].Value;
|
||||
|
||||
public (ulong UserId, string Username) OtherPlayer => CurrentPhase == Phase.P2Move
|
||||
? _players[0].Value
|
||||
: _players[1].Value;
|
||||
|
||||
//public event Func<Connect4Game, Task> OnGameStarted;
|
||||
public event Func<Connect4Game, Task> OnGameStateUpdated;
|
||||
public event Func<Connect4Game, Task> OnGameFailedToStart;
|
||||
public event Func<Connect4Game, Result, Task> OnGameEnded;
|
||||
|
||||
public Phase CurrentPhase { get; private set; } = Phase.Joining;
|
||||
|
||||
public ImmutableArray<Field> GameState
|
||||
=> _gameState.ToImmutableArray();
|
||||
|
||||
public ImmutableArray<(ulong UserId, string Username)?> Players
|
||||
=> _players.ToImmutableArray();
|
||||
|
||||
public (ulong UserId, string Username) CurrentPlayer
|
||||
=> CurrentPhase == Phase.P1Move ? _players[0].Value : _players[1].Value;
|
||||
|
||||
public (ulong UserId, string Username) OtherPlayer
|
||||
=> CurrentPhase == Phase.P2Move ? _players[0].Value : _players[1].Value;
|
||||
|
||||
//state is bottom to top, left to right
|
||||
private readonly Field[] _gameState = new Field[NumberOfRows * NumberOfColumns];
|
||||
private readonly (ulong UserId, string Username)?[] _players = new (ulong, string)?[2];
|
||||
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
private readonly Options _options;
|
||||
private readonly ICurrencyService _cs;
|
||||
@@ -69,17 +70,18 @@ public sealed class Connect4Game : IDisposable
|
||||
* [ ][ ][ ][ ][ ][ ]
|
||||
*/
|
||||
|
||||
public Connect4Game(ulong userId, string userName, Options options, ICurrencyService cs)
|
||||
public Connect4Game(
|
||||
ulong userId,
|
||||
string userName,
|
||||
Options options,
|
||||
ICurrencyService cs)
|
||||
{
|
||||
_players[0] = (userId, userName);
|
||||
_options = options;
|
||||
_cs = cs;
|
||||
|
||||
_rng = new();
|
||||
for (var i = 0; i < NumberOfColumns * NumberOfRows; i++)
|
||||
{
|
||||
_gameState[i] = Field.Empty;
|
||||
}
|
||||
for (var i = 0; i < NumberOfColumns * NumberOfRows; i++) _gameState[i] = Field.Empty;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
@@ -97,7 +99,6 @@ public sealed class Connect4Game : IDisposable
|
||||
var __ = OnGameFailedToStart?.Invoke(this);
|
||||
CurrentPhase = Phase.Ended;
|
||||
await _cs.AddAsync(_players[0].Value.UserId, "Connect4-refund", _options.Bet, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
@@ -127,18 +128,23 @@ public sealed class Connect4Game : IDisposable
|
||||
_players[0] = (userId, userName);
|
||||
}
|
||||
else //else join as a second player
|
||||
{
|
||||
_players[1] = (userId, userName);
|
||||
}
|
||||
|
||||
CurrentPhase = Phase.P1Move; //start the game
|
||||
_playerTimeoutTimer = new(async state =>
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
}, null, TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromSeconds(_options.TurnTimer),
|
||||
TimeSpan.FromSeconds(_options.TurnTimer));
|
||||
var __ = OnGameStateUpdated?.Invoke(this);
|
||||
|
||||
return true;
|
||||
@@ -167,13 +173,11 @@ public sealed class Connect4Game : IDisposable
|
||||
|
||||
var start = NumberOfRows * inputCol;
|
||||
for (var i = start; i < start + NumberOfRows; i++)
|
||||
{
|
||||
if (_gameState[i] == Field.Empty)
|
||||
{
|
||||
_gameState[i] = GetPlayerPiece(userId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//check winnning condition
|
||||
// ok, i'll go from [0-2] in rows (and through all columns) and check upward if 4 are connected
|
||||
@@ -190,7 +194,6 @@ public sealed class Connect4Game : IDisposable
|
||||
|
||||
var first = _gameState[i + (j * NumberOfRows)];
|
||||
if (first != Field.Empty)
|
||||
{
|
||||
for (var k = 1; k < 4; k++)
|
||||
{
|
||||
var next = _gameState[i + k + (j * NumberOfRows)];
|
||||
@@ -201,9 +204,11 @@ public sealed class Connect4Game : IDisposable
|
||||
else
|
||||
continue;
|
||||
}
|
||||
else break;
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +225,6 @@ public sealed class Connect4Game : IDisposable
|
||||
|
||||
var first = _gameState[j + (i * NumberOfRows)];
|
||||
if (first != Field.Empty)
|
||||
{
|
||||
for (var k = 1; k < 4; k++)
|
||||
{
|
||||
var next = _gameState[j + ((i + k) * NumberOfRows)];
|
||||
@@ -231,7 +235,6 @@ public sealed class Connect4Game : IDisposable
|
||||
continue;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,10 +311,7 @@ public sealed class Connect4Game : IDisposable
|
||||
}
|
||||
|
||||
//check draw? if it's even possible
|
||||
if (_gameState.All(x => x != Field.Empty))
|
||||
{
|
||||
EndGame(Result.Draw, null);
|
||||
}
|
||||
if (_gameState.All(x => x != Field.Empty)) EndGame(Result.Draw, null);
|
||||
|
||||
if (CurrentPhase != Phase.Ended)
|
||||
{
|
||||
@@ -322,6 +322,7 @@ public sealed class Connect4Game : IDisposable
|
||||
|
||||
ResetTimer();
|
||||
}
|
||||
|
||||
var _ = OnGameStateUpdated?.Invoke(this);
|
||||
return true;
|
||||
}
|
||||
@@ -329,7 +330,8 @@ public sealed class Connect4Game : IDisposable
|
||||
}
|
||||
|
||||
private void ResetTimer()
|
||||
=> _playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
|
||||
=> _playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer),
|
||||
TimeSpan.FromSeconds(_options.TurnTimer));
|
||||
|
||||
private void EndGame(Result result, ulong? winId)
|
||||
{
|
||||
@@ -340,27 +342,25 @@ public sealed class Connect4Game : IDisposable
|
||||
|
||||
if (result == Result.Draw)
|
||||
{
|
||||
_cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", this._options.Bet, true);
|
||||
_cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", this._options.Bet, true);
|
||||
_cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", _options.Bet, true);
|
||||
_cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", _options.Bet, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (winId != null)
|
||||
_cs.AddAsync(winId.Value, "Connnect4-win", (long)(this._options.Bet * 1.98), true);
|
||||
_cs.AddAsync(winId.Value, "Connnect4-win", (long)(_options.Bet * 1.98), true);
|
||||
}
|
||||
|
||||
private Field GetPlayerPiece(ulong userId) => _players[0].Value.UserId == userId
|
||||
? Field.P1
|
||||
: Field.P2;
|
||||
private Field GetPlayerPiece(ulong userId)
|
||||
=> _players[0].Value.UserId == userId ? Field.P1 : Field.P2;
|
||||
|
||||
//column is full if there are no empty fields
|
||||
private bool IsColumnFull(int column)
|
||||
{
|
||||
var start = NumberOfRows * column;
|
||||
for (var i = start; i < start + NumberOfRows; i++)
|
||||
{
|
||||
if (_gameState[i] == Field.Empty)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -375,6 +375,16 @@ public sealed class Connect4Game : IDisposable
|
||||
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
[Option('t',
|
||||
"turn-timer",
|
||||
Required = false,
|
||||
Default = 15,
|
||||
HelpText = "Turn time in seconds. It has to be between 5 and 60. Default 15.")]
|
||||
public int TurnTimer { get; set; } = 15;
|
||||
|
||||
[Option('b', "bet", Required = false, Default = 0, HelpText = "Amount you bet. Default 0.")]
|
||||
public int Bet { get; set; }
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (TurnTimer is < 5 or > 60)
|
||||
@@ -383,10 +393,5 @@ public sealed class Connect4Game : IDisposable
|
||||
if (Bet < 0)
|
||||
Bet = 0;
|
||||
}
|
||||
|
||||
[Option('t', "turn-timer", Required = false, Default = 15, HelpText = "Turn time in seconds. It has to be between 5 and 60. Default 15.")]
|
||||
public int TurnTimer { get; set; } = 15;
|
||||
[Option('b', "bet", Required = false, Default = 0, HelpText = "Amount you bet. Default 0.")]
|
||||
public int Bet { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,28 @@ public partial class Gambling
|
||||
[Group]
|
||||
public class Connect4Commands : GamblingSubmodule<GamblingService>
|
||||
{
|
||||
private static readonly string[] numbers =
|
||||
{
|
||||
":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:"
|
||||
};
|
||||
|
||||
private int RepostCounter
|
||||
{
|
||||
get => _repostCounter;
|
||||
set
|
||||
{
|
||||
if (value is < 0 or > 7)
|
||||
_repostCounter = 0;
|
||||
else _repostCounter = value;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ICurrencyService _cs;
|
||||
private static readonly string[] numbers = new string[] { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:" };
|
||||
|
||||
private IUserMessage msg;
|
||||
|
||||
private int _repostCounter;
|
||||
|
||||
public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb)
|
||||
: base(gamb)
|
||||
@@ -22,7 +41,8 @@ public partial class Gambling
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NadekoOptionsAttribute(typeof(Connect4Game.Options))]
|
||||
public async Task Connect4(params string[] args)
|
||||
@@ -45,7 +65,6 @@ public partial class Gambling
|
||||
}
|
||||
|
||||
if (options.Bet > 0)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id, "Connect4-bet", options.Bet, true))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
@@ -53,7 +72,6 @@ public partial class Gambling
|
||||
game.Dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
game.OnGameStateUpdated += Game_OnGameStateUpdated;
|
||||
game.OnGameFailedToStart += Game_OnGameFailedToStart;
|
||||
@@ -62,13 +80,9 @@ public partial class Gambling
|
||||
|
||||
game.Initialize();
|
||||
if (options.Bet == 0)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.connect4_created);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.connect4_created_bet(options.Bet + CurrencySign));
|
||||
}
|
||||
|
||||
Task _client_MessageReceived(SocketMessage arg)
|
||||
{
|
||||
@@ -78,22 +92,20 @@ public partial class Gambling
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
var success = false;
|
||||
if (int.TryParse(arg.Content, out var col))
|
||||
{
|
||||
success = await game.Input(arg.Author.Id, col);
|
||||
}
|
||||
if (int.TryParse(arg.Content, out var col)) success = await game.Input(arg.Author.Id, col);
|
||||
|
||||
if (success)
|
||||
try { await arg.DeleteAsync(); } catch { }
|
||||
{
|
||||
try { await arg.DeleteAsync(); }
|
||||
catch { }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (game.CurrentPhase is Connect4Game.Phase.Joining or Connect4Game.Phase.Ended)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (game.CurrentPhase is Connect4Game.Phase.Joining or Connect4Game.Phase.Ended) return;
|
||||
RepostCounter++;
|
||||
if (RepostCounter == 0)
|
||||
try { msg = await ctx.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()); } catch { }
|
||||
try { msg = await ctx.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()); }
|
||||
catch { }
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
@@ -106,6 +118,7 @@ public partial class Gambling
|
||||
_client.MessageReceived -= _client_MessageReceived;
|
||||
toDispose.Dispose();
|
||||
}
|
||||
|
||||
return ErrorLocalizedAsync(strs.connect4_failed_to_start);
|
||||
}
|
||||
|
||||
@@ -119,44 +132,28 @@ public partial class Gambling
|
||||
|
||||
string title;
|
||||
if (result == Connect4Game.Result.CurrentPlayerWon)
|
||||
{
|
||||
title = GetText(strs.connect4_won(Format.Bold(arg.CurrentPlayer.Username), Format.Bold(arg.OtherPlayer.Username)));
|
||||
}
|
||||
title = GetText(strs.connect4_won(Format.Bold(arg.CurrentPlayer.Username),
|
||||
Format.Bold(arg.OtherPlayer.Username)));
|
||||
else if (result == Connect4Game.Result.OtherPlayerWon)
|
||||
{
|
||||
title = GetText(strs.connect4_won(Format.Bold(arg.OtherPlayer.Username), Format.Bold(arg.CurrentPlayer.Username)));
|
||||
}
|
||||
title = GetText(strs.connect4_won(Format.Bold(arg.OtherPlayer.Username),
|
||||
Format.Bold(arg.CurrentPlayer.Username)));
|
||||
else
|
||||
title = GetText(strs.connect4_draw);
|
||||
|
||||
return msg.ModifyAsync(x => x.Embed = _eb.Create()
|
||||
.WithTitle(title)
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
private IUserMessage msg;
|
||||
|
||||
private int _repostCounter = 0;
|
||||
private int RepostCounter
|
||||
{
|
||||
get => _repostCounter;
|
||||
set
|
||||
{
|
||||
if (value is < 0 or > 7)
|
||||
_repostCounter = 0;
|
||||
else _repostCounter = value;
|
||||
.WithTitle(title)
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Game_OnGameStateUpdated(Connect4Game game)
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor();
|
||||
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
|
||||
.WithDescription(GetGameStateText(game))
|
||||
.WithOkColor();
|
||||
|
||||
|
||||
if (msg is null)
|
||||
@@ -185,14 +182,12 @@ public partial class Gambling
|
||||
else
|
||||
sb.Append("🔵"); //blue circle
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
for (var i = 0; i < Connect4Game.NumberOfColumns; i++)
|
||||
{
|
||||
sb.Append(numbers[i]);
|
||||
}
|
||||
for (var i = 0; i < Connect4Game.NumberOfColumns; i++) sb.Append(numbers[i]);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Gambling.Common.Events;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Events;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
@@ -11,65 +11,51 @@ public partial class Gambling
|
||||
[Group]
|
||||
public class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
||||
{
|
||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf)
|
||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
|
||||
: base(gamblingConf)
|
||||
{
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NadekoOptionsAttribute(typeof(EventOptions))]
|
||||
[OwnerOnly]
|
||||
public async Task EventStart(CurrencyEvent.Type ev, params string[] options)
|
||||
{
|
||||
var (opts, _) = OptionsParser.ParseFrom(new EventOptions(), options);
|
||||
if (!await _service.TryCreateEventAsync(ctx.Guild.Id,
|
||||
ctx.Channel.Id,
|
||||
ev,
|
||||
opts,
|
||||
GetEmbed))
|
||||
{
|
||||
if (!await _service.TryCreateEventAsync(ctx.Guild.Id, ctx.Channel.Id, ev, opts, GetEmbed))
|
||||
await ReplyErrorLocalizedAsync(strs.start_event_fail);
|
||||
}
|
||||
}
|
||||
|
||||
private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
|
||||
=> type switch
|
||||
{
|
||||
CurrencyEvent.Type.Reaction => _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
|
||||
CurrencyEvent.Type.GameStatus => _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.event_title(type.ToString())))
|
||||
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
|
||||
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
||||
};
|
||||
|
||||
private string GetReactionDescription(long amount, long potSize)
|
||||
{
|
||||
var potSizeStr = Format.Bold(potSize == 0
|
||||
? "∞" + CurrencySign
|
||||
: potSize + CurrencySign);
|
||||
|
||||
return GetText(strs.new_reaction_event(
|
||||
CurrencySign,
|
||||
Format.Bold(amount + CurrencySign),
|
||||
potSizeStr));
|
||||
var potSizeStr = Format.Bold(potSize == 0 ? "∞" + CurrencySign : potSize + CurrencySign);
|
||||
|
||||
return GetText(strs.new_reaction_event(CurrencySign, Format.Bold(amount + CurrencySign), potSizeStr));
|
||||
}
|
||||
|
||||
private string GetGameStatusDescription(long amount, long potSize)
|
||||
{
|
||||
var potSizeStr = Format.Bold(potSize == 0
|
||||
? "∞" + CurrencySign
|
||||
: potSize + CurrencySign);
|
||||
|
||||
return GetText(strs.new_gamestatus_event(
|
||||
CurrencySign,
|
||||
Format.Bold(amount + CurrencySign),
|
||||
potSizeStr));
|
||||
var potSizeStr = Format.Bold(potSize == 0 ? "∞" + CurrencySign : potSize + CurrencySign);
|
||||
|
||||
return GetText(strs.new_gamestatus_event(CurrencySign, Format.Bold(amount + CurrencySign), potSizeStr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
@@ -10,29 +10,35 @@ public partial class Gambling
|
||||
{
|
||||
public enum Mixed { Mixed }
|
||||
|
||||
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
|
||||
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService)
|
||||
: base(gamblingConfService)
|
||||
{
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task RaffleCur(Mixed _, ShmartNumber amount) =>
|
||||
RaffleCur(amount, true);
|
||||
public Task RaffleCur(Mixed _, ShmartNumber amount)
|
||||
=> RaffleCur(amount, true);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task RaffleCur(ShmartNumber amount, bool mixed = false)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount))
|
||||
return;
|
||||
|
||||
async Task OnEnded(IUser arg, long won)
|
||||
{
|
||||
await SendConfirmAsync(GetText(strs.rafflecur_ended(CurrencyName, Format.Bold(arg.ToString()), won + CurrencySign)));
|
||||
await SendConfirmAsync(GetText(strs.rafflecur_ended(CurrencyName,
|
||||
Format.Bold(arg.ToString()),
|
||||
won + CurrencySign)));
|
||||
}
|
||||
var res = await _service.JoinOrCreateGame(ctx.Channel.Id,
|
||||
ctx.User, amount, mixed, OnEnded);
|
||||
|
||||
var res = await _service.JoinOrCreateGame(ctx.Channel.Id, ctx.User, amount, mixed, OnEnded);
|
||||
|
||||
if (res.Item1 != null)
|
||||
{
|
||||
@@ -49,4 +55,4 @@ public partial class Gambling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,9 @@ public partial class Gambling
|
||||
[Group]
|
||||
public class DiceRollCommands : NadekoSubmodule
|
||||
{
|
||||
private static readonly Regex dndRegex = new(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
|
||||
private static readonly Regex dndRegex = new(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex fudgeRegex = new(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
|
||||
|
||||
private static readonly char[] _fateRolls = { '-', ' ', '+' };
|
||||
@@ -20,7 +22,8 @@ public partial class Gambling
|
||||
public DiceRollCommands(IDataCache data)
|
||||
=> _images = data.LocalImages;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Roll()
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
@@ -38,23 +41,27 @@ public partial class Gambling
|
||||
Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Roll(int num)
|
||||
=> await InternalRoll(num, true);
|
||||
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Rolluo(int num = 1)
|
||||
=> await InternalRoll(num, false);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Roll(string arg)
|
||||
=> await InternallDndRoll(arg, true);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Rolluo(string arg)
|
||||
=> await InternallDndRoll(arg, false);
|
||||
@@ -81,65 +88,66 @@ public partial class Gambling
|
||||
toInsert = 0;
|
||||
else if (randomNumber != 1)
|
||||
for (var j = 0; j < dice.Count; j++)
|
||||
{
|
||||
if (values[j] < randomNumber)
|
||||
{
|
||||
toInsert = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
toInsert = dice.Count;
|
||||
}
|
||||
|
||||
dice.Insert(toInsert, GetDice(randomNumber));
|
||||
values.Insert(toInsert, randomNumber);
|
||||
}
|
||||
|
||||
using var bitmap = dice.Merge(out var format);
|
||||
await using var ms = bitmap.ToStream(format);
|
||||
foreach (var d in dice)
|
||||
{
|
||||
d.Dispose();
|
||||
}
|
||||
foreach (var d in dice) d.Dispose();
|
||||
|
||||
await ctx.Channel.SendFileAsync(ms, $"dice.{format.FileExtensions.First()}",
|
||||
Format.Bold(ctx.User.ToString()) + " " +
|
||||
GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString()))) +
|
||||
" " + GetText(strs.total_average(
|
||||
Format.Bold(values.Sum().ToString()),
|
||||
await ctx.Channel.SendFileAsync(ms,
|
||||
$"dice.{format.FileExtensions.First()}",
|
||||
Format.Bold(ctx.User.ToString())
|
||||
+ " "
|
||||
+ GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString())))
|
||||
+ " "
|
||||
+ GetText(strs.total_average(Format.Bold(values.Sum().ToString()),
|
||||
Format.Bold((values.Sum() / (1.0f * values.Count)).ToString("N2")))));
|
||||
}
|
||||
|
||||
private async Task InternallDndRoll(string arg, bool ordered)
|
||||
{
|
||||
Match match;
|
||||
if ((match = fudgeRegex.Match(arg)).Length != 0 &&
|
||||
int.TryParse(match.Groups["n1"].ToString(), out var n1) &&
|
||||
n1 is > 0 and < 500)
|
||||
if ((match = fudgeRegex.Match(arg)).Length != 0
|
||||
&& int.TryParse(match.Groups["n1"].ToString(), out var n1)
|
||||
&& n1 is > 0 and < 500)
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
var rolls = new List<char>();
|
||||
|
||||
for (var i = 0; i < n1; i++)
|
||||
{
|
||||
rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
|
||||
}
|
||||
for (var i = 0; i < n1; i++) rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(ctx.User.Mention + " " + GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
|
||||
.AddField(Format.Bold("Result"), string.Join(" ", rolls.Select(c => Format.Code($"[{c}]"))));
|
||||
|
||||
.WithOkColor()
|
||||
.WithDescription(ctx.User.Mention
|
||||
+ " "
|
||||
+ GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
|
||||
.AddField(Format.Bold("Result"),
|
||||
string.Join(" ", rolls.Select(c => Format.Code($"[{c}]"))));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
else if ((match = dndRegex.Match(arg)).Length != 0)
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
if (int.TryParse(match.Groups["n1"].ToString(), out n1) &&
|
||||
int.TryParse(match.Groups["n2"].ToString(), out var n2) &&
|
||||
n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
|
||||
if (int.TryParse(match.Groups["n1"].ToString(), out n1)
|
||||
&& int.TryParse(match.Groups["n2"].ToString(), out var n2)
|
||||
&& n1 <= 50
|
||||
&& n2 <= 100000
|
||||
&& n1 > 0
|
||||
&& n2 > 0)
|
||||
{
|
||||
if (!int.TryParse(match.Groups["add"].Value, out var add))
|
||||
add = 0;
|
||||
@@ -147,39 +155,39 @@ public partial class Gambling
|
||||
sub = 0;
|
||||
|
||||
var arr = new int[n1];
|
||||
for (var i = 0; i < n1; i++)
|
||||
{
|
||||
arr[i] = rng.Next(1, n2 + 1);
|
||||
}
|
||||
for (var i = 0; i < n1; i++) arr[i] = rng.Next(1, n2 + 1);
|
||||
|
||||
var sum = arr.Sum();
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithDescription(ctx.User.Mention + " " + GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
|
||||
.AddField(Format.Bold("Rolls"), string.Join(" ",
|
||||
(ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x =>
|
||||
Format.Code(x.ToString()))))
|
||||
.AddField(Format.Bold("Sum"),
|
||||
sum + " + " + add + " - " + sub + " = " + (sum + add - sub));
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(ctx.User.Mention
|
||||
+ " "
|
||||
+ GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
|
||||
.AddField(Format.Bold("Rolls"),
|
||||
string.Join(" ",
|
||||
(ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x
|
||||
=> Format.Code(x.ToString()))))
|
||||
.AddField(Format.Bold("Sum"),
|
||||
sum + " + " + add + " - " + sub + " = " + (sum + add - sub));
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task NRoll([Leftover] string range)
|
||||
{
|
||||
int rolled;
|
||||
if (range.Contains("-"))
|
||||
{
|
||||
var arr = range.Split('-')
|
||||
.Take(2)
|
||||
.Select(int.Parse)
|
||||
.ToArray();
|
||||
var arr = range.Split('-').Take(2).Select(int.Parse).ToArray();
|
||||
if (arr[0] > arr[1])
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.second_larger_than_first);
|
||||
return;
|
||||
}
|
||||
|
||||
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
|
||||
}
|
||||
else
|
||||
@@ -202,7 +210,8 @@ public partial class Gambling
|
||||
using var imgZero = Image.Load(images[0]);
|
||||
return new[] { imgOne, imgZero }.Merge();
|
||||
}
|
||||
|
||||
return Image.Load(_images.Dice[num]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
@@ -37,18 +37,17 @@ public partial class Gambling
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var currentCard = cards.Draw();
|
||||
cardObjects.Add(currentCard);
|
||||
images.Add(Image.Load(_images.GetCard(currentCard.ToString().ToLowerInvariant().Replace(' ', '_'))));
|
||||
}
|
||||
|
||||
using var img = images.Merge();
|
||||
foreach (var i in images)
|
||||
{
|
||||
i.Dispose();
|
||||
}
|
||||
foreach (var i in images) i.Dispose();
|
||||
|
||||
var toSend = $"{Format.Bold(ctx.User.ToString())}";
|
||||
if (cardObjects.Count == 5)
|
||||
@@ -60,7 +59,8 @@ public partial class Gambling
|
||||
return (img.ToStream(), toSend);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Draw(int num = 1)
|
||||
{
|
||||
@@ -76,7 +76,8 @@ public partial class Gambling
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task DrawNew(int num = 1)
|
||||
{
|
||||
if (num < 1)
|
||||
@@ -91,7 +92,8 @@ public partial class Gambling
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task DeckShuffle()
|
||||
{
|
||||
@@ -108,4 +110,4 @@ public partial class Gambling
|
||||
await ReplyConfirmLocalizedAsync(strs.deck_reshuffled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,17 +12,29 @@ public partial class Gambling
|
||||
[Group]
|
||||
public class FlipCoinCommands : GamblingSubmodule<GamblingService>
|
||||
{
|
||||
public enum BetFlipGuess
|
||||
{
|
||||
H = 1,
|
||||
Head = 1,
|
||||
Heads = 1,
|
||||
T = 2,
|
||||
Tail = 2,
|
||||
Tails = 2
|
||||
}
|
||||
|
||||
private static readonly NadekoRandom rng = new();
|
||||
private readonly IImageCache _images;
|
||||
private readonly ICurrencyService _cs;
|
||||
private static readonly NadekoRandom rng = new();
|
||||
|
||||
public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) : base(gss)
|
||||
public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss)
|
||||
: base(gss)
|
||||
{
|
||||
_images = data.LocalImages;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Flip(int count = 1)
|
||||
{
|
||||
if (count is > 10 or < 1)
|
||||
@@ -30,6 +42,7 @@ public partial class Gambling
|
||||
await ReplyErrorLocalizedAsync(strs.flip_invalid(10));
|
||||
return;
|
||||
}
|
||||
|
||||
var headCount = 0;
|
||||
var tailCount = 0;
|
||||
var imgs = new Image<Rgba32>[count];
|
||||
@@ -51,40 +64,31 @@ public partial class Gambling
|
||||
|
||||
using var img = imgs.Merge(out var format);
|
||||
await using var stream = img.ToStream(format);
|
||||
foreach (var i in imgs)
|
||||
{
|
||||
i.Dispose();
|
||||
}
|
||||
foreach (var i in imgs) i.Dispose();
|
||||
var msg = count != 1
|
||||
? Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_results(count, headCount, tailCount))
|
||||
: Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flipped(headCount > 0
|
||||
? Format.Bold(GetText(strs.heads))
|
||||
: Format.Bold(GetText(strs.tails))));
|
||||
: Format.Bold(ctx.User.ToString())
|
||||
+ " "
|
||||
+ GetText(strs.flipped(headCount > 0
|
||||
? Format.Bold(GetText(strs.heads))
|
||||
: Format.Bold(GetText(strs.tails))));
|
||||
await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg);
|
||||
}
|
||||
|
||||
public enum BetFlipGuess
|
||||
{
|
||||
H = 1,
|
||||
Head = 1,
|
||||
Heads = 1,
|
||||
T = 2,
|
||||
Tail = 2,
|
||||
Tails = 2
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Betflip(ShmartNumber amount, BetFlipGuess guess)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount) || amount == 1)
|
||||
return;
|
||||
|
||||
var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, gamble: true);
|
||||
var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, true);
|
||||
if (!removed)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
BetFlipGuess result;
|
||||
Uri imageToSend;
|
||||
var coins = _images.ImageUrls.Coins;
|
||||
@@ -104,17 +108,17 @@ public partial class Gambling
|
||||
{
|
||||
var toWin = (long)(amount * _config.BetFlip.Multiplier);
|
||||
str = Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_guess(toWin + CurrencySign));
|
||||
await _cs.AddAsync(ctx.User, "Betflip Gamble", toWin, false, gamble: true);
|
||||
await _cs.AddAsync(ctx.User, "Betflip Gamble", toWin, false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
str = ctx.User.ToString() + " " + GetText(strs.better_luck);
|
||||
str = ctx.User + " " + GetText(strs.better_luck);
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithDescription(str)
|
||||
.WithOkColor()
|
||||
.WithImageUrl(imageToSend.ToString()));
|
||||
.WithDescription(str)
|
||||
.WithOkColor()
|
||||
.WithImageUrl(imageToSend.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,35 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling : GamblingModule<GamblingService>
|
||||
{
|
||||
public enum RpsPick
|
||||
{
|
||||
R = 0,
|
||||
Rock = 0,
|
||||
Rocket = 0,
|
||||
P = 1,
|
||||
Paper = 1,
|
||||
Paperclip = 1,
|
||||
S = 2,
|
||||
Scissors = 2
|
||||
}
|
||||
|
||||
public enum RpsResult
|
||||
{
|
||||
Win,
|
||||
Loss,
|
||||
Draw
|
||||
}
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly IDataCache _cache;
|
||||
@@ -19,9 +38,16 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
private readonly DownloadTracker _tracker;
|
||||
private readonly GamblingConfigService _configService;
|
||||
|
||||
public Gambling(DbService db, ICurrencyService currency,
|
||||
IDataCache cache, DiscordSocketClient client,
|
||||
DownloadTracker tracker, GamblingConfigService configService) : base(configService)
|
||||
private IUserMessage rdMsg;
|
||||
|
||||
public Gambling(
|
||||
DbService db,
|
||||
ICurrencyService currency,
|
||||
IDataCache cache,
|
||||
DiscordSocketClient client,
|
||||
DownloadTracker tracker,
|
||||
GamblingConfigService configService)
|
||||
: base(configService)
|
||||
{
|
||||
_db = db;
|
||||
_cs = currency;
|
||||
@@ -48,30 +74,33 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return n(uow.DiscordUser.GetUserCurrency(id));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Economy()
|
||||
{
|
||||
var ec = _service.GetEconomy();
|
||||
decimal onePercent = 0;
|
||||
if (ec.Cash > 0)
|
||||
{
|
||||
onePercent = ec.OnePercent / (ec.Cash-ec.Bot); // This stops the top 1% from owning more than 100% of the money
|
||||
// [21:03] Bob Page: Kinda remids me of US economy
|
||||
}
|
||||
onePercent =
|
||||
ec.OnePercent / (ec.Cash - ec.Bot); // This stops the top 1% from owning more than 100% of the money
|
||||
// [21:03] Bob Page: Kinda remids me of US economy
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.economy_state))
|
||||
.AddField(GetText(strs.currency_owned), ((BigInteger)(ec.Cash - ec.Bot)).ToString("N", Culture) + CurrencySign)
|
||||
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
|
||||
.AddField(GetText(strs.currency_planted), (BigInteger)ec.Planted)
|
||||
.AddField(GetText(strs.owned_waifus_total), (BigInteger)ec.Waifus + CurrencySign)
|
||||
.AddField(GetText(strs.bot_currency), n(ec.Bot))
|
||||
.AddField(GetText(strs.total), ((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", Culture) + CurrencySign)
|
||||
.WithOkColor();
|
||||
.WithTitle(GetText(strs.economy_state))
|
||||
.AddField(GetText(strs.currency_owned),
|
||||
((BigInteger)(ec.Cash - ec.Bot)).ToString("N", Culture) + CurrencySign)
|
||||
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
|
||||
.AddField(GetText(strs.currency_planted), (BigInteger)ec.Planted)
|
||||
.AddField(GetText(strs.owned_waifus_total), (BigInteger)ec.Waifus + CurrencySign)
|
||||
.AddField(GetText(strs.bot_currency), n(ec.Bot))
|
||||
.AddField(GetText(strs.total),
|
||||
((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", Culture) + CurrencySign)
|
||||
.WithOkColor();
|
||||
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Timely()
|
||||
{
|
||||
var val = _config.Timely.Amount;
|
||||
@@ -94,7 +123,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await ReplyConfirmLocalizedAsync(strs.timely(n(val), period));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task TimelyReset()
|
||||
{
|
||||
@@ -102,26 +132,28 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await ReplyConfirmLocalizedAsync(strs.timely_reset);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task TimelySet(int amount, int period = 24)
|
||||
{
|
||||
if (amount < 0 || period < 0)
|
||||
return;
|
||||
|
||||
|
||||
_configService.ModifyConfig(gs =>
|
||||
{
|
||||
gs.Timely.Amount = amount;
|
||||
gs.Timely.Cooldown = period;
|
||||
});
|
||||
|
||||
|
||||
if (amount == 0)
|
||||
await ReplyConfirmLocalizedAsync(strs.timely_set_none);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.timely_set(Format.Bold(n(amount)), Format.Bold(period.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Raffle([Leftover] IRole role = null)
|
||||
{
|
||||
@@ -129,15 +161,15 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
var members = (await role.GetMembersAsync()).Where(u => u.Status != UserStatus.Offline);
|
||||
var membersArray = members as IUser[] ?? members.ToArray();
|
||||
if (membersArray.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (membersArray.Length == 0) return;
|
||||
var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)];
|
||||
await SendConfirmAsync("🎟 " + GetText(strs.raffled_user), $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}");
|
||||
await SendConfirmAsync("🎟 " + GetText(strs.raffled_user),
|
||||
$"**{usr.Username}#{usr.Discriminator}**",
|
||||
footer: $"ID: {usr.Id}");
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RaffleAny([Leftover] IRole role = null)
|
||||
{
|
||||
@@ -145,30 +177,32 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
var members = await role.GetMembersAsync();
|
||||
var membersArray = members as IUser[] ?? members.ToArray();
|
||||
if (membersArray.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (membersArray.Length == 0) return;
|
||||
var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)];
|
||||
await SendConfirmAsync("🎟 " + GetText(strs.raffled_user), $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}");
|
||||
await SendConfirmAsync("🎟 " + GetText(strs.raffled_user),
|
||||
$"**{usr.Username}#{usr.Discriminator}**",
|
||||
footer: $"ID: {usr.Id}");
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(2)]
|
||||
public Task CurrencyTransactions(int page = 1) =>
|
||||
InternalCurrencyTransactions(ctx.User.Id, page);
|
||||
public Task CurrencyTransactions(int page = 1)
|
||||
=> InternalCurrencyTransactions(ctx.User.Id, page);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
[Priority(0)]
|
||||
public Task CurrencyTransactions([Leftover] IUser usr) =>
|
||||
InternalCurrencyTransactions(usr.Id, 1);
|
||||
public Task CurrencyTransactions([Leftover] IUser usr)
|
||||
=> InternalCurrencyTransactions(usr.Id, 1);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
[Priority(1)]
|
||||
public Task CurrencyTransactions(IUser usr, int page) =>
|
||||
InternalCurrencyTransactions(usr.Id, page);
|
||||
public Task CurrencyTransactions(IUser usr, int page)
|
||||
=> InternalCurrencyTransactions(usr.Id, page);
|
||||
|
||||
private async Task InternalCurrencyTransactions(ulong userId, int page)
|
||||
{
|
||||
@@ -182,9 +216,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.transactions(
|
||||
((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString() ?? $"{userId}")))
|
||||
.WithOkColor();
|
||||
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
||||
?? $"{userId}")))
|
||||
.WithOkColor();
|
||||
|
||||
var desc = string.Empty;
|
||||
foreach (var tr in trs)
|
||||
@@ -199,12 +233,14 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(0)]
|
||||
public async Task Cash(ulong userId)
|
||||
=> await ReplyConfirmLocalizedAsync(strs.has(Format.Code(userId.ToString()), $"{GetCurrency(userId)}"));
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Cash([Leftover] IUser user = null)
|
||||
{
|
||||
@@ -212,44 +248,51 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await ConfirmLocalizedAsync(strs.has(Format.Bold(user.ToString()), $"{GetCurrency(user.Id)}"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task Give(ShmartNumber amount, IGuildUser receiver, [Leftover] string msg = null)
|
||||
{
|
||||
if (amount <= 0 || ctx.User.Id == receiver.Id || receiver.IsBot)
|
||||
return;
|
||||
var success = await _cs.RemoveAsync((IGuildUser)ctx.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, false);
|
||||
var success =
|
||||
await _cs.RemoveAsync((IGuildUser)ctx.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount);
|
||||
if (!success)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
await _cs.AddAsync(receiver, $"Gift from {ctx.User.Username} ({ctx.User.Id}) - {msg}.", amount, true);
|
||||
await ReplyConfirmLocalizedAsync(strs.gifted(n(amount), Format.Bold(receiver.ToString())));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public Task Give(ShmartNumber amount, [Leftover] IGuildUser receiver)
|
||||
=> Give(amount, receiver, null);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(0)]
|
||||
public Task Award(long amount, IGuildUser usr, [Leftover] string msg) =>
|
||||
Award(amount, usr.Id, msg);
|
||||
public Task Award(long amount, IGuildUser usr, [Leftover] string msg)
|
||||
=> Award(amount, usr.Id, msg);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(1)]
|
||||
public Task Award(long amount, [Leftover] IGuildUser usr) =>
|
||||
Award(amount, usr.Id);
|
||||
public Task Award(long amount, [Leftover] IGuildUser usr)
|
||||
=> Award(amount, usr.Id);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
[Priority(2)]
|
||||
public async Task Award(long amount, ulong usrId, [Leftover] string msg = null)
|
||||
@@ -259,7 +302,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
var usr = await ((DiscordSocketClient)Context.Client).Rest.GetUserAsync(usrId);
|
||||
|
||||
if(usr is null)
|
||||
if (usr is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.user_not_found);
|
||||
return;
|
||||
@@ -272,28 +315,27 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await ReplyConfirmLocalizedAsync(strs.awarded(n(amount), $"<@{usrId}>"));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(3)]
|
||||
public async Task Award(long amount, [Leftover] IRole role)
|
||||
{
|
||||
var users = (await ctx.Guild.GetUsersAsync())
|
||||
.Where(u => u.GetRoles().Contains(role))
|
||||
.ToList();
|
||||
var users = (await ctx.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).ToList();
|
||||
|
||||
await _cs.AddBulkAsync(users.Select(x => x.Id),
|
||||
users.Select(x => $"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
|
||||
users.Select(x => amount),
|
||||
gamble: true);
|
||||
users.Select(x => $"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
|
||||
users.Select(x => amount),
|
||||
true);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.mass_award(
|
||||
n(amount),
|
||||
await ReplyConfirmLocalizedAsync(strs.mass_award(n(amount),
|
||||
Format.Bold(users.Count.ToString()),
|
||||
Format.Bold(role.Name)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(0)]
|
||||
@@ -302,17 +344,17 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
var users = (await role.GetMembersAsync()).ToList();
|
||||
|
||||
await _cs.RemoveBulkAsync(users.Select(x => x.Id),
|
||||
users.Select(x => $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
|
||||
users.Select(x => amount),
|
||||
gamble: true);
|
||||
users.Select(x => $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
|
||||
users.Select(x => amount),
|
||||
true);
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.mass_take(
|
||||
n(amount),
|
||||
await ReplyConfirmLocalizedAsync(strs.mass_take(n(amount),
|
||||
Format.Bold(users.Count.ToString()),
|
||||
Format.Bold(role.Name)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
[Priority(1)]
|
||||
@@ -321,7 +363,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
if (await _cs.RemoveAsync(user, $"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})", amount,
|
||||
if (await _cs.RemoveAsync(user,
|
||||
$"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})",
|
||||
amount,
|
||||
gamble: ctx.Client.CurrentUser.Id != user.Id))
|
||||
await ReplyConfirmLocalizedAsync(strs.take(n(amount), Format.Bold(user.ToString())));
|
||||
else
|
||||
@@ -329,23 +373,25 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task Take(long amount, [Leftover] ulong usrId)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
if (await _cs.RemoveAsync(usrId, $"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})", amount,
|
||||
gamble: ctx.Client.CurrentUser.Id != usrId))
|
||||
if (await _cs.RemoveAsync(usrId,
|
||||
$"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})",
|
||||
amount,
|
||||
ctx.Client.CurrentUser.Id != usrId))
|
||||
await ReplyConfirmLocalizedAsync(strs.take(n(amount), $"<@{usrId}>"));
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.take_fail(n(amount), Format.Code(usrId.ToString()), CurrencySign));
|
||||
}
|
||||
|
||||
private IUserMessage rdMsg = null;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RollDuel(IUser u)
|
||||
{
|
||||
@@ -354,13 +400,11 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
//since the challenge is created by another user, we need to reverse the ids
|
||||
//if it gets removed, means challenge is accepted
|
||||
if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game))
|
||||
{
|
||||
await game.StartGame();
|
||||
}
|
||||
if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game)) await game.StartGame();
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RollDuel(ShmartNumber amount, IUser u)
|
||||
{
|
||||
@@ -370,9 +414,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.roll_duel));
|
||||
var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.roll_duel));
|
||||
|
||||
var description = string.Empty;
|
||||
|
||||
@@ -381,22 +423,18 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
if (_service.Duels.TryGetValue((ctx.User.Id, u.Id), out var other))
|
||||
{
|
||||
if (other.Amount != amount)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.roll_duel_already_challenged);
|
||||
}
|
||||
else
|
||||
{
|
||||
await RollDuel(u);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_service.Duels.TryAdd((u.Id, ctx.User.Id), game))
|
||||
{
|
||||
game.OnGameTick += Game_OnGameTick;
|
||||
game.OnEnded += Game_OnEnded;
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.roll_duel_challenge(
|
||||
Format.Bold(ctx.User.ToString()),
|
||||
await ReplyConfirmLocalizedAsync(strs.roll_duel_challenge(Format.Bold(ctx.User.ToString()),
|
||||
Format.Bold(u.ToString()),
|
||||
Format.Bold(n(amount))));
|
||||
}
|
||||
@@ -411,16 +449,12 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
embed = embed.WithDescription(description);
|
||||
|
||||
if (rdMsg is null)
|
||||
{
|
||||
rdMsg = await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
else
|
||||
{
|
||||
await rdMsg.ModifyAsync(x =>
|
||||
{
|
||||
x.Embed = embed.Build();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async Task Game_OnEnded(RollDuelGame rdGame, RollDuelGame.Reason reason)
|
||||
@@ -429,13 +463,11 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
{
|
||||
if (reason == RollDuelGame.Reason.Normal)
|
||||
{
|
||||
var winner = rdGame.Winner == rdGame.P1
|
||||
? ctx.User
|
||||
: u;
|
||||
var winner = rdGame.Winner == rdGame.P1 ? ctx.User : u;
|
||||
description += $"\n**{winner}** Won {n((long)(rdGame.Amount * 2 * 0.98))}";
|
||||
|
||||
embed = embed.WithDescription(description);
|
||||
|
||||
|
||||
await rdMsg.ModifyAsync(x => x.Embed = embed.Build());
|
||||
}
|
||||
else if (reason == RollDuelGame.Reason.Timeout)
|
||||
@@ -459,13 +491,13 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
if (!await CheckBetMandatory(amount))
|
||||
return;
|
||||
|
||||
if (!await _cs.RemoveAsync(ctx.User, "Betroll Gamble", amount, false, gamble: true))
|
||||
if (!await _cs.RemoveAsync(ctx.User, "Betroll Gamble", amount, false, true))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
var br = new Betroll(base._config.BetRoll);
|
||||
var br = new Betroll(_config.BetRoll);
|
||||
|
||||
var result = br.Roll();
|
||||
|
||||
@@ -474,31 +506,31 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
if (result.Multiplier > 0)
|
||||
{
|
||||
var win = (long)(amount * result.Multiplier);
|
||||
str += GetText(strs.br_win(
|
||||
n(win),
|
||||
result.Threshold + (result.Roll == 100 ? " 👑" : "")));
|
||||
await _cs.AddAsync(ctx.User, "Betroll Gamble",
|
||||
win, false, gamble: true);
|
||||
str += GetText(strs.br_win(n(win), result.Threshold + (result.Roll == 100 ? " 👑" : "")));
|
||||
await _cs.AddAsync(ctx.User, "Betroll Gamble", win, false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
str += GetText(strs.better_luck);
|
||||
}
|
||||
|
||||
|
||||
await SendConfirmAsync(str);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public Task BetRoll(ShmartNumber amount)
|
||||
=> InternallBetroll(amount);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[NadekoOptions(typeof(LbOpts))]
|
||||
[Priority(0)]
|
||||
public Task Leaderboard(params string[] args)
|
||||
=> Leaderboard(1, args);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[NadekoOptions(typeof(LbOpts))]
|
||||
[Priority(1)]
|
||||
public async Task Leaderboard(int page = 1, params string[] args)
|
||||
@@ -510,10 +542,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
var cleanRichest = new List<DiscordUser>();
|
||||
// it's pointless to have clean on dm context
|
||||
if (ctx.Guild is null)
|
||||
{
|
||||
opts.Clean = false;
|
||||
}
|
||||
if (ctx.Guild is null) opts.Clean = false;
|
||||
|
||||
if (opts.Clean)
|
||||
{
|
||||
@@ -523,13 +552,12 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
{
|
||||
cleanRichest = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 10_000);
|
||||
}
|
||||
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
|
||||
|
||||
var sg = (SocketGuild)ctx.Guild;
|
||||
cleanRichest = cleanRichest.Where(x => sg.GetUser(x.UserId) != null)
|
||||
.ToList();
|
||||
cleanRichest = cleanRichest.Where(x => sg.GetUser(x.UserId) != null).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -537,62 +565,46 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
cleanRichest = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, page).ToList();
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(CurrencySign + " " + GetText(strs.leaderboard));
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
var embed = _eb.Create().WithOkColor().WithTitle(CurrencySign + " " + GetText(strs.leaderboard));
|
||||
|
||||
List<DiscordUser> toSend;
|
||||
if (!opts.Clean)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
toSend = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, curPage);
|
||||
}
|
||||
else
|
||||
{
|
||||
toSend = cleanRichest.Skip(curPage * 9).Take(9).ToList();
|
||||
}
|
||||
|
||||
if (!toSend.Any())
|
||||
{
|
||||
embed.WithDescription(GetText(strs.no_user_on_this_page));
|
||||
return embed;
|
||||
}
|
||||
|
||||
for (var i = 0; i < toSend.Count; i++)
|
||||
{
|
||||
var x = toSend[i];
|
||||
var usrStr = x.ToString().TrimTo(20, true);
|
||||
|
||||
var j = i;
|
||||
embed.AddField("#" + ((9 * curPage) + j + 1) + " " + usrStr, n(x.CurrencyAmount), true);
|
||||
}
|
||||
|
||||
List<DiscordUser> toSend;
|
||||
if (!opts.Clean)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
toSend = uow.DiscordUser.GetTopRichest(_client.CurrentUser.Id, 9, curPage);
|
||||
}
|
||||
else
|
||||
{
|
||||
toSend = cleanRichest.Skip(curPage * 9).Take(9).ToList();
|
||||
}
|
||||
if (!toSend.Any())
|
||||
{
|
||||
embed.WithDescription(GetText(strs.no_user_on_this_page));
|
||||
return embed;
|
||||
}
|
||||
|
||||
for (var i = 0; i < toSend.Count; i++)
|
||||
{
|
||||
var x = toSend[i];
|
||||
var usrStr = x.ToString().TrimTo(20, true);
|
||||
|
||||
var j = i;
|
||||
embed.AddField("#" + ((9 * curPage) + j + 1) + " " + usrStr, n(x.CurrencyAmount), true);
|
||||
}
|
||||
|
||||
return embed;
|
||||
}, opts.Clean ? cleanRichest.Count() : 9000, 9, opts.Clean);
|
||||
},
|
||||
opts.Clean ? cleanRichest.Count() : 9000,
|
||||
9,
|
||||
opts.Clean);
|
||||
}
|
||||
|
||||
|
||||
public enum RpsPick
|
||||
{
|
||||
R = 0,
|
||||
Rock = 0,
|
||||
Rocket = 0,
|
||||
P = 1,
|
||||
Paper = 1,
|
||||
Paperclip = 1,
|
||||
S = 2,
|
||||
Scissors = 2
|
||||
}
|
||||
|
||||
public enum RpsResult
|
||||
{
|
||||
Win,
|
||||
Loss,
|
||||
Draw,
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Rps(RpsPick pick, ShmartNumber amount = default)
|
||||
{
|
||||
long oldAmount = amount;
|
||||
@@ -611,35 +623,31 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return "✂️";
|
||||
}
|
||||
}
|
||||
|
||||
var embed = _eb.Create();
|
||||
|
||||
var nadekoPick = (RpsPick)new NadekoRandom().Next(0, 3);
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id,
|
||||
"Rps-bet", amount, gamble: true))
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id, "Rps-bet", amount, true))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string msg;
|
||||
if (pick == nadekoPick)
|
||||
{
|
||||
await _cs.AddAsync(ctx.User.Id,
|
||||
"Rps-draw", amount, gamble: true);
|
||||
await _cs.AddAsync(ctx.User.Id, "Rps-draw", amount, true);
|
||||
embed.WithOkColor();
|
||||
msg = GetText(strs.rps_draw(getRpsPick(pick)));
|
||||
}
|
||||
else if ((pick == RpsPick.Paper && nadekoPick == RpsPick.Rock) ||
|
||||
(pick == RpsPick.Rock && nadekoPick == RpsPick.Scissors) ||
|
||||
(pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper))
|
||||
else if ((pick == RpsPick.Paper && nadekoPick == RpsPick.Rock)
|
||||
|| (pick == RpsPick.Rock && nadekoPick == RpsPick.Scissors)
|
||||
|| (pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper))
|
||||
{
|
||||
amount = (long)(amount * base._config.BetFlip.Multiplier);
|
||||
await _cs.AddAsync(ctx.User.Id,
|
||||
"Rps-win", amount, gamble: true);
|
||||
amount = (long)(amount * _config.BetFlip.Multiplier);
|
||||
await _cs.AddAsync(ctx.User.Id, "Rps-win", amount, true);
|
||||
embed.WithOkColor();
|
||||
embed.AddField(GetText(strs.won), n(amount));
|
||||
msg = GetText(strs.rps_win(ctx.User.Mention, getRpsPick(pick), getRpsPick(nadekoPick)));
|
||||
@@ -651,9 +659,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
msg = GetText(strs.rps_win(ctx.Client.CurrentUser.Mention, getRpsPick(nadekoPick), getRpsPick(pick)));
|
||||
}
|
||||
|
||||
embed
|
||||
.WithDescription(msg);
|
||||
embed.WithDescription(msg);
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
@@ -12,17 +12,16 @@ public partial class Gambling
|
||||
{
|
||||
private readonly ILogCommandService logService;
|
||||
|
||||
public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss) : base(gss)
|
||||
public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss)
|
||||
: base(gss)
|
||||
=> this.logService = logService;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Pick(string pass = null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric()) return;
|
||||
|
||||
var picked = await _service.PickAsync(ctx.Guild.Id, (ITextChannel)ctx.Channel, ctx.User.Id, pass);
|
||||
|
||||
@@ -33,27 +32,23 @@ public partial class Gambling
|
||||
}
|
||||
|
||||
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
|
||||
{
|
||||
try
|
||||
{
|
||||
logService.AddDeleteIgnore(ctx.Message.Id);
|
||||
await ctx.Message.DeleteAsync();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Plant(ShmartNumber amount, string pass = null)
|
||||
{
|
||||
if (amount < 1)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric()) return;
|
||||
|
||||
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
|
||||
{
|
||||
@@ -61,14 +56,17 @@ public partial class Gambling
|
||||
await ctx.Message.DeleteAsync();
|
||||
}
|
||||
|
||||
var success = await _service.PlantAsync(ctx.Guild.Id, ctx.Channel, ctx.User.Id, ctx.User.ToString(), amount, pass);
|
||||
if (!success)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough( CurrencySign));
|
||||
}
|
||||
var success = await _service.PlantAsync(ctx.Guild.Id,
|
||||
ctx.Channel,
|
||||
ctx.User.Id,
|
||||
ctx.User.ToString(),
|
||||
amount,
|
||||
pass);
|
||||
if (!success) await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
#if GLOBAL_NADEKO
|
||||
@@ -78,16 +76,13 @@ public partial class Gambling
|
||||
{
|
||||
var enabled = _service.ToggleCurrencyGeneration(ctx.Guild.Id, ctx.Channel.Id);
|
||||
if (enabled)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.curgen_enabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.curgen_disabled);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[OwnerOnly]
|
||||
@@ -97,19 +92,19 @@ public partial class Gambling
|
||||
return Task.CompletedTask;
|
||||
var enabledIn = _service.GetAllGeneratingChannels();
|
||||
|
||||
return ctx.SendPaginatedConfirmAsync(page, cur =>
|
||||
{
|
||||
var items = enabledIn.Skip(page * 9).Take(9);
|
||||
|
||||
if (!items.Any())
|
||||
return ctx.SendPaginatedConfirmAsync(page,
|
||||
cur =>
|
||||
{
|
||||
return _eb.Create().WithErrorColor()
|
||||
.WithDescription("-");
|
||||
}
|
||||
var items = enabledIn.Skip(page * 9).Take(9);
|
||||
|
||||
return items.Aggregate(_eb.Create().WithOkColor(),
|
||||
(eb, i) => eb.AddField(i.GuildId.ToString(), i.ChannelId));
|
||||
}, enabledIn.Count(), 9);
|
||||
if (!items.Any())
|
||||
return _eb.Create().WithErrorColor().WithDescription("-");
|
||||
|
||||
return items.Aggregate(_eb.Create().WithOkColor(),
|
||||
(eb, i) => eb.AddField(i.GuildId.ToString(), i.ChannelId));
|
||||
},
|
||||
enabledIn.Count(),
|
||||
9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,4 @@ namespace NadekoBot.Modules.Gambling.Services;
|
||||
public class AnimalRaceService : INService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new();
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,4 @@ namespace NadekoBot.Modules.Gambling.Services;
|
||||
public class BlackJackService : INService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, Blackjack> Games { get; } = new();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Common.Events;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Events;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
@@ -14,18 +14,19 @@ public class CurrencyEventsService : INService
|
||||
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events = new();
|
||||
|
||||
|
||||
public CurrencyEventsService(
|
||||
DiscordSocketClient client,
|
||||
ICurrencyService cs,
|
||||
GamblingConfigService configService)
|
||||
public CurrencyEventsService(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService configService)
|
||||
{
|
||||
_client = client;
|
||||
_cs = cs;
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
public async Task<bool> TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type,
|
||||
EventOptions opts, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embed)
|
||||
public async Task<bool> TryCreateEventAsync(
|
||||
ulong guildId,
|
||||
ulong channelId,
|
||||
CurrencyEvent.Type type,
|
||||
EventOptions opts,
|
||||
Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embed)
|
||||
{
|
||||
var g = _client.GetGuild(guildId);
|
||||
if (g?.GetChannel(channelId) is not SocketTextChannel ch)
|
||||
@@ -34,21 +35,14 @@ public class CurrencyEventsService : INService
|
||||
ICurrencyEvent ce;
|
||||
|
||||
if (type == CurrencyEvent.Type.Reaction)
|
||||
{
|
||||
ce = new ReactionEvent(_client, _cs, g, ch, opts, _configService.Data, embed);
|
||||
}
|
||||
else if (type == CurrencyEvent.Type.GameStatus)
|
||||
{
|
||||
ce = new GameStatusEvent(_client, _cs, g, ch, opts, embed);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var added = _events.TryAdd(guildId, ce);
|
||||
if (added)
|
||||
{
|
||||
try
|
||||
{
|
||||
ce.OnEnded += OnEventEnded;
|
||||
@@ -60,7 +54,6 @@ public class CurrencyEventsService : INService
|
||||
_events.TryRemove(guildId, out ce);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
@@ -70,4 +63,4 @@ public class CurrencyEventsService : INService
|
||||
_events.TryRemove(gid, out _);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,24 @@ public class CurrencyRaffleService : INService
|
||||
NotEnoughCurrency,
|
||||
AlreadyJoinedOrInvalidAmount
|
||||
}
|
||||
|
||||
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new();
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new();
|
||||
|
||||
public CurrencyRaffleService(DbService db, ICurrencyService cs)
|
||||
{
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame(ulong channelId, IUser user, long amount, bool mixed, Func<IUser, long, Task> onEnded)
|
||||
public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame(
|
||||
ulong channelId,
|
||||
IUser user,
|
||||
long amount,
|
||||
bool mixed,
|
||||
Func<IUser, long, Task> onEnded)
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
@@ -31,9 +36,7 @@ public class CurrencyRaffleService : INService
|
||||
if (!Games.TryGetValue(channelId, out var crg))
|
||||
{
|
||||
newGame = true;
|
||||
crg = new(mixed
|
||||
? CurrencyRaffleGame.Type.Mixed
|
||||
: CurrencyRaffleGame.Type.Normal);
|
||||
crg = new(mixed ? CurrencyRaffleGame.Type.Mixed : CurrencyRaffleGame.Type.Normal);
|
||||
Games.Add(channelId, crg);
|
||||
}
|
||||
|
||||
@@ -51,6 +54,7 @@ public class CurrencyRaffleService : INService
|
||||
await _cs.AddAsync(user.Id, "Curency Raffle Refund", amount);
|
||||
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
|
||||
}
|
||||
|
||||
if (newGame)
|
||||
{
|
||||
var _t = Task.Run(async () =>
|
||||
@@ -62,8 +66,7 @@ public class CurrencyRaffleService : INService
|
||||
var winner = crg.GetWinner();
|
||||
var won = crg.Users.Sum(x => x.Amount);
|
||||
|
||||
await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win",
|
||||
won);
|
||||
await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win", won);
|
||||
Games.Remove(channelId, out _);
|
||||
var oe = onEnded(winner.DiscordUser, won);
|
||||
}
|
||||
@@ -71,6 +74,7 @@ public class CurrencyRaffleService : INService
|
||||
finally { _locker.Release(); }
|
||||
});
|
||||
}
|
||||
|
||||
return (crg, null);
|
||||
}
|
||||
finally
|
||||
@@ -78,4 +82,4 @@ public class CurrencyRaffleService : INService
|
||||
_locker.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,74 +6,119 @@ namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
||||
{
|
||||
public override string Name { get; } = "gambling";
|
||||
private const string FilePath = "data/gambling.yml";
|
||||
private static readonly TypedKey<GamblingConfig> changeKey = new("config.gambling.updated");
|
||||
public override string Name { get; } = "gambling";
|
||||
|
||||
private readonly IEnumerable<WaifuItemModel> antiGiftSeed = new[]
|
||||
{
|
||||
new WaifuItemModel("🥀", 100, "WiltedRose", true), new WaifuItemModel("✂️", 1000, "Haircut", true),
|
||||
new WaifuItemModel("🧻", 10000, "ToiletPaper", true)
|
||||
};
|
||||
|
||||
|
||||
|
||||
public GamblingConfigService(IConfigSeria serializer, IPubSub pubSub)
|
||||
: base(FilePath, serializer, pubSub, changeKey)
|
||||
{
|
||||
AddParsedProp("currency.name", gs => gs.Currency.Name, ConfigParsers.String, ConfigPrinters.ToString);
|
||||
AddParsedProp("currency.sign", gs => gs.Currency.Sign, ConfigParsers.String, ConfigPrinters.ToString);
|
||||
|
||||
|
||||
AddParsedProp("minbet", gs => gs.MinBet, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("maxbet", gs => gs.MaxBet, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
|
||||
|
||||
AddParsedProp("gen.min", gs => gs.Generation.MinAmount, int.TryParse, ConfigPrinters.ToString, val => val >= 1);
|
||||
AddParsedProp("gen.max", gs => gs.Generation.MaxAmount, int.TryParse, ConfigPrinters.ToString, val => val >= 1);
|
||||
AddParsedProp("gen.cd", gs => gs.Generation.GenCooldown, int.TryParse, ConfigPrinters.ToString, val => val > 0);
|
||||
AddParsedProp("gen.chance", gs => gs.Generation.Chance, decimal.TryParse, ConfigPrinters.ToString, val => val is >= 0 and <= 1);
|
||||
AddParsedProp("gen.chance",
|
||||
gs => gs.Generation.Chance,
|
||||
decimal.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val is >= 0 and <= 1);
|
||||
AddParsedProp("gen.has_pw", gs => gs.Generation.HasPassword, bool.TryParse, ConfigPrinters.ToString);
|
||||
AddParsedProp("bf.multi", gs => gs.BetFlip.Multiplier, decimal.TryParse, ConfigPrinters.ToString, val => val >= 1);
|
||||
AddParsedProp("waifu.min_price", gs => gs.Waifu.MinPrice, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("waifu.multi.reset", gs => gs.Waifu.Multipliers.WaifuReset, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("waifu.multi.crush_claim", gs => gs.Waifu.Multipliers.CrushClaim, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("waifu.multi.normal_claim", gs => gs.Waifu.Multipliers.NormalClaim, decimal.TryParse, ConfigPrinters.ToString, val => val > 0);
|
||||
AddParsedProp("waifu.multi.divorce_value", gs => gs.Waifu.Multipliers.DivorceNewValue, decimal.TryParse, ConfigPrinters.ToString, val => val > 0);
|
||||
AddParsedProp("waifu.multi.all_gifts", gs => gs.Waifu.Multipliers.AllGiftPrices, decimal.TryParse, ConfigPrinters.ToString, val => val > 0);
|
||||
AddParsedProp("waifu.multi.gift_effect", gs => gs.Waifu.Multipliers.GiftEffect, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("waifu.multi.negative_gift_effect", gs => gs.Waifu.Multipliers.NegativeGiftEffect, decimal.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("decay.percent", gs => gs.Decay.Percent, decimal.TryParse, ConfigPrinters.ToString, val => val is >= 0 and <= 1);
|
||||
AddParsedProp("decay.maxdecay", gs => gs.Decay.MaxDecay, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("decay.threshold", gs => gs.Decay.MinThreshold, int.TryParse, ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("bf.multi",
|
||||
gs => gs.BetFlip.Multiplier,
|
||||
decimal.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val >= 1);
|
||||
AddParsedProp("waifu.min_price",
|
||||
gs => gs.Waifu.MinPrice,
|
||||
int.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val >= 0);
|
||||
AddParsedProp("waifu.multi.reset",
|
||||
gs => gs.Waifu.Multipliers.WaifuReset,
|
||||
int.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val >= 0);
|
||||
AddParsedProp("waifu.multi.crush_claim",
|
||||
gs => gs.Waifu.Multipliers.CrushClaim,
|
||||
decimal.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val >= 0);
|
||||
AddParsedProp("waifu.multi.normal_claim",
|
||||
gs => gs.Waifu.Multipliers.NormalClaim,
|
||||
decimal.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val > 0);
|
||||
AddParsedProp("waifu.multi.divorce_value",
|
||||
gs => gs.Waifu.Multipliers.DivorceNewValue,
|
||||
decimal.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val > 0);
|
||||
AddParsedProp("waifu.multi.all_gifts",
|
||||
gs => gs.Waifu.Multipliers.AllGiftPrices,
|
||||
decimal.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val > 0);
|
||||
AddParsedProp("waifu.multi.gift_effect",
|
||||
gs => gs.Waifu.Multipliers.GiftEffect,
|
||||
decimal.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val >= 0);
|
||||
AddParsedProp("waifu.multi.negative_gift_effect",
|
||||
gs => gs.Waifu.Multipliers.NegativeGiftEffect,
|
||||
decimal.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val >= 0);
|
||||
AddParsedProp("decay.percent",
|
||||
gs => gs.Decay.Percent,
|
||||
decimal.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val is >= 0 and <= 1);
|
||||
AddParsedProp("decay.maxdecay",
|
||||
gs => gs.Decay.MaxDecay,
|
||||
int.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val >= 0);
|
||||
AddParsedProp("decay.threshold",
|
||||
gs => gs.Decay.MinThreshold,
|
||||
int.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val >= 0);
|
||||
|
||||
Migrate();
|
||||
}
|
||||
|
||||
private readonly IEnumerable<WaifuItemModel> antiGiftSeed = new[]
|
||||
{
|
||||
new WaifuItemModel("🥀", 100, "WiltedRose", true),
|
||||
new WaifuItemModel("✂️", 1000, "Haircut", true),
|
||||
new WaifuItemModel("🧻", 10000, "ToiletPaper", true),
|
||||
};
|
||||
|
||||
public void Migrate()
|
||||
{
|
||||
if (data.Version < 2)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Waifu.Items = c.Waifu.Items.Concat(antiGiftSeed).ToList();
|
||||
c.Version = 2;
|
||||
});
|
||||
}
|
||||
|
||||
if (data.Version < 3)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 3;
|
||||
c.VoteReward = 100;
|
||||
});
|
||||
}
|
||||
|
||||
if (data.Version < 4)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 4;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Connect4;
|
||||
using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Connect4;
|
||||
using NadekoBot.Modules.Gambling.Common.Slot;
|
||||
using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class GamblingService : INService
|
||||
{
|
||||
public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new();
|
||||
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new();
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly Bot _bot;
|
||||
@@ -18,13 +20,15 @@ public class GamblingService : INService
|
||||
private readonly IDataCache _cache;
|
||||
private readonly GamblingConfigService _gss;
|
||||
|
||||
public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new();
|
||||
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new();
|
||||
|
||||
private readonly Timer _decayTimer;
|
||||
|
||||
public GamblingService(DbService db, Bot bot, ICurrencyService cs,
|
||||
DiscordSocketClient client, IDataCache cache, GamblingConfigService gss)
|
||||
public GamblingService(
|
||||
DbService db,
|
||||
Bot bot,
|
||||
ICurrencyService cs,
|
||||
DiscordSocketClient client,
|
||||
IDataCache cache,
|
||||
GamblingConfigService gss)
|
||||
{
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
@@ -32,30 +36,29 @@ public class GamblingService : INService
|
||||
_client = client;
|
||||
_cache = cache;
|
||||
_gss = gss;
|
||||
|
||||
if (_bot.Client.ShardId == 0)
|
||||
{
|
||||
_decayTimer = new(_ =>
|
||||
{
|
||||
var config = _gss.Data;
|
||||
var maxDecay = config.Decay.MaxDecay;
|
||||
if (config.Decay.Percent is <= 0 or > 1 || maxDecay < 0)
|
||||
return;
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
|
||||
|
||||
if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval))
|
||||
return;
|
||||
|
||||
Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% " +
|
||||
$"| max: {maxDecay} " +
|
||||
$"| threshold: {config.Decay.MinThreshold}");
|
||||
|
||||
if (maxDecay == 0)
|
||||
maxDecay = int.MaxValue;
|
||||
|
||||
uow.Database.ExecuteSqlInterpolated($@"
|
||||
if (_bot.Client.ShardId == 0)
|
||||
_decayTimer = new(_ =>
|
||||
{
|
||||
var config = _gss.Data;
|
||||
var maxDecay = config.Decay.MaxDecay;
|
||||
if (config.Decay.Percent is <= 0 or > 1 || maxDecay < 0)
|
||||
return;
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
|
||||
|
||||
if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval))
|
||||
return;
|
||||
|
||||
Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% "
|
||||
+ $"| max: {maxDecay} "
|
||||
+ $"| threshold: {config.Decay.MinThreshold}");
|
||||
|
||||
if (maxDecay == 0)
|
||||
maxDecay = int.MaxValue;
|
||||
|
||||
uow.Database.ExecuteSqlInterpolated($@"
|
||||
UPDATE DiscordUser
|
||||
SET CurrencyAmount=
|
||||
CASE WHEN
|
||||
@@ -67,23 +70,20 @@ SET CurrencyAmount=
|
||||
END
|
||||
WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentUser.Id};");
|
||||
|
||||
_cache.SetLastCurrencyDecay();
|
||||
uow.SaveChanges();
|
||||
}, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
_cache.SetLastCurrencyDecay();
|
||||
uow.SaveChanges();
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromMinutes(5),
|
||||
TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
|
||||
public async Task<SlotResponse> SlotAsync(ulong userId, long amount)
|
||||
{
|
||||
var takeRes = await _cs.RemoveAsync(userId, "Slot Machine", amount, true);
|
||||
|
||||
|
||||
if (!takeRes)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Error = GamblingError.NotEnough
|
||||
};
|
||||
}
|
||||
return new() { Error = GamblingError.NotEnough };
|
||||
|
||||
var game = new SlotGame();
|
||||
var result = game.Spin();
|
||||
@@ -96,37 +96,21 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
|
||||
await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true);
|
||||
}
|
||||
|
||||
var toReturn = new SlotResponse
|
||||
{
|
||||
Multiplier = result.Multiplier,
|
||||
Won = won,
|
||||
};
|
||||
var toReturn = new SlotResponse { Multiplier = result.Multiplier, Won = won };
|
||||
|
||||
toReturn.Rolls.AddRange(result.Rolls);
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
|
||||
public struct EconomyResult
|
||||
{
|
||||
public decimal Cash { get; set; }
|
||||
public decimal Planted { get; set; }
|
||||
public decimal Waifus { get; set; }
|
||||
public decimal OnePercent { get; set; }
|
||||
public long Bot { get; set; }
|
||||
}
|
||||
|
||||
public EconomyResult GetEconomy()
|
||||
{
|
||||
if (_cache.TryGetEconomy(out var data))
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<EconomyResult>(data);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
decimal cash;
|
||||
decimal onePercent;
|
||||
@@ -149,7 +133,7 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
|
||||
Planted = planted,
|
||||
Bot = bot,
|
||||
Waifus = waifus,
|
||||
OnePercent = onePercent,
|
||||
OnePercent = onePercent
|
||||
};
|
||||
|
||||
_cache.SetEconomy(JsonConvert.SerializeObject(result));
|
||||
@@ -158,4 +142,14 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
|
||||
|
||||
public Task<WheelOfFortuneGame.Result> WheelOfFortuneSpinAsync(ulong userId, long bet)
|
||||
=> new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync();
|
||||
}
|
||||
|
||||
|
||||
public struct EconomyResult
|
||||
{
|
||||
public decimal Cash { get; set; }
|
||||
public decimal Planted { get; set; }
|
||||
public decimal Waifus { get; set; }
|
||||
public decimal OnePercent { get; set; }
|
||||
public long Bot { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace NadekoBot.Modules.Gambling.Services;
|
||||
public interface IShopService
|
||||
{
|
||||
/// <summary>
|
||||
/// Changes the price of a shop item
|
||||
/// Changes the price of a shop item
|
||||
/// </summary>
|
||||
/// <param name="guildId">Id of the guild in which the shop is</param>
|
||||
/// <param name="index">Index of the item</param>
|
||||
@@ -13,7 +13,7 @@ public interface IShopService
|
||||
Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the name of a shop item
|
||||
/// Changes the name of a shop item
|
||||
/// </summary>
|
||||
/// <param name="guildId">Id of the guild in which the shop is</param>
|
||||
/// <param name="index">Index of the item</param>
|
||||
@@ -22,7 +22,7 @@ public interface IShopService
|
||||
Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName);
|
||||
|
||||
/// <summary>
|
||||
/// Swaps indexes of 2 items in the shop
|
||||
/// Swaps indexes of 2 items in the shop
|
||||
/// </summary>
|
||||
/// <param name="guildId">Id of the guild in which the shop is</param>
|
||||
/// <param name="index1">First entry's index</param>
|
||||
@@ -31,11 +31,11 @@ public interface IShopService
|
||||
Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2);
|
||||
|
||||
/// <summary>
|
||||
/// Swaps indexes of 2 items in the shop
|
||||
/// Swaps indexes of 2 items in the shop
|
||||
/// </summary>
|
||||
/// <param name="guildId">Id of the guild in which the shop is</param>
|
||||
/// <param name="fromIndex">Current index of the entry to move</param>
|
||||
/// <param name="toIndex">Destination index of the entry</param>
|
||||
/// <returns>Whether swap was successful</returns>
|
||||
Task<bool> MoveEntryAsync(ulong guildId, int fromIndex, int toIndex);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
@@ -14,13 +14,9 @@ public class ShopService : IShopService, INService
|
||||
public ShopService(DbService db)
|
||||
=> _db = db;
|
||||
|
||||
private IndexedCollection<ShopEntry> GetEntriesInternal(NadekoContext uow, ulong guildId) =>
|
||||
uow.GuildConfigsForId(
|
||||
guildId,
|
||||
set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items)
|
||||
)
|
||||
.ShopEntries
|
||||
.ToIndexed();
|
||||
private IndexedCollection<ShopEntry> GetEntriesInternal(NadekoContext uow, ulong guildId)
|
||||
=> uow.GuildConfigsForId(guildId, set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items))
|
||||
.ShopEntries.ToIndexed();
|
||||
|
||||
public async Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice)
|
||||
{
|
||||
@@ -98,4 +94,4 @@ public class ShopService : IShopService, INService
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using NadekoBot.Db;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class PlantPickService : INService
|
||||
{
|
||||
//channelId/last generation
|
||||
public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new();
|
||||
private readonly DbService _db;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly IImageCache _images;
|
||||
@@ -25,13 +27,18 @@ public class PlantPickService : INService
|
||||
private readonly GamblingConfigService _gss;
|
||||
|
||||
public readonly ConcurrentHashSet<ulong> _generationChannels = new();
|
||||
//channelId/last generation
|
||||
public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new();
|
||||
private readonly SemaphoreSlim pickLock = new(1, 1);
|
||||
|
||||
public PlantPickService(DbService db, CommandHandler cmd, IBotStrings strings,
|
||||
IDataCache cache, FontProvider fonts, ICurrencyService cs,
|
||||
CommandHandler cmdHandler, DiscordSocketClient client, GamblingConfigService gss)
|
||||
public PlantPickService(
|
||||
DbService db,
|
||||
CommandHandler cmd,
|
||||
IBotStrings strings,
|
||||
IDataCache cache,
|
||||
FontProvider fonts,
|
||||
ICurrencyService cs,
|
||||
CommandHandler cmdHandler,
|
||||
DiscordSocketClient client,
|
||||
GamblingConfigService gss)
|
||||
{
|
||||
_db = db;
|
||||
_strings = strings;
|
||||
@@ -47,13 +54,12 @@ public class PlantPickService : INService
|
||||
using var uow = db.GetDbContext();
|
||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||
var configs = uow.Set<GuildConfig>()
|
||||
.AsQueryable()
|
||||
.Include(x => x.GenerateCurrencyChannelIds)
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.ToList();
|
||||
|
||||
_generationChannels = new(configs
|
||||
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
|
||||
.AsQueryable()
|
||||
.Include(x => x.GenerateCurrencyChannelIds)
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.ToList();
|
||||
|
||||
_generationChannels = new(configs.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
|
||||
}
|
||||
|
||||
private string GetText(ulong gid, LocStr str)
|
||||
@@ -65,7 +71,7 @@ public class PlantPickService : INService
|
||||
using var uow = _db.GetDbContext();
|
||||
var guildConfig = uow.GuildConfigsForId(gid, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
|
||||
|
||||
var toAdd = new GCChannelId() { ChannelId = cid };
|
||||
var toAdd = new GCChannelId { ChannelId = cid };
|
||||
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
|
||||
{
|
||||
guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
|
||||
@@ -75,13 +81,11 @@ public class PlantPickService : INService
|
||||
else
|
||||
{
|
||||
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
|
||||
if (toDelete != null)
|
||||
{
|
||||
uow.Remove(toDelete);
|
||||
}
|
||||
if (toDelete != null) uow.Remove(toDelete);
|
||||
_generationChannels.TryRemove(cid);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
uow.SaveChanges();
|
||||
return enabled;
|
||||
}
|
||||
@@ -94,7 +98,7 @@ public class PlantPickService : INService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a random currency image stream, with an optional password sticked onto it.
|
||||
/// Get a random currency image stream, with an optional password sticked onto it.
|
||||
/// </summary>
|
||||
/// <param name="pass">Optional password to add to top left corner.</param>
|
||||
/// <returns>Stream of the currency image</returns>
|
||||
@@ -111,6 +115,7 @@ public class PlantPickService : INService
|
||||
{
|
||||
extension = format.FileExtensions.FirstOrDefault() ?? "png";
|
||||
}
|
||||
|
||||
// return the image
|
||||
return curImg.ToStream();
|
||||
}
|
||||
@@ -124,7 +129,7 @@ public class PlantPickService : INService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a password to the image.
|
||||
/// Add a password to the image.
|
||||
/// </summary>
|
||||
/// <param name="curImg">Image to add password to.</param>
|
||||
/// <param name="pass">Password to add to top left corner.</param>
|
||||
@@ -149,10 +154,7 @@ public class PlantPickService : INService
|
||||
new PointF(0, size.Height + 10));
|
||||
|
||||
// draw the password over the background
|
||||
x.DrawText(pass,
|
||||
font,
|
||||
SixLabors.ImageSharp.Color.White,
|
||||
new(0, 0));
|
||||
x.DrawText(pass, font, Color.White, new(0, 0));
|
||||
});
|
||||
// return image as a stream for easy sending
|
||||
return (img.ToStream(format), format.FileExtensions.FirstOrDefault() ?? "png");
|
||||
@@ -177,7 +179,8 @@ public class PlantPickService : INService
|
||||
var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue);
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
if (DateTime.UtcNow - TimeSpan.FromSeconds(config.Generation.GenCooldown) < lastGeneration) //recently generated in this channel, don't generate again
|
||||
if (DateTime.UtcNow - TimeSpan.FromSeconds(config.Generation.GenCooldown)
|
||||
< lastGeneration) //recently generated in this channel, don't generate again
|
||||
return;
|
||||
|
||||
var num = rng.Next(1, 101) + (config.Generation.Chance * 100);
|
||||
@@ -194,9 +197,11 @@ public class PlantPickService : INService
|
||||
var prefix = _cmdHandler.GetPrefix(channel.Guild.Id);
|
||||
var toSend = dropAmount == 1
|
||||
? GetText(channel.GuildId, strs.curgen_sn(config.Currency.Sign))
|
||||
+ " " + GetText(channel.GuildId, strs.pick_sn(prefix))
|
||||
+ " "
|
||||
+ GetText(channel.GuildId, strs.pick_sn(prefix))
|
||||
: GetText(channel.GuildId, strs.curgen_pl(dropAmount, config.Currency.Sign))
|
||||
+ " " + GetText(channel.GuildId, strs.pick_pl(prefix));
|
||||
+ " "
|
||||
+ GetText(channel.GuildId, strs.pick_pl(prefix));
|
||||
|
||||
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
|
||||
|
||||
@@ -223,7 +228,7 @@ public class PlantPickService : INService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a hexadecimal string from 1000 to ffff.
|
||||
/// Generate a hexadecimal string from 1000 to ffff.
|
||||
/// </summary>
|
||||
/// <returns>A hexadecimal string from 1000 to ffff</returns>
|
||||
private string GenerateCurrencyPassword()
|
||||
@@ -234,7 +239,11 @@ public class PlantPickService : INService
|
||||
return num.ToString("x4");
|
||||
}
|
||||
|
||||
public async Task<long> PickAsync(ulong gid, ITextChannel ch, ulong uid, string pass)
|
||||
public async Task<long> PickAsync(
|
||||
ulong gid,
|
||||
ITextChannel ch,
|
||||
ulong uid,
|
||||
string pass)
|
||||
{
|
||||
await pickLock.WaitAsync();
|
||||
try
|
||||
@@ -246,12 +255,11 @@ public class PlantPickService : INService
|
||||
// this method will sum all plants with that password,
|
||||
// remove them, and get messageids of the removed plants
|
||||
|
||||
pass = pass?.Trim().TrimTo(10, hideDots: true).ToUpperInvariant();
|
||||
pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant();
|
||||
// gets all plants in this channel with the same password
|
||||
var entries = uow.PlantedCurrency
|
||||
.AsQueryable()
|
||||
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
||||
.ToList();
|
||||
var entries = uow.PlantedCurrency.AsQueryable()
|
||||
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
||||
.ToList();
|
||||
// sum how much currency that is, and get all of the message ids (so that i can delete them)
|
||||
amount = entries.Sum(x => x.Amount);
|
||||
ids = entries.Select(x => x.MessageId).ToArray();
|
||||
@@ -260,10 +268,8 @@ public class PlantPickService : INService
|
||||
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
// give the picked currency to the user
|
||||
await _cs.AddAsync(uid, "Picked currency", amount, gamble: false);
|
||||
}
|
||||
await _cs.AddAsync(uid, "Picked currency", amount);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
@@ -283,16 +289,18 @@ public class PlantPickService : INService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ulong?> SendPlantMessageAsync(ulong gid, IMessageChannel ch, string user, long amount, string pass)
|
||||
public async Task<ulong?> SendPlantMessageAsync(
|
||||
ulong gid,
|
||||
IMessageChannel ch,
|
||||
string user,
|
||||
long amount,
|
||||
string pass)
|
||||
{
|
||||
try
|
||||
{
|
||||
// get the text
|
||||
var prefix = _cmdHandler.GetPrefix(gid);
|
||||
var msgToSend = GetText(gid,
|
||||
strs.planted(
|
||||
Format.Bold(user),
|
||||
amount + _gss.Data.Currency.Sign));
|
||||
var msgToSend = GetText(gid, strs.planted(Format.Bold(user), amount + _gss.Data.Currency.Sign));
|
||||
|
||||
if (amount > 1)
|
||||
msgToSend += " " + GetText(gid, strs.pick_pl(prefix));
|
||||
@@ -313,34 +321,48 @@ public class PlantPickService : INService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> PlantAsync(ulong gid, IMessageChannel ch, ulong uid, string user, long amount, string pass)
|
||||
public async Task<bool> PlantAsync(
|
||||
ulong gid,
|
||||
IMessageChannel ch,
|
||||
ulong uid,
|
||||
string user,
|
||||
long amount,
|
||||
string pass)
|
||||
{
|
||||
// normalize it - no more than 10 chars, uppercase
|
||||
pass = pass?.Trim().TrimTo(10, hideDots: true).ToUpperInvariant();
|
||||
pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant();
|
||||
// has to be either null or alphanumeric
|
||||
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
|
||||
return false;
|
||||
|
||||
// remove currency from the user who's planting
|
||||
if (await _cs.RemoveAsync(uid, "Planted currency", amount, gamble: false))
|
||||
if (await _cs.RemoveAsync(uid, "Planted currency", amount))
|
||||
{
|
||||
// try to send the message with the currency image
|
||||
var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass);
|
||||
if (msgId is null)
|
||||
{
|
||||
// if it fails it will return null, if it returns null, refund
|
||||
await _cs.AddAsync(uid, "Planted currency refund", amount, gamble: false);
|
||||
await _cs.AddAsync(uid, "Planted currency refund", amount);
|
||||
return false;
|
||||
}
|
||||
|
||||
// if it doesn't fail, put the plant in the database for other people to pick
|
||||
await AddPlantToDatabase(gid, ch.Id, uid, msgId.Value, amount, pass);
|
||||
return true;
|
||||
}
|
||||
|
||||
// if user doesn't have enough currency, fail
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task AddPlantToDatabase(ulong gid, ulong cid, ulong uid, ulong mid, long amount, string pass)
|
||||
private async Task AddPlantToDatabase(
|
||||
ulong gid,
|
||||
ulong cid,
|
||||
ulong uid,
|
||||
ulong mid,
|
||||
long amount,
|
||||
string pass)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
uow.PlantedCurrency.Add(new()
|
||||
@@ -350,8 +372,8 @@ public class PlantPickService : INService
|
||||
ChannelId = cid,
|
||||
Password = pass,
|
||||
UserId = uid,
|
||||
MessageId = mid,
|
||||
MessageId = mid
|
||||
});
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
@@ -10,7 +10,7 @@ public class VoteModel
|
||||
[JsonPropertyName("userId")]
|
||||
public ulong UserId { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class VoteRewardService : INService, IReadyExecutor
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
@@ -33,18 +33,17 @@ public class VoteRewardService : INService, IReadyExecutor
|
||||
_currencyService = currencyService;
|
||||
_gamb = gamb;
|
||||
}
|
||||
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
if (_client.ShardId != 0)
|
||||
return;
|
||||
|
||||
_http = new(new HttpClientHandler()
|
||||
|
||||
_http = new(new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = false,
|
||||
ServerCertificateCustomValidationCallback = delegate { return true; }
|
||||
AllowAutoRedirect = false, ServerCertificateCustomValidationCallback = delegate { return true; }
|
||||
});
|
||||
|
||||
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(30000);
|
||||
@@ -54,8 +53,7 @@ public class VoteRewardService : INService, IReadyExecutor
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(topggKey)
|
||||
&& !string.IsNullOrWhiteSpace(topggServiceUrl))
|
||||
if (!string.IsNullOrWhiteSpace(topggKey) && !string.IsNullOrWhiteSpace(topggServiceUrl))
|
||||
{
|
||||
_http.DefaultRequestHeaders.Authorization = new(topggKey);
|
||||
var uri = new Uri(new(topggServiceUrl), "topgg/new");
|
||||
@@ -82,11 +80,10 @@ public class VoteRewardService : INService, IReadyExecutor
|
||||
|
||||
var discordsKey = _creds.Votes?.DiscordsKey;
|
||||
var discordsServiceUrl = _creds.Votes?.DiscordsServiceUrl;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(discordsKey)
|
||||
&& !string.IsNullOrWhiteSpace(discordsServiceUrl))
|
||||
if (!string.IsNullOrWhiteSpace(discordsKey) && !string.IsNullOrWhiteSpace(discordsServiceUrl))
|
||||
{
|
||||
_http.DefaultRequestHeaders.Authorization = new(discordsKey);
|
||||
var res = await _http.GetStringAsync(new Uri(new(discordsServiceUrl), "discords/new"));
|
||||
@@ -95,7 +92,7 @@ public class VoteRewardService : INService, IReadyExecutor
|
||||
if (data is { Count: > 0 })
|
||||
{
|
||||
var ids = data.Select(x => x.UserId).ToList();
|
||||
|
||||
|
||||
await _currencyService.AddBulkAsync(ids,
|
||||
data.Select(_ => "discords.com vote reward"),
|
||||
data.Select(x => _gamb.Data.VoteReward),
|
||||
@@ -111,4 +108,4 @@ public class VoteRewardService : INService, IReadyExecutor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,24 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Gambling.Common.Waifu;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Waifu;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
public class WaifuService : INService
|
||||
{
|
||||
public class FullWaifuInfo
|
||||
{
|
||||
public WaifuInfo Waifu { get; set; }
|
||||
public IEnumerable<string> Claims { get; set; }
|
||||
public int Divorces { get; set; }
|
||||
}
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly IDataCache _cache;
|
||||
private readonly GamblingConfigService _gss;
|
||||
|
||||
public WaifuService(DbService db, ICurrencyService cs, IDataCache cache,
|
||||
public WaifuService(
|
||||
DbService db,
|
||||
ICurrencyService cs,
|
||||
IDataCache cache,
|
||||
GamblingConfigService gss)
|
||||
{
|
||||
_db = db;
|
||||
@@ -45,18 +41,13 @@ public class WaifuService : INService
|
||||
// owner has to be the owner of the waifu
|
||||
if (waifu is null || waifu.ClaimerId != ownerUser.Id)
|
||||
return false;
|
||||
|
||||
|
||||
// if waifu likes the person, gotta pay the penalty
|
||||
if (waifu.AffinityId == ownerUser.Id)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(owner.Id,
|
||||
"Waifu Transfer - affinity penalty",
|
||||
(int)(waifu.Price * 0.6),
|
||||
true))
|
||||
{
|
||||
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer - affinity penalty", (int)(waifu.Price * 0.6), true))
|
||||
// unable to pay 60% penalty
|
||||
return false;
|
||||
}
|
||||
|
||||
waifu.Price = (int)(waifu.Price * 0.7); // half of 60% = 30% price reduction
|
||||
if (waifu.Price < settings.Waifu.MinPrice)
|
||||
@@ -64,12 +55,9 @@ public class WaifuService : INService
|
||||
}
|
||||
else // if not, pay 10% fee
|
||||
{
|
||||
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, gamble: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, true)) return false;
|
||||
|
||||
waifu.Price = (int) (waifu.Price * 0.95); // half of 10% = 5% price reduction
|
||||
waifu.Price = (int)(waifu.Price * 0.95); // half of 10% = 5% price reduction
|
||||
if (waifu.Price < settings.Waifu.MinPrice)
|
||||
waifu.Price = settings.Waifu.MinPrice;
|
||||
}
|
||||
@@ -92,41 +80,36 @@ public class WaifuService : INService
|
||||
if (waifu is null)
|
||||
return settings.Waifu.MinPrice;
|
||||
|
||||
var divorces = uow.WaifuUpdates.Count(x => x.Old != null &&
|
||||
x.Old.UserId == user.Id &&
|
||||
x.UpdateType == WaifuUpdateType.Claimed &&
|
||||
x.New == null);
|
||||
var affs = uow.WaifuUpdates
|
||||
.AsQueryable()
|
||||
.Where(w => w.User.UserId == user.Id && w.UpdateType == WaifuUpdateType.AffinityChanged &&
|
||||
w.New != null)
|
||||
.ToList()
|
||||
.GroupBy(x => x.New)
|
||||
.Count();
|
||||
var divorces = uow.WaifuUpdates.Count(x
|
||||
=> x.Old != null && x.Old.UserId == user.Id && x.UpdateType == WaifuUpdateType.Claimed && x.New == null);
|
||||
var affs = uow.WaifuUpdates.AsQueryable()
|
||||
.Where(w => w.User.UserId == user.Id
|
||||
&& w.UpdateType == WaifuUpdateType.AffinityChanged
|
||||
&& w.New != null)
|
||||
.ToList()
|
||||
.GroupBy(x => x.New)
|
||||
.Count();
|
||||
|
||||
return (int) Math.Ceiling(waifu.Price * 1.25f) +
|
||||
((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
|
||||
return (int)Math.Ceiling(waifu.Price * 1.25f) + ((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
|
||||
}
|
||||
|
||||
public async Task<bool> TryReset(IUser user)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var price = GetResetPrice(user);
|
||||
if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, gamble: true))
|
||||
if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, true))
|
||||
return false;
|
||||
|
||||
var affs = uow.WaifuUpdates
|
||||
.AsQueryable()
|
||||
.Where(w => w.User.UserId == user.Id
|
||||
&& w.UpdateType == WaifuUpdateType.AffinityChanged
|
||||
&& w.New != null);
|
||||
var affs = uow.WaifuUpdates.AsQueryable()
|
||||
.Where(w => w.User.UserId == user.Id
|
||||
&& w.UpdateType == WaifuUpdateType.AffinityChanged
|
||||
&& w.New != null);
|
||||
|
||||
var divorces = uow.WaifuUpdates
|
||||
.AsQueryable()
|
||||
.Where(x => x.Old != null &&
|
||||
x.Old.UserId == user.Id &&
|
||||
x.UpdateType == WaifuUpdateType.Claimed &&
|
||||
x.New == null);
|
||||
var divorces = uow.WaifuUpdates.AsQueryable()
|
||||
.Where(x => x.Old != null
|
||||
&& x.Old.UserId == user.Id
|
||||
&& x.UpdateType == WaifuUpdateType.Claimed
|
||||
&& x.New == null);
|
||||
|
||||
//reset changes of heart to 0
|
||||
uow.WaifuUpdates.RemoveRange(affs);
|
||||
@@ -161,32 +144,23 @@ public class WaifuService : INService
|
||||
{
|
||||
var claimer = uow.GetOrCreateUser(user);
|
||||
var waifu = uow.GetOrCreateUser(target);
|
||||
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
|
||||
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true))
|
||||
{
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
else
|
||||
{
|
||||
uow.WaifuInfo.Add(w = new()
|
||||
{
|
||||
Waifu = waifu,
|
||||
Claimer = claimer,
|
||||
Affinity = null,
|
||||
Price = amount
|
||||
});
|
||||
uow.WaifuInfo.Add(w = new() { Waifu = waifu, Claimer = claimer, Affinity = null, Price = amount });
|
||||
uow.WaifuUpdates.Add(new()
|
||||
{
|
||||
User = waifu,
|
||||
Old = null,
|
||||
New = claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
User = waifu, Old = null, New = claimer, UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
result = WaifuClaimResult.Success;
|
||||
}
|
||||
}
|
||||
else if (isAffinity && amount > w.Price * settings.Waifu.Multipliers.CrushClaim)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
|
||||
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true))
|
||||
{
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
@@ -199,16 +173,13 @@ public class WaifuService : INService
|
||||
|
||||
uow.WaifuUpdates.Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
User = w.Waifu, Old = oldClaimer, New = w.Claimer, UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity
|
||||
{
|
||||
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
|
||||
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true))
|
||||
{
|
||||
result = WaifuClaimResult.NotEnoughFunds;
|
||||
}
|
||||
@@ -221,15 +192,14 @@ public class WaifuService : INService
|
||||
|
||||
uow.WaifuUpdates.Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = w.Claimer,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
User = w.Waifu, Old = oldClaimer, New = w.Claimer, UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = WaifuClaimResult.InsufficientAmount;
|
||||
}
|
||||
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
@@ -256,21 +226,12 @@ public class WaifuService : INService
|
||||
else if (w is null)
|
||||
{
|
||||
var thisUser = uow.GetOrCreateUser(user);
|
||||
uow.WaifuInfo.Add(new()
|
||||
{
|
||||
Affinity = newAff,
|
||||
Waifu = thisUser,
|
||||
Price = 1,
|
||||
Claimer = null
|
||||
});
|
||||
uow.WaifuInfo.Add(new() { Affinity = newAff, Waifu = thisUser, Price = 1, Claimer = null });
|
||||
success = true;
|
||||
|
||||
uow.WaifuUpdates.Add(new()
|
||||
{
|
||||
User = thisUser,
|
||||
Old = null,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
User = thisUser, Old = null, New = newAff, UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -282,10 +243,7 @@ public class WaifuService : INService
|
||||
|
||||
uow.WaifuUpdates.Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldAff,
|
||||
New = newAff,
|
||||
UpdateType = WaifuUpdateType.AffinityChanged
|
||||
User = w.Waifu, Old = oldAff, New = newAff, UpdateType = WaifuUpdateType.AffinityChanged
|
||||
});
|
||||
}
|
||||
|
||||
@@ -318,7 +276,9 @@ public class WaifuService : INService
|
||||
w = uow.WaifuInfo.ByWaifuUserId(targetId);
|
||||
var now = DateTime.UtcNow;
|
||||
if (w?.Claimer is null || w.Claimer.UserId != user.Id)
|
||||
{
|
||||
result = DivorceResult.NotYourWife;
|
||||
}
|
||||
else if (!_cache.TryAddDivorceCooldown(user.Id, out remaining))
|
||||
{
|
||||
result = DivorceResult.Cooldown;
|
||||
@@ -329,13 +289,13 @@ public class WaifuService : INService
|
||||
|
||||
if (w.Affinity?.UserId == user.Id)
|
||||
{
|
||||
await _cs.AddAsync(w.Waifu.UserId, "Waifu Compensation", amount, gamble: true);
|
||||
w.Price = (int) Math.Floor(w.Price * _gss.Data.Waifu.Multipliers.DivorceNewValue);
|
||||
await _cs.AddAsync(w.Waifu.UserId, "Waifu Compensation", amount, true);
|
||||
w.Price = (int)Math.Floor(w.Price * _gss.Data.Waifu.Multipliers.DivorceNewValue);
|
||||
result = DivorceResult.SucessWithPenalty;
|
||||
}
|
||||
else
|
||||
{
|
||||
await _cs.AddAsync(user.Id, "Waifu Refund", amount, gamble: true);
|
||||
await _cs.AddAsync(user.Id, "Waifu Refund", amount, true);
|
||||
|
||||
result = DivorceResult.Success;
|
||||
}
|
||||
@@ -345,10 +305,7 @@ public class WaifuService : INService
|
||||
|
||||
uow.WaifuUpdates.Add(new()
|
||||
{
|
||||
User = w.Waifu,
|
||||
Old = oldClaimer,
|
||||
New = null,
|
||||
UpdateType = WaifuUpdateType.Claimed
|
||||
User = w.Waifu, Old = oldClaimer, New = null, UpdateType = WaifuUpdateType.Claimed
|
||||
});
|
||||
}
|
||||
|
||||
@@ -360,42 +317,24 @@ public class WaifuService : INService
|
||||
|
||||
public async Task<bool> GiftWaifuAsync(IUser from, IUser giftedWaifu, WaifuItemModel itemObj)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(from, "Bought waifu item", itemObj.Price, gamble: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!await _cs.RemoveAsync(from, "Bought waifu item", itemObj.Price, gamble: true)) return false;
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id,
|
||||
set => set.Include(x => x.Items)
|
||||
.Include(x => x.Claimer));
|
||||
var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id, set => set.Include(x => x.Items).Include(x => x.Claimer));
|
||||
if (w is null)
|
||||
{
|
||||
uow.WaifuInfo.Add(w = new()
|
||||
{
|
||||
Affinity = null,
|
||||
Claimer = null,
|
||||
Price = 1,
|
||||
Waifu = uow.GetOrCreateUser(giftedWaifu),
|
||||
Affinity = null, Claimer = null, Price = 1, Waifu = uow.GetOrCreateUser(giftedWaifu)
|
||||
});
|
||||
}
|
||||
|
||||
if (!itemObj.Negative)
|
||||
{
|
||||
w.Items.Add(new()
|
||||
{
|
||||
Name = itemObj.Name.ToLowerInvariant(),
|
||||
ItemEmoji = itemObj.ItemEmoji,
|
||||
});
|
||||
|
||||
if (w.Claimer?.UserId == @from.Id)
|
||||
{
|
||||
w.Items.Add(new() { Name = itemObj.Name.ToLowerInvariant(), ItemEmoji = itemObj.ItemEmoji });
|
||||
|
||||
if (w.Claimer?.UserId == from.Id)
|
||||
w.Price += (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
|
||||
}
|
||||
else
|
||||
{
|
||||
w.Price += itemObj.Price / 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -414,7 +353,6 @@ public class WaifuService : INService
|
||||
using var uow = _db.GetDbContext();
|
||||
var wi = uow.GetWaifuInfo(targetId);
|
||||
if (wi is null)
|
||||
{
|
||||
wi = new()
|
||||
{
|
||||
AffinityCount = 0,
|
||||
@@ -428,15 +366,15 @@ public class WaifuService : INService
|
||||
Items = new(),
|
||||
Price = 1
|
||||
};
|
||||
}
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
public WaifuInfoStats GetFullWaifuInfoAsync(IGuildUser target)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var du = uow.GetOrCreateUser(target);
|
||||
|
||||
|
||||
return GetFullWaifuInfoAsync(target.Id);
|
||||
}
|
||||
|
||||
@@ -501,8 +439,18 @@ public class WaifuService : INService
|
||||
public IReadOnlyList<WaifuItemModel> GetWaifuItems()
|
||||
{
|
||||
var conf = _gss.Data;
|
||||
return conf.Waifu.Items
|
||||
.Select(x => new WaifuItemModel(x.ItemEmoji, (int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices), x.Name, x.Negative))
|
||||
.ToList();
|
||||
return conf.Waifu.Items.Select(x
|
||||
=> new WaifuItemModel(x.ItemEmoji,
|
||||
(int)(x.Price * conf.Waifu.Multipliers.AllGiftPrices),
|
||||
x.Name,
|
||||
x.Negative))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public class FullWaifuInfo
|
||||
{
|
||||
public WaifuInfo Waifu { get; set; }
|
||||
public IEnumerable<string> Claims { get; set; }
|
||||
public int Divorces { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
@@ -13,69 +13,71 @@ public partial class Gambling
|
||||
[Group]
|
||||
public class ShopCommands : GamblingSubmodule<IShopService>
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
public enum List
|
||||
{
|
||||
List
|
||||
}
|
||||
|
||||
public enum Role
|
||||
{
|
||||
Role
|
||||
}
|
||||
|
||||
public enum List
|
||||
{
|
||||
List
|
||||
}
|
||||
private readonly DbService _db;
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
public ShopCommands(DbService db, ICurrencyService cs, GamblingConfigService gamblingConf)
|
||||
: base(gamblingConf)
|
||||
: base(gamblingConf)
|
||||
{
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
|
||||
private Task ShopInternalAsync(int page = 0)
|
||||
{
|
||||
if (page < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(page));
|
||||
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var entries = uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries
|
||||
.ToIndexed();
|
||||
return ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray();
|
||||
|
||||
if (!theseEntries.Any())
|
||||
return _eb.Create().WithErrorColor()
|
||||
.WithDescription(GetText(strs.shop_none));
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.shop));
|
||||
|
||||
for (var i = 0; i < theseEntries.Length; i++)
|
||||
set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items))
|
||||
.ShopEntries.ToIndexed();
|
||||
return ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
var entry = theseEntries[i];
|
||||
embed.AddField(
|
||||
$"#{(curPage * 9) + i + 1} - {entry.Price}{CurrencySign}",
|
||||
EntryToString(entry),
|
||||
true);
|
||||
}
|
||||
return embed;
|
||||
}, entries.Count, 9, true);
|
||||
var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray();
|
||||
|
||||
if (!theseEntries.Any())
|
||||
return _eb.Create().WithErrorColor().WithDescription(GetText(strs.shop_none));
|
||||
var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.shop));
|
||||
|
||||
for (var i = 0; i < theseEntries.Length; i++)
|
||||
{
|
||||
var entry = theseEntries[i];
|
||||
embed.AddField($"#{(curPage * 9) + i + 1} - {entry.Price}{CurrencySign}",
|
||||
EntryToString(entry),
|
||||
true);
|
||||
}
|
||||
|
||||
return embed;
|
||||
},
|
||||
entries.Count,
|
||||
9);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Shop(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
|
||||
return ShopInternalAsync(page);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Buy(int index)
|
||||
{
|
||||
@@ -85,9 +87,8 @@ public partial class Gambling
|
||||
ShopEntry entry;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set
|
||||
.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items));
|
||||
var config = uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items));
|
||||
var entries = new IndexedCollection<ShopEntry>(config.ShopEntries);
|
||||
entry = entries.ElementAtOrDefault(index);
|
||||
uow.SaveChanges();
|
||||
@@ -109,7 +110,7 @@ public partial class Gambling
|
||||
await ReplyErrorLocalizedAsync(strs.shop_role_not_found);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (guser.RoleIds.Any(id => id == role.Id))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.shop_role_already_bought);
|
||||
@@ -125,23 +126,23 @@ public partial class Gambling
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error adding shop role");
|
||||
await _cs.AddAsync(ctx.User.Id, $"Shop error refund", entry.Price);
|
||||
await _cs.AddAsync(ctx.User.Id, "Shop error refund", entry.Price);
|
||||
await ReplyErrorLocalizedAsync(strs.shop_role_purchase_error);
|
||||
return;
|
||||
}
|
||||
|
||||
var profit = GetProfitAmount(entry.Price);
|
||||
await _cs.AddAsync(entry.AuthorId, $"Shop sell item - {entry.Type}", profit);
|
||||
await _cs.AddAsync(ctx.Client.CurrentUser.Id, $"Shop sell item - cut", entry.Price - profit);
|
||||
await _cs.AddAsync(ctx.Client.CurrentUser.Id, "Shop sell item - cut", entry.Price - profit);
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_role_purchase(Format.Bold(role.Name)));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
else if (entry.Type == ShopEntryType.List)
|
||||
|
||||
if (entry.Type == ShopEntryType.List)
|
||||
{
|
||||
if (entry.Items.Count == 0)
|
||||
{
|
||||
@@ -158,14 +159,15 @@ public partial class Gambling
|
||||
var x = uow.Set<ShopEntryItem>().Remove(item);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ctx.User
|
||||
.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.shop_purchase(ctx.Guild.Name)))
|
||||
.AddField(GetText(strs.item), item.Text, false)
|
||||
.AddField(GetText(strs.price), entry.Price.ToString(), true)
|
||||
.AddField(GetText(strs.name), entry.Name, true));
|
||||
await ctx.User.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.shop_purchase(ctx.Guild.Name)))
|
||||
.AddField(GetText(strs.item), item.Text)
|
||||
.AddField(GetText(strs.price), entry.Price.ToString(), true)
|
||||
.AddField(GetText(strs.name), entry.Name, true));
|
||||
|
||||
await _cs.AddAsync(entry.AuthorId,
|
||||
$"Shop sell item - {entry.Name}",
|
||||
@@ -173,41 +175,37 @@ public partial class Gambling
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _cs.AddAsync(ctx.User.Id,
|
||||
$"Shop error refund - {entry.Name}",
|
||||
entry.Price);
|
||||
await _cs.AddAsync(ctx.User.Id, $"Shop error refund - {entry.Name}", entry.Price);
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries);
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items))
|
||||
.ShopEntries);
|
||||
entry = entries.ElementAtOrDefault(index);
|
||||
if (entry != null)
|
||||
{
|
||||
if (entry.Items.Add(item))
|
||||
{
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.shop_buy_error);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_item_purchase);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static long GetProfitAmount(int price) =>
|
||||
(int)Math.Ceiling(0.90 * price);
|
||||
private static long GetProfitAmount(int price)
|
||||
=> (int)Math.Ceiling(0.90 * price);
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
@@ -215,8 +213,8 @@ public partial class Gambling
|
||||
{
|
||||
if (price < 1)
|
||||
return;
|
||||
|
||||
var entry = new ShopEntry()
|
||||
|
||||
var entry = new ShopEntry
|
||||
{
|
||||
Name = "-",
|
||||
Price = price,
|
||||
@@ -228,19 +226,18 @@ public partial class Gambling
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries)
|
||||
{
|
||||
entry
|
||||
};
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items))
|
||||
.ShopEntries) { entry };
|
||||
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||
.WithTitle(GetText(strs.shop_item_add)));
|
||||
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry).WithTitle(GetText(strs.shop_item_add)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopAdd(List _, int price, [Leftover] string name)
|
||||
@@ -248,31 +245,29 @@ public partial class Gambling
|
||||
if (price < 1)
|
||||
return;
|
||||
|
||||
var entry = new ShopEntry()
|
||||
var entry = new ShopEntry
|
||||
{
|
||||
Name = name.TrimTo(100),
|
||||
Price = price,
|
||||
Type = ShopEntryType.List,
|
||||
AuthorId = ctx.User.Id,
|
||||
Items = new(),
|
||||
Items = new()
|
||||
};
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries)
|
||||
{
|
||||
entry
|
||||
};
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items))
|
||||
.ShopEntries) { entry };
|
||||
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
|
||||
.WithTitle(GetText(strs.shop_item_add)));
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(entry).WithTitle(GetText(strs.shop_item_add)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopListAdd(int index, [Leftover] string itemText)
|
||||
@@ -280,27 +275,22 @@ public partial class Gambling
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
var item = new ShopEntryItem()
|
||||
{
|
||||
Text = itemText
|
||||
};
|
||||
var item = new ShopEntryItem { Text = itemText };
|
||||
ShopEntry entry;
|
||||
var rightType = false;
|
||||
var added = false;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items)).ShopEntries);
|
||||
set => set.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items))
|
||||
.ShopEntries);
|
||||
entry = entries.ElementAtOrDefault(index);
|
||||
if (entry != null && (rightType = entry.Type == ShopEntryType.List))
|
||||
{
|
||||
if (added = entry.Items.Add(item))
|
||||
{
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entry is null)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_not_found);
|
||||
else if (!rightType)
|
||||
@@ -311,7 +301,8 @@ public partial class Gambling
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_list_item_added);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopRemove(int index)
|
||||
@@ -322,9 +313,8 @@ public partial class Gambling
|
||||
ShopEntry removed;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set
|
||||
.Include(x => x.ShopEntries)
|
||||
.ThenInclude(x => x.Items));
|
||||
var config = uow.GuildConfigsForId(ctx.Guild.Id,
|
||||
set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items));
|
||||
|
||||
var entries = new IndexedCollection<ShopEntry>(config.ShopEntries);
|
||||
removed = entries.ElementAtOrDefault(index);
|
||||
@@ -339,11 +329,11 @@ public partial class Gambling
|
||||
if (removed is null)
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_not_found);
|
||||
else
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(removed)
|
||||
.WithTitle(GetText(strs.shop_item_rm)));
|
||||
await ctx.Channel.EmbedAsync(EntryToEmbed(removed).WithTitle(GetText(strs.shop_item_rm)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopChangePrice(int index, int price)
|
||||
@@ -363,14 +353,15 @@ public partial class Gambling
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopChangeName(int index, [Leftover] string newName)
|
||||
{
|
||||
if (--index < 0 || string.IsNullOrWhiteSpace(newName))
|
||||
return;
|
||||
|
||||
|
||||
var succ = await _service.ChangeEntryNameAsync(ctx.Guild.Id, index, newName);
|
||||
if (succ)
|
||||
{
|
||||
@@ -383,14 +374,15 @@ public partial class Gambling
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopSwap(int index1, int index2)
|
||||
{
|
||||
if (--index1 < 0 || --index2 < 0 || index1 == index2)
|
||||
return;
|
||||
|
||||
|
||||
var succ = await _service.SwapEntriesAsync(ctx.Guild.Id, index1, index2);
|
||||
if (succ)
|
||||
{
|
||||
@@ -402,8 +394,9 @@ public partial class Gambling
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopMove(int fromIndex, int toIndex)
|
||||
@@ -422,36 +415,36 @@ public partial class Gambling
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public IEmbedBuilder EntryToEmbed(ShopEntry entry)
|
||||
{
|
||||
var embed = _eb.Create().WithOkColor();
|
||||
|
||||
if (entry.Type == ShopEntryType.Role)
|
||||
return embed.AddField(GetText(strs.name), GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"))), true)
|
||||
.AddField(GetText(strs.price), entry.Price.ToString(), true)
|
||||
.AddField(GetText(strs.type), entry.Type.ToString(), true);
|
||||
else if (entry.Type == ShopEntryType.List)
|
||||
return embed
|
||||
.AddField(GetText(strs.name),
|
||||
GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name
|
||||
?? "MISSING_ROLE"))),
|
||||
true)
|
||||
.AddField(GetText(strs.price), entry.Price.ToString(), true)
|
||||
.AddField(GetText(strs.type), entry.Type.ToString(), true);
|
||||
if (entry.Type == ShopEntryType.List)
|
||||
return embed.AddField(GetText(strs.name), entry.Name, true)
|
||||
.AddField(GetText(strs.price), entry.Price.ToString(), true)
|
||||
.AddField(GetText(strs.type), GetText(strs.random_unique_item), true);
|
||||
.AddField(GetText(strs.price), entry.Price.ToString(), true)
|
||||
.AddField(GetText(strs.type), GetText(strs.random_unique_item), true);
|
||||
//else if (entry.Type == ShopEntryType.Infinite_List)
|
||||
// return embed.AddField(GetText(strs.name), GetText(strs.shop_role(Format.Bold(entry.RoleName)), true))
|
||||
// .AddField(GetText(strs.price), entry.Price.ToString(), true)
|
||||
// .AddField(GetText(strs.type), entry.Type.ToString(), true);
|
||||
else return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
public string EntryToString(ShopEntry entry)
|
||||
{
|
||||
if (entry.Type == ShopEntryType.Role)
|
||||
{
|
||||
return GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE")));
|
||||
}
|
||||
else if (entry.Type == ShopEntryType.List)
|
||||
{
|
||||
if (entry.Type == ShopEntryType.List)
|
||||
return GetText(strs.unique_items_left(entry.Items.Count)) + "\n" + entry.Name;
|
||||
}
|
||||
//else if (entry.Type == ShopEntryType.Infinite_List)
|
||||
//{
|
||||
|
||||
@@ -459,4 +452,4 @@ public partial class Gambling
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
#nullable disable
|
||||
using System.Text;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using SixLabors.Fonts;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System.Text;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
@@ -31,64 +31,23 @@ public partial class Gambling
|
||||
private FontProvider _fonts;
|
||||
private readonly DbService _db;
|
||||
|
||||
public SlotCommands(IDataCache data,
|
||||
FontProvider fonts, DbService db,
|
||||
GamblingConfigService gamb) : base(gamb)
|
||||
public SlotCommands(
|
||||
IDataCache data,
|
||||
FontProvider fonts,
|
||||
DbService db,
|
||||
GamblingConfigService gamb)
|
||||
: base(gamb)
|
||||
{
|
||||
_images = data.LocalImages;
|
||||
_fonts = fonts;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public sealed class SlotMachine
|
||||
{
|
||||
public const int MAX_VALUE = 5;
|
||||
public Task Test()
|
||||
=> Task.CompletedTask;
|
||||
|
||||
private static readonly List<Func<int[], int>> _winningCombos = new()
|
||||
{
|
||||
//three flowers
|
||||
arr => arr.All(a=>a==MAX_VALUE) ? 30 : 0,
|
||||
//three of the same
|
||||
arr => !arr.Any(a => a != arr[0]) ? 10 : 0,
|
||||
//two flowers
|
||||
arr => arr.Count(a => a == MAX_VALUE) == 2 ? 4 : 0,
|
||||
//one flower
|
||||
arr => arr.Any(a => a == MAX_VALUE) ? 1 : 0,
|
||||
};
|
||||
|
||||
public static SlotResult Pull()
|
||||
{
|
||||
var numbers = new int[3];
|
||||
for (var i = 0; i < numbers.Length; i++)
|
||||
{
|
||||
numbers[i] = new NadekoRandom().Next(0, MAX_VALUE + 1);
|
||||
}
|
||||
var multi = 0;
|
||||
foreach (var t in _winningCombos)
|
||||
{
|
||||
multi = t(numbers);
|
||||
if (multi != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return new(numbers, multi);
|
||||
}
|
||||
|
||||
public struct SlotResult
|
||||
{
|
||||
public int[] Numbers { get; }
|
||||
public int Multiplier { get; }
|
||||
public SlotResult(int[] nums, int multi)
|
||||
{
|
||||
Numbers = nums;
|
||||
Multiplier = multi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task Test() => Task.CompletedTask;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SlotStats()
|
||||
{
|
||||
@@ -100,16 +59,17 @@ public partial class Gambling
|
||||
bet = 1;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("Slot Stats")
|
||||
.AddField("Total Bet", bet.ToString(), true)
|
||||
.AddField("Paid Out", paid.ToString(), true)
|
||||
.WithFooter($"Payout Rate: {paid * 1.0 / bet * 100:f4}%");
|
||||
.WithOkColor()
|
||||
.WithTitle("Slot Stats")
|
||||
.AddField("Total Bet", bet.ToString(), true)
|
||||
.AddField("Paid Out", paid.ToString(), true)
|
||||
.WithFooter($"Payout Rate: {paid * 1.0 / bet * 100:f4}%");
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task SlotTest(int tests = 1000)
|
||||
{
|
||||
@@ -133,21 +93,24 @@ public partial class Gambling
|
||||
sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
|
||||
payout += key * dict[key];
|
||||
}
|
||||
await SendConfirmAsync("Slot Test Results", sb.ToString(),
|
||||
|
||||
await SendConfirmAsync("Slot Test Results",
|
||||
sb.ToString(),
|
||||
footer: $"Total Bet: {tests} | Payout: {payout} | {payout * 1.0f / tests * 100}%");
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Slot(ShmartNumber amount)
|
||||
{
|
||||
if (!_runningUsers.Add(ctx.User.Id))
|
||||
return;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (!await CheckBetMandatory(amount))
|
||||
return;
|
||||
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
|
||||
var result = await _service.SlotAsync(ctx.User.Id, amount);
|
||||
@@ -155,9 +118,7 @@ public partial class Gambling
|
||||
if (result.Error != GamblingError.None)
|
||||
{
|
||||
if (result.Error == GamblingError.NotEnough)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -168,40 +129,45 @@ public partial class Gambling
|
||||
long ownedAmount;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
ownedAmount = uow.Set<DiscordUser>()
|
||||
.FirstOrDefault(x => x.UserId == ctx.User.Id)
|
||||
?.CurrencyAmount ?? 0;
|
||||
ownedAmount = uow.Set<DiscordUser>().FirstOrDefault(x => x.UserId == ctx.User.Id)?.CurrencyAmount
|
||||
?? 0;
|
||||
}
|
||||
|
||||
|
||||
using (var bgImage = Image.Load<Rgba32>(_images.SlotBackground, out var format))
|
||||
{
|
||||
var numbers = new int[3];
|
||||
result.Rolls.CopyTo(numbers, 0);
|
||||
|
||||
Color fontColor = _config.Slots.CurrencyFontColor;
|
||||
|
||||
|
||||
bgImage.Mutate(x => x.DrawText(new()
|
||||
{
|
||||
TextOptions = new()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 140,
|
||||
WrapTextWidth = 140
|
||||
}
|
||||
}, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), fontColor,
|
||||
},
|
||||
result.Won.ToString(),
|
||||
_fonts.DottyFont.CreateFont(65),
|
||||
fontColor,
|
||||
new(227, 92)));
|
||||
|
||||
var bottomFont = _fonts.DottyFont.CreateFont(50);
|
||||
|
||||
|
||||
bgImage.Mutate(x => x.DrawText(new()
|
||||
{
|
||||
TextOptions = new()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 135,
|
||||
WrapTextWidth = 135
|
||||
}
|
||||
}, amount.ToString(), bottomFont, fontColor,
|
||||
},
|
||||
amount.ToString(),
|
||||
bottomFont,
|
||||
fontColor,
|
||||
new(129, 472)));
|
||||
|
||||
bgImage.Mutate(x => x.DrawText(new()
|
||||
@@ -210,9 +176,12 @@ public partial class Gambling
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
WrapTextWidth = 135,
|
||||
WrapTextWidth = 135
|
||||
}
|
||||
}, ownedAmount.ToString(), bottomFont, fontColor,
|
||||
},
|
||||
ownedAmount.ToString(),
|
||||
bottomFont,
|
||||
fontColor,
|
||||
new(325, 472)));
|
||||
//sw.PrintLap("drew red text");
|
||||
|
||||
@@ -238,8 +207,8 @@ public partial class Gambling
|
||||
await using (var imgStream = bgImage.ToStream())
|
||||
{
|
||||
await ctx.Channel.SendFileAsync(imgStream,
|
||||
filename: "result.png",
|
||||
text: Format.Bold(ctx.User.ToString()) + " " + msg);
|
||||
"result.png",
|
||||
Format.Bold(ctx.User.ToString()) + " " + msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,5 +221,49 @@ public partial class Gambling
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SlotMachine
|
||||
{
|
||||
public const int MAX_VALUE = 5;
|
||||
|
||||
private static readonly List<Func<int[], int>> _winningCombos = new()
|
||||
{
|
||||
//three flowers
|
||||
arr => arr.All(a => a == MAX_VALUE) ? 30 : 0,
|
||||
//three of the same
|
||||
arr => !arr.Any(a => a != arr[0]) ? 10 : 0,
|
||||
//two flowers
|
||||
arr => arr.Count(a => a == MAX_VALUE) == 2 ? 4 : 0,
|
||||
//one flower
|
||||
arr => arr.Any(a => a == MAX_VALUE) ? 1 : 0
|
||||
};
|
||||
|
||||
public static SlotResult Pull()
|
||||
{
|
||||
var numbers = new int[3];
|
||||
for (var i = 0; i < numbers.Length; i++) numbers[i] = new NadekoRandom().Next(0, MAX_VALUE + 1);
|
||||
var multi = 0;
|
||||
foreach (var t in _winningCombos)
|
||||
{
|
||||
multi = t(numbers);
|
||||
if (multi != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return new(numbers, multi);
|
||||
}
|
||||
|
||||
public struct SlotResult
|
||||
{
|
||||
public int[] Numbers { get; }
|
||||
public int Multiplier { get; }
|
||||
|
||||
public SlotResult(int[] nums, int multi)
|
||||
{
|
||||
Numbers = nums;
|
||||
Multiplier = multi;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,4 @@ public class TestGamblingService : InteractionModuleBase
|
||||
[SlashCommand("test", "uwu")]
|
||||
public async Task Test(string input1, int input2)
|
||||
=> await RespondAsync("Bravo " + input1 + input2);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user