Applied codestyle to all .cs files

This commit is contained in:
Kwoth
2021-12-29 06:07:16 +01:00
parent 723447c7d4
commit 82000c97a4
543 changed files with 13221 additions and 14059 deletions

View File

@@ -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();
}
}
}

View File

@@ -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")));
}
}
}
}

View File

@@ -18,4 +18,4 @@ public enum LogType
VoicePresence,
VoicePresenceTTS,
UserMuted
}
}

View File

@@ -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);
}

View File

@@ -10,4 +10,4 @@ public class PunishQueueItem
public int MuteTime { get; set; }
public ulong? RoleId { get; set; }
public IGuildUser User { get; set; }
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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
};
}
}

View File

@@ -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
}

View File

@@ -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`"));
}
}
}
}

View File

@@ -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" }
*/
*/

View File

@@ -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())));
}
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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)));
}
}
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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(',');
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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());
}
}
}
}

View File

@@ -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()
{
}
}
}
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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()
};
}
}

View File

@@ -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();
}
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}

View File

@@ -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)
{
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -6,4 +6,4 @@ public interface ICurrencyEvent
event Func<ulong, Task> OnEnded;
Task StopEvent();
Task StartEvent();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -5,4 +5,4 @@ public enum GamblingError
{
None,
NotEnough
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -5,4 +5,4 @@ public class Payout
{
public string User { get; set; }
public int Amount { get; set; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -7,4 +7,4 @@ public class SlotResponse
public long Won { get; set; }
public List<int> Rolls { get; set; } = new();
public GamblingError Error { get; set; }
}
}

View File

@@ -13,4 +13,4 @@ public enum AffinityTitle
Sloot,
Depraved,
Harlot
}
}

View File

@@ -14,5 +14,5 @@ public enum ClaimTitle
Veteran,
Incubis,
Harem_King,
Harem_God,
}
Harem_God
}

View File

@@ -7,4 +7,4 @@ public enum DivorceResult
SucessWithPenalty,
NotYourWife,
Cooldown
}
}

View File

@@ -6,4 +6,4 @@ public enum WaifuClaimResult
Success,
NotEnoughFunds,
InsufficientAmount
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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]);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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()));
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -6,4 +6,4 @@ namespace NadekoBot.Modules.Gambling.Services;
public class AnimalRaceService : INService
{
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new();
}
}

View File

@@ -6,4 +6,4 @@ namespace NadekoBot.Modules.Gambling.Services;
public class BlackJackService : INService
{
public ConcurrentDictionary<ulong, Blackjack> Games { get; } = new();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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;
});
}
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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 "";
}
}
}
}

View File

@@ -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;
}
}
}
}
}
}

View File

@@ -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