Global usings and file scoped namespaces

This commit is contained in:
Kwoth
2021-12-19 05:14:11 +01:00
parent bc31dae965
commit ee33313519
548 changed files with 47528 additions and 49115 deletions

View File

@@ -5,349 +5,346 @@ using NadekoBot.Common.Attributes;
using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration : NadekoModule<AdministrationService>
{
public partial class Administration : NadekoModule<AdministrationService>
private readonly ImageOnlyChannelService _imageOnly;
public Administration(ImageOnlyChannelService imageOnly)
{
private readonly ImageOnlyChannelService _imageOnly;
public Administration(ImageOnlyChannelService imageOnly)
{
_imageOnly = imageOnly;
}
_imageOnly = imageOnly;
}
public enum List
{
List = 0,
Ls = 0
}
public enum List
{
List = 0,
Ls = 0
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.Administrator)]
public async Task ImageOnlyChannel(StoopidTime time = null)
{
var newValue = _imageOnly.ToggleImageOnlyChannel(ctx.Guild.Id, ctx.Channel.Id);
if (newValue)
await ReplyConfirmLocalizedAsync(strs.imageonly_enable);
else
await ReplyPendingLocalizedAsync(strs.imageonly_disable);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.Administrator)]
public async Task ImageOnlyChannel(StoopidTime time = null)
{
var newValue = _imageOnly.ToggleImageOnlyChannel(ctx.Guild.Id, ctx.Channel.Id);
if (newValue)
await ReplyConfirmLocalizedAsync(strs.imageonly_enable);
else
await ReplyPendingLocalizedAsync(strs.imageonly_disable);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageChannel)]
[BotPerm(ChannelPerm.ManageChannel)]
public async Task Slowmode(StoopidTime time = null)
{
var seconds = (int?)time?.Time.TotalSeconds ?? 0;
if (time is not null && (time.Time < TimeSpan.FromSeconds(0) || time.Time > TimeSpan.FromHours(6)))
return;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageChannel)]
[BotPerm(ChannelPerm.ManageChannel)]
public async Task Slowmode(StoopidTime time = null)
{
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;
});
await ctx.OkAsync();
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(2)]
public async Task Delmsgoncmd(List _)
{
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 ? "✅" : "❌");
var str = string.Join("\n", channels
.Select(x =>
{
tcp.SlowModeInterval = seconds;
});
var ch = guild.GetChannel(x.ChannelId)?.ToString()
?? x.ChannelId.ToString();
var prefix = x.State ? "✅ " : "❌ ";
return prefix + ch;
}));
await ctx.OkAsync();
}
if (string.IsNullOrWhiteSpace(str))
str = "-";
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(2)]
public async Task Delmsgoncmd(List _)
embed.AddField(GetText(strs.channel_delmsgoncmd), str);
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
public enum Server
{
Server
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(1)]
public async Task Delmsgoncmd(Server _ = Server.Server)
{
if (_service.ToggleDeleteMessageOnCommand(ctx.Guild.Id))
{
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 ? "✅" : "❌");
var str = string.Join("\n", channels
.Select(x =>
{
var ch = guild.GetChannel(x.ChannelId)?.ToString()
?? x.ChannelId.ToString();
var prefix = x.State ? "✅ " : "❌ ";
return prefix + ch;
}));
if (string.IsNullOrWhiteSpace(str))
str = "-";
embed.AddField(GetText(strs.channel_delmsgoncmd), str);
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
_service.DeleteMessagesOnCommand.Add(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync(strs.delmsg_on).ConfigureAwait(false);
}
public enum Server
else
{
Server
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(1)]
public async Task Delmsgoncmd(Server _ = Server.Server)
{
if (_service.ToggleDeleteMessageOnCommand(ctx.Guild.Id))
{
_service.DeleteMessagesOnCommand.Add(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync(strs.delmsg_on).ConfigureAwait(false);
}
else
{
_service.DeleteMessagesOnCommand.TryRemove(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync(strs.delmsg_off).ConfigureAwait(false);
}
}
public enum Channel
{
Channel,
Ch,
Chnl,
Chan
}
public enum State
{
Enable,
Disable,
Inherit
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(0)]
public Task Delmsgoncmd(Channel _, State s, ITextChannel ch)
=> Delmsgoncmd(_, s, ch.Id);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(1)]
public async Task Delmsgoncmd(Channel _, State s, ulong? chId = null)
{
var actualChId = chId ?? ctx.Channel.Id;
await _service.SetDelMsgOnCmdState(ctx.Guild.Id, actualChId, s).ConfigureAwait(false);
if (s == State.Disable)
{
await ReplyConfirmLocalizedAsync(strs.delmsg_channel_off).ConfigureAwait(false);
}
else if (s == State.Enable)
{
await ReplyConfirmLocalizedAsync(strs.delmsg_channel_on).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.delmsg_channel_inherit).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.DeafenMembers)]
[BotPerm(GuildPerm.DeafenMembers)]
public async Task Deafen(params IGuildUser[] users)
{
await _service.DeafenUsers(true, users).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.deafen).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.DeafenMembers)]
[BotPerm(GuildPerm.DeafenMembers)]
public async Task UnDeafen(params IGuildUser[] users)
{
await _service.DeafenUsers(false, users).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.undeafen).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task DelVoiChanl([Leftover] IVoiceChannel voiceChannel)
{
await voiceChannel.DeleteAsync().ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.delvoich(Format.Bold(voiceChannel.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task CreatVoiChanl([Leftover] string channelName)
{
var ch = await ctx.Guild.CreateVoiceChannelAsync(channelName).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.createvoich(Format.Bold(ch.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task DelTxtChanl([Leftover] ITextChannel toDelete)
{
await toDelete.DeleteAsync().ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.deltextchan(Format.Bold(toDelete.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task CreaTxtChanl([Leftover] string channelName)
{
var txtCh = await ctx.Guild.CreateTextChannelAsync(channelName).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.createtextchan(Format.Bold(txtCh.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task SetTopic([Leftover] string topic = null)
{
var channel = (ITextChannel) ctx.Channel;
topic = topic ?? "";
await channel.ModifyAsync(c => c.Topic = topic).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.set_topic).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task SetChanlName([Leftover] string name)
{
var channel = (ITextChannel) ctx.Channel;
await channel.ModifyAsync(c => c.Name = name).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.set_channel_name).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task NsfwToggle()
{
var channel = (ITextChannel) ctx.Channel;
var isEnabled = channel.IsNsfw;
await channel.ModifyAsync(c => c.IsNsfw = !isEnabled).ConfigureAwait(false);
if (isEnabled)
await ReplyConfirmLocalizedAsync(strs.nsfw_set_false).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.nsfw_set_true).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[Priority(0)]
public Task Edit(ulong messageId, [Leftover] string text)
=> Edit((ITextChannel) ctx.Channel, messageId, text);
[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);
if (!userPerms.Has(ChannelPermission.ManageMessages))
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_u).ConfigureAwait(false);
return;
}
if (!botPerms.Has(ChannelPermission.ViewChannel))
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_i).ConfigureAwait(false);
return;
}
await _service.EditMessage(ctx, channel, messageId, text);
}
[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);
[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,
Func<IMessage, Task> func)
{
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).ConfigureAwait(false);
return;
}
if (!botPerms.Has(ChannelPermission.ManageMessages))
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_i).ConfigureAwait(false);
return;
}
var msg = await channel.GetMessageAsync(messageId).ConfigureAwait(false);
if (msg is null)
{
await ReplyErrorLocalizedAsync(strs.msg_not_found).ConfigureAwait(false);
return;
}
if (time is null)
{
await msg.DeleteAsync().ConfigureAwait(false);
}
else if (time.Time <= TimeSpan.FromDays(7))
{
var _ = Task.Run(async () =>
{
await Task.Delay(time.Time).ConfigureAwait(false);
await msg.DeleteAsync().ConfigureAwait(false);
});
}
else
{
await ReplyErrorLocalizedAsync(strs.time_too_long).ConfigureAwait(false);
return;
}
await ctx.OkAsync();
_service.DeleteMessagesOnCommand.TryRemove(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync(strs.delmsg_off).ConfigureAwait(false);
}
}
public enum Channel
{
Channel,
Ch,
Chnl,
Chan
}
public enum State
{
Enable,
Disable,
Inherit
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(0)]
public Task Delmsgoncmd(Channel _, State s, ITextChannel ch)
=> Delmsgoncmd(_, s, ch.Id);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(1)]
public async Task Delmsgoncmd(Channel _, State s, ulong? chId = null)
{
var actualChId = chId ?? ctx.Channel.Id;
await _service.SetDelMsgOnCmdState(ctx.Guild.Id, actualChId, s).ConfigureAwait(false);
if (s == State.Disable)
{
await ReplyConfirmLocalizedAsync(strs.delmsg_channel_off).ConfigureAwait(false);
}
else if (s == State.Enable)
{
await ReplyConfirmLocalizedAsync(strs.delmsg_channel_on).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.delmsg_channel_inherit).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.DeafenMembers)]
[BotPerm(GuildPerm.DeafenMembers)]
public async Task Deafen(params IGuildUser[] users)
{
await _service.DeafenUsers(true, users).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.deafen).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.DeafenMembers)]
[BotPerm(GuildPerm.DeafenMembers)]
public async Task UnDeafen(params IGuildUser[] users)
{
await _service.DeafenUsers(false, users).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.undeafen).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task DelVoiChanl([Leftover] IVoiceChannel voiceChannel)
{
await voiceChannel.DeleteAsync().ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.delvoich(Format.Bold(voiceChannel.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task CreatVoiChanl([Leftover] string channelName)
{
var ch = await ctx.Guild.CreateVoiceChannelAsync(channelName).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.createvoich(Format.Bold(ch.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task DelTxtChanl([Leftover] ITextChannel toDelete)
{
await toDelete.DeleteAsync().ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.deltextchan(Format.Bold(toDelete.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task CreaTxtChanl([Leftover] string channelName)
{
var txtCh = await ctx.Guild.CreateTextChannelAsync(channelName).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.createtextchan(Format.Bold(txtCh.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task SetTopic([Leftover] string topic = null)
{
var channel = (ITextChannel) ctx.Channel;
topic = topic ?? "";
await channel.ModifyAsync(c => c.Topic = topic).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.set_topic).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task SetChanlName([Leftover] string name)
{
var channel = (ITextChannel) ctx.Channel;
await channel.ModifyAsync(c => c.Name = name).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.set_channel_name).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task NsfwToggle()
{
var channel = (ITextChannel) ctx.Channel;
var isEnabled = channel.IsNsfw;
await channel.ModifyAsync(c => c.IsNsfw = !isEnabled).ConfigureAwait(false);
if (isEnabled)
await ReplyConfirmLocalizedAsync(strs.nsfw_set_false).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.nsfw_set_true).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[Priority(0)]
public Task Edit(ulong messageId, [Leftover] string text)
=> Edit((ITextChannel) ctx.Channel, messageId, text);
[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);
if (!userPerms.Has(ChannelPermission.ManageMessages))
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_u).ConfigureAwait(false);
return;
}
if (!botPerms.Has(ChannelPermission.ViewChannel))
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_i).ConfigureAwait(false);
return;
}
await _service.EditMessage(ctx, channel, messageId, text);
}
[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);
[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,
Func<IMessage, Task> func)
{
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).ConfigureAwait(false);
return;
}
if (!botPerms.Has(ChannelPermission.ManageMessages))
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_i).ConfigureAwait(false);
return;
}
var msg = await channel.GetMessageAsync(messageId).ConfigureAwait(false);
if (msg is null)
{
await ReplyErrorLocalizedAsync(strs.msg_not_found).ConfigureAwait(false);
return;
}
if (time is null)
{
await msg.DeleteAsync().ConfigureAwait(false);
}
else if (time.Time <= TimeSpan.FromDays(7))
{
var _ = Task.Run(async () =>
{
await Task.Delay(time.Time).ConfigureAwait(false);
await msg.DeleteAsync().ConfigureAwait(false);
});
}
else
{
await ReplyErrorLocalizedAsync(strs.time_too_long).ConfigureAwait(false);
return;
}
await ctx.OkAsync();
}
}

View File

@@ -1,74 +1,72 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Administration.Services;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class AutoAssignRoleCommands : NadekoSubmodule<AutoAssignRoleService>
{
[Group]
public class AutoAssignRoleCommands : NadekoSubmodule<AutoAssignRoleService>
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task AutoAssignRole([Leftover] IRole role)
{
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task AutoAssignRole([Leftover] IRole role)
var guser = (IGuildUser) ctx.User;
if (role.Id == ctx.Guild.EveryoneRole.Id)
return;
// the user can't aar the role which is higher or equal to his highest role
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
{
var guser = (IGuildUser) ctx.User;
if (role.Id == ctx.Guild.EveryoneRole.Id)
return;
// the user can't aar the role which is higher or equal to his highest role
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
{
await ReplyErrorLocalizedAsync(strs.hierarchy);
return;
}
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())));
}
await ReplyErrorLocalizedAsync(strs.hierarchy);
return;
}
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]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task AutoAssignRole()
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task AutoAssignRole()
{
if (!_service.TryGetRoles(ctx.Guild.Id, out var roles))
{
if (!_service.TryGetRoles(ctx.Guild.Id, out var roles))
{
await ReplyConfirmLocalizedAsync(strs.aar_none);
return;
}
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()))
.JoinWith(",\n")));
await ReplyConfirmLocalizedAsync(strs.aar_none);
return;
}
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()))
.JoinWith(",\n")));
}
}
}

View File

@@ -1,21 +1,20 @@
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public enum LogType
{
public enum LogType
{
Other,
MessageUpdated,
MessageDeleted,
UserJoined,
UserLeft,
UserBanned,
UserUnbanned,
UserUpdated,
ChannelCreated,
ChannelDestroyed,
ChannelUpdated,
UserPresence,
VoicePresence,
VoicePresenceTTS,
UserMuted
}
Other,
MessageUpdated,
MessageDeleted,
UserJoined,
UserLeft,
UserBanned,
UserUnbanned,
UserUpdated,
ChannelCreated,
ChannelDestroyed,
ChannelUpdated,
UserPresence,
VoicePresence,
VoicePresenceTTS,
UserMuted
}

View File

@@ -1,50 +1,48 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Threading;
using Discord;
using NadekoBot.Common.Collections;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Administration.Common
namespace NadekoBot.Modules.Administration.Common;
public enum ProtectionType
{
public enum ProtectionType
{
Raiding,
Spamming,
Alting
}
public class AntiRaidStats
{
public AntiRaidSetting AntiRaidSettings { get; set; }
public int UsersCount { get; set; }
public ConcurrentHashSet<IGuildUser> RaidUsers { get; set; } = new ConcurrentHashSet<IGuildUser>();
}
public class AntiSpamStats
{
public AntiSpamSetting AntiSpamSettings { get; set; }
public ConcurrentDictionary<ulong, UserSpamStats> UserStats { get; set; }
= new ConcurrentDictionary<ulong, UserSpamStats>();
}
public class AntiAltStats
{
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;
public AntiAltStats(AntiAltSetting setting)
{
_setting = setting;
}
public void Increment() => Interlocked.Increment(ref _counter);
}
Raiding,
Spamming,
Alting
}
public class AntiRaidStats
{
public AntiRaidSetting AntiRaidSettings { get; set; }
public int UsersCount { get; set; }
public ConcurrentHashSet<IGuildUser> RaidUsers { get; set; } = new ConcurrentHashSet<IGuildUser>();
}
public class AntiSpamStats
{
public AntiSpamSetting AntiSpamSettings { get; set; }
public ConcurrentDictionary<ulong, UserSpamStats> UserStats { get; set; }
= new ConcurrentDictionary<ulong, UserSpamStats>();
}
public class AntiAltStats
{
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;
public AntiAltStats(AntiAltSetting setting)
{
_setting = setting;
}
public void Increment() => Interlocked.Increment(ref _counter);
}

View File

@@ -1,14 +1,13 @@
using Discord;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Administration.Common
namespace NadekoBot.Modules.Administration.Common;
public class PunishQueueItem
{
public class PunishQueueItem
{
public PunishmentAction Action { get; set; }
public ProtectionType Type { get; set; }
public int MuteTime { get; set; }
public ulong? RoleId { get; set; }
public IGuildUser User { get; set; }
}
public PunishmentAction Action { get; set; }
public ProtectionType Type { get; set; }
public int MuteTime { get; set; }
public ulong? RoleId { get; set; }
public IGuildUser User { get; set; }
}

View File

@@ -1,50 +1,47 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Collections.Concurrent;
using System.Threading;
using Discord;
namespace NadekoBot.Modules.Administration.Common
namespace NadekoBot.Modules.Administration.Common;
public sealed class UserSpamStats : IDisposable
{
public sealed class UserSpamStats : IDisposable
public int Count => timers.Count;
public string LastMessage { get; set; }
private ConcurrentQueue<Timer> timers { get; }
public UserSpamStats(IUserMessage msg)
{
public int Count => timers.Count;
public string LastMessage { get; set; }
LastMessage = msg.Content.ToUpperInvariant();
timers = new ConcurrentQueue<Timer>();
private ConcurrentQueue<Timer> timers { get; }
ApplyNextMessage(msg);
}
public UserSpamStats(IUserMessage msg)
private readonly object applyLock = new object();
public void ApplyNextMessage(IUserMessage message)
{
lock (applyLock)
{
LastMessage = msg.Content.ToUpperInvariant();
timers = new ConcurrentQueue<Timer>();
ApplyNextMessage(msg);
}
private readonly object applyLock = new object();
public void ApplyNextMessage(IUserMessage message)
{
lock (applyLock)
var upperMsg = message.Content.ToUpperInvariant();
if (upperMsg != LastMessage || (string.IsNullOrWhiteSpace(upperMsg) && message.Attachments.Any()))
{
var upperMsg = message.Content.ToUpperInvariant();
if (upperMsg != LastMessage || (string.IsNullOrWhiteSpace(upperMsg) && message.Attachments.Any()))
{
LastMessage = upperMsg;
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));
timers.Enqueue(t);
LastMessage = upperMsg;
while (timers.TryDequeue(out var old))
old.Change(Timeout.Infinite, Timeout.Infinite);
}
}
public void Dispose()
{
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));
timers.Enqueue(t);
}
}
}
public void Dispose()
{
while (timers.TryDequeue(out var old))
old.Change(Timeout.Infinite, Timeout.Infinite);
}
}

View File

@@ -1,11 +1,9 @@
using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using System;
using System.Threading.Tasks;
using Discord;
using NadekoBot.Modules.Administration.Services;
using System.Linq;
#if !GLOBAL_NADEKO
namespace NadekoBot.Modules.Administration

View File

@@ -1,5 +1,4 @@
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using NadekoBot.Common.Attributes;
@@ -7,81 +6,80 @@ using NadekoBot.Common.TypeReaders;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class DiscordPermOverrideCommands : NadekoSubmodule<DiscordPermOverrideService>
{
[Group]
public class DiscordPermOverrideCommands : NadekoSubmodule<DiscordPermOverrideService>
// override stats, it should require that the user has managessages guild permission
// .po 'stats' add user guild managemessages
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task DiscordPermOverride(CommandOrCrInfo cmd, params GuildPerm[] perms)
{
// override stats, it should require that the user has managessages guild permission
// .po 'stats' add user guild managemessages
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task DiscordPermOverride(CommandOrCrInfo cmd, params GuildPerm[] perms)
if (perms is null || perms.Length == 0)
{
if (perms is null || perms.Length == 0)
{
await _service.RemoveOverride(ctx.Guild.Id, cmd.Name);
await ReplyConfirmLocalizedAsync(strs.perm_override_reset);
return;
}
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()),
Format.Code(cmd.Name)));
await _service.RemoveOverride(ctx.Guild.Id, cmd.Name);
await ReplyConfirmLocalizedAsync(strs.perm_override_reset);
return;
}
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()),
Format.Code(cmd.Name)));
}
[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)));
[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)));
if (!result)
return;
await _service.ClearAllOverrides(ctx.Guild.Id);
if (!result)
return;
await _service.ClearAllOverrides(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync(strs.perm_override_all);
}
await ReplyConfirmLocalizedAsync(strs.perm_override_all);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task DiscordPermOverrideList(int page = 1)
{
if (--page < 0)
return;
[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);
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, true);
}
}
}

View File

@@ -4,38 +4,37 @@ using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Administration.Services;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class GameChannelCommands : NadekoSubmodule<GameVoiceChannelService>
{
[Group]
public class GameChannelCommands : NadekoSubmodule<GameVoiceChannelService>
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.MoveMembers)]
public async Task GameVoiceChannel()
{
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.MoveMembers)]
public async Task GameVoiceChannel()
var vch = ((IGuildUser)ctx.User).VoiceChannel;
if (vch is null)
{
var vch = ((IGuildUser)ctx.User).VoiceChannel;
await ReplyErrorLocalizedAsync(strs.not_in_voice).ConfigureAwait(false);
return;
}
var id = _service.ToggleGameVoiceChannel(ctx.Guild.Id, vch.Id);
if (vch is null)
{
await ReplyErrorLocalizedAsync(strs.not_in_voice).ConfigureAwait(false);
return;
}
var id = _service.ToggleGameVoiceChannel(ctx.Guild.Id, vch.Id);
if (id is null)
{
await ReplyConfirmLocalizedAsync(strs.gvc_disabled).ConfigureAwait(false);
}
else
{
_service.GameVoiceChannels.Add(vch.Id);
await ReplyConfirmLocalizedAsync(strs.gvc_enabled(Format.Bold(vch.Name))).ConfigureAwait(false);
}
if (id is null)
{
await ReplyConfirmLocalizedAsync(strs.gvc_disabled).ConfigureAwait(false);
}
else
{
_service.GameVoiceChannels.Add(vch.Id);
await ReplyConfirmLocalizedAsync(strs.gvc_enabled(Format.Bold(vch.Name))).ConfigureAwait(false);
}
}
}
}
}

View File

@@ -1,132 +1,128 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class LocalizationCommands : NadekoSubmodule
{
[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", "Українська, Україна"}
};
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task LanguageSet()
{
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", "Українська, Україна"}
};
await ReplyConfirmLocalizedAsync(strs.lang_set_show(
Format.Bold(_cultureInfo.ToString()),
Format.Bold(_cultureInfo.NativeName)));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task LanguageSet()
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(1)]
public async Task LanguageSet(string name)
{
try
{
await ReplyConfirmLocalizedAsync(strs.lang_set_show(
Format.Bold(_cultureInfo.ToString()),
Format.Bold(_cultureInfo.NativeName)));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(1)]
public async Task LanguageSet(string name)
{
try
CultureInfo ci;
if (name.Trim().ToLowerInvariant() == "default")
{
CultureInfo ci;
if (name.Trim().ToLowerInvariant() == "default")
{
Localization.RemoveGuildCulture(ctx.Guild);
ci = Localization.DefaultCultureInfo;
}
else
{
ci = new CultureInfo(name);
Localization.SetGuildCulture(ctx.Guild, ci);
}
await ReplyConfirmLocalizedAsync(strs.lang_set(Format.Bold(ci.ToString()),
Format.Bold(ci.NativeName)));
Localization.RemoveGuildCulture(ctx.Guild);
ci = Localization.DefaultCultureInfo;
}
catch (Exception)
else
{
await ReplyErrorLocalizedAsync(strs.lang_set_fail).ConfigureAwait(false);
ci = new CultureInfo(name);
Localization.SetGuildCulture(ctx.Guild, ci);
}
}
[NadekoCommand, Aliases]
public async Task LanguageSetDefault()
{
var cul = Localization.DefaultCultureInfo;
await ReplyErrorLocalizedAsync(strs.lang_set_bot_show(cul, cul.NativeName));
await ReplyConfirmLocalizedAsync(strs.lang_set(Format.Bold(ci.ToString()),
Format.Bold(ci.NativeName)));
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task LanguageSetDefault(string name)
catch (Exception)
{
try
await ReplyErrorLocalizedAsync(strs.lang_set_fail).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
public async Task LanguageSetDefault()
{
var cul = Localization.DefaultCultureInfo;
await ReplyErrorLocalizedAsync(strs.lang_set_bot_show(cul, cul.NativeName));
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task LanguageSetDefault(string name)
{
try
{
CultureInfo ci;
if (name.Trim().ToLowerInvariant() == "default")
{
CultureInfo ci;
if (name.Trim().ToLowerInvariant() == "default")
{
Localization.ResetDefaultCulture();
ci = Localization.DefaultCultureInfo;
}
else
{
ci = new CultureInfo(name);
Localization.SetDefaultCulture(ci);
}
await ReplyConfirmLocalizedAsync(strs.lang_set_bot(Format.Bold(ci.ToString()),
Format.Bold(ci.NativeName)));
Localization.ResetDefaultCulture();
ci = Localization.DefaultCultureInfo;
}
catch (Exception)
else
{
await ReplyErrorLocalizedAsync(strs.lang_set_fail).ConfigureAwait(false);
ci = new CultureInfo(name);
Localization.SetDefaultCulture(ci);
}
}
[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}")))).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.lang_set_bot(Format.Bold(ci.ToString()),
Format.Bold(ci.NativeName)));
}
catch (Exception)
{
await ReplyErrorLocalizedAsync(strs.lang_set_fail).ConfigureAwait(false);
}
}
[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}")))).ConfigureAwait(false);
}
}
}

View File

@@ -6,158 +6,154 @@ using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
[NoPublicBot]
public class LogCommands : NadekoSubmodule<ILogCommandService>
{
[Group]
[NoPublicBot]
public class LogCommands : NadekoSubmodule<ILogCommandService>
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task LogServer(PermissionAction action)
{
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task LogServer(PermissionAction action)
{
await _service.LogServer(ctx.Guild.Id, ctx.Channel.Id, action.Value).ConfigureAwait(false);
if (action.Value)
await ReplyConfirmLocalizedAsync(strs.log_all).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.log_disabled).ConfigureAwait(false);
}
await _service.LogServer(ctx.Guild.Id, ctx.Channel.Id, action.Value).ConfigureAwait(false);
if (action.Value)
await ReplyConfirmLocalizedAsync(strs.log_all).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.log_disabled).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task LogIgnore()
{
var settings = _service.GetGuildLogSettings(ctx.Guild.Id);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task LogIgnore()
{
var settings = _service.GetGuildLogSettings(ctx.Guild.Id);
var chs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.Channel).ToList()
?? new List<IgnoredLogItem>();
var usrs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.User).ToList()
?? new List<IgnoredLogItem>();
var chs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.Channel).ToList()
?? new List<IgnoredLogItem>();
var usrs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.User).ToList()
?? 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}>")));
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}>")));
await ctx.Channel.EmbedAsync(eb);
}
await ctx.Channel.EmbedAsync(eb);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task LogIgnore([Leftover]ITextChannel target)
{
target ??= (ITextChannel)ctx.Channel;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task LogIgnore([Leftover]ITextChannel target)
{
target ??= (ITextChannel)ctx.Channel;
var removed = _service.LogIgnore(ctx.Guild.Id, target.Id, IgnoredItemType.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 + ")"))).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.log_not_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
}
if (!removed)
await ReplyConfirmLocalizedAsync(strs.log_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.log_not_ignore_chan(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task LogIgnore([Leftover]IUser target)
{
var removed = _service.LogIgnore(ctx.Guild.Id, target.Id, IgnoredItemType.User);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
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 + ")"))).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.log_not_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
}
if (!removed)
await ReplyConfirmLocalizedAsync(strs.log_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.log_not_ignore_user(Format.Bold(target.Mention + "(" + target.Id + ")"))).ConfigureAwait(false);
}
[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);
}));
await SendConfirmAsync(Format.Bold(GetText(strs.log_events)) + "\n" +
str)
.ConfigureAwait(false);
}
private static ulong? GetLogProperty(LogSetting l, LogType type)
{
switch (type)
[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 =>
{
case LogType.Other:
return l.LogOtherId;
case LogType.MessageUpdated:
return l.MessageUpdatedId;
case LogType.MessageDeleted:
return l.MessageDeletedId;
case LogType.UserJoined:
return l.UserJoinedId;
case LogType.UserLeft:
return l.UserLeftId;
case LogType.UserBanned:
return l.UserBannedId;
case LogType.UserUnbanned:
return l.UserUnbannedId;
case LogType.UserUpdated:
return l.UserUpdatedId;
case LogType.ChannelCreated:
return l.ChannelCreatedId;
case LogType.ChannelDestroyed:
return l.ChannelDestroyedId;
case LogType.ChannelUpdated:
return l.ChannelUpdatedId;
case LogType.UserPresence:
return l.LogUserPresenceId;
case LogType.VoicePresence:
return l.LogVoicePresenceId;
case LogType.VoicePresenceTTS:
return l.LogVoicePresenceTTSId;
case LogType.UserMuted:
return l.UserMutedId;
default:
return null;
}
}
var val = logSetting is null ? null : GetLogProperty(logSetting, Enum.Parse<LogType>(x));
if (val != null)
return $"{Format.Bold(x)} <#{val}>";
return Format.Bold(x);
}));
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task Log(LogType type)
await SendConfirmAsync(Format.Bold(GetText(strs.log_events)) + "\n" +
str)
.ConfigureAwait(false);
}
private static ulong? GetLogProperty(LogSetting l, LogType type)
{
switch (type)
{
var val = _service.Log(ctx.Guild.Id, ctx.Channel.Id, type);
if (val)
await ReplyConfirmLocalizedAsync(strs.log(Format.Bold(type.ToString()))).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.log_stop(Format.Bold(type.ToString()))).ConfigureAwait(false);
case LogType.Other:
return l.LogOtherId;
case LogType.MessageUpdated:
return l.MessageUpdatedId;
case LogType.MessageDeleted:
return l.MessageDeletedId;
case LogType.UserJoined:
return l.UserJoinedId;
case LogType.UserLeft:
return l.UserLeftId;
case LogType.UserBanned:
return l.UserBannedId;
case LogType.UserUnbanned:
return l.UserUnbannedId;
case LogType.UserUpdated:
return l.UserUpdatedId;
case LogType.ChannelCreated:
return l.ChannelCreatedId;
case LogType.ChannelDestroyed:
return l.ChannelDestroyedId;
case LogType.ChannelUpdated:
return l.ChannelUpdatedId;
case LogType.UserPresence:
return l.LogUserPresenceId;
case LogType.VoicePresence:
return l.LogVoicePresenceId;
case LogType.VoicePresenceTTS:
return l.LogVoicePresenceTTSId;
case LogType.UserMuted:
return l.UserMutedId;
default:
return null;
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task Log(LogType type)
{
var val = _service.Log(ctx.Guild.Id, ctx.Channel.Id, type);
if (val)
await ReplyConfirmLocalizedAsync(strs.log(Format.Bold(type.ToString()))).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.log_stop(Format.Bold(type.ToString()))).ConfigureAwait(false);
}
}
}
}

View File

@@ -3,235 +3,231 @@ using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Modules.Administration.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Extensions;
using Serilog;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class MuteCommands : NadekoSubmodule<MuteService>
{
[Group]
public class MuteCommands : NadekoSubmodule<MuteService>
private async Task<bool> VerifyMutePermissions(IGuildUser runnerUser, IGuildUser targetUser)
{
private async Task<bool> VerifyMutePermissions(IGuildUser runnerUser, IGuildUser targetUser)
var runnerUserRoles = runnerUser.GetRoles();
var targetUserRoles = targetUser.GetRoles();
if (runnerUser.Id != ctx.Guild.OwnerId &&
runnerUserRoles.Max(x => x.Position) <= targetUserRoles.Max(x => x.Position))
{
var runnerUserRoles = runnerUser.GetRoles();
var targetUserRoles = targetUser.GetRoles();
if (runnerUser.Id != ctx.Guild.OwnerId &&
runnerUserRoles.Max(x => x.Position) <= targetUserRoles.Max(x => x.Position))
{
await ReplyErrorLocalizedAsync(strs.mute_perms).ConfigureAwait(false);
return false;
}
return true;
await ReplyErrorLocalizedAsync(strs.mute_perms).ConfigureAwait(false);
return false;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async Task MuteRole([Leftover] IRole role = null)
return true;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async Task MuteRole([Leftover] IRole role = null)
{
if (role is null)
{
if (role is null)
{
var muteRole = await _service.GetMuteRole(ctx.Guild).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.mute_role(Format.Code(muteRole.Name))).ConfigureAwait(false);
return;
}
var muteRole = await _service.GetMuteRole(ctx.Guild).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.mute_role(Format.Code(muteRole.Name))).ConfigureAwait(false);
return;
}
if (ctx.User.Id != ctx.Guild.OwnerId &&
role.Position >= ((SocketGuildUser) ctx.User).Roles.Max(x => x.Position))
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_u).ConfigureAwait(false);
return;
}
if (ctx.User.Id != ctx.Guild.OwnerId &&
role.Position >= ((SocketGuildUser) ctx.User).Roles.Max(x => x.Position))
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_u).ConfigureAwait(false);
return;
}
await _service.SetMuteRoleAsync(ctx.Guild.Id, role.Name).ConfigureAwait(false);
await _service.SetMuteRoleAsync(ctx.Guild.Id, role.Name).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.mute_role_set).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.mute_role_set).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
[Priority(0)]
public async Task Mute(IGuildUser target, [Leftover] string reason = "")
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
[Priority(0)]
public async Task Mute(IGuildUser target, [Leftover] string reason = "")
{
try
{
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, target))
return;
await _service.MuteUser(target, ctx.User, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_muted(Format.Bold(target.ToString()))).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex.ToString());
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
[Priority(1)]
public async Task Mute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
if (!await VerifyMutePermissions((IGuildUser)ctx.User, target))
return;
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.TimedMute(user, ctx.User, time.Time, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_muted_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
}
catch (Exception ex)
{
Log.Warning(ex, "Error in mute command");
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
await _service.MuteUser(target, ctx.User, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_muted(Format.Bold(target.ToString()))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
public async Task Unmute(IGuildUser user, [Leftover] string reason = "")
catch (Exception ex)
{
try
{
await _service.UnmuteUser(user.GuildId, user.Id, ctx.User, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_unmuted(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
Log.Warning(ex.ToString());
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task ChatMute(IGuildUser user, [Leftover] string reason = "")
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
[Priority(1)]
public async Task Mute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
return;
try
{
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.MuteUser(user, ctx.User, MuteType.Chat, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_chat_mute(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex.ToString());
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[Priority(1)]
public async Task ChatMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Chat, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_chat_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
}
catch (Exception ex)
{
Log.Warning(ex.ToString());
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
await _service.TimedMute(user, ctx.User, time.Time, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_muted_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async Task ChatUnmute(IGuildUser user, [Leftover] string reason = "")
catch (Exception ex)
{
try
{
await _service.UnmuteUser(user.Guild.Id, user.Id, ctx.User, MuteType.Chat, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_chat_unmute(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
Log.Warning(ex, "Error in mute command");
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.MuteMembers)]
[Priority(0)]
public async Task VoiceMute(IGuildUser user, [Leftover] string reason = "")
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
public async Task Unmute(IGuildUser user, [Leftover] string reason = "")
{
try
{
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.MuteUser(user, ctx.User, MuteType.Voice, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_voice_mute(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
await _service.UnmuteUser(user.GuildId, user.Id, ctx.User, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_unmuted(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.MuteMembers)]
[Priority(1)]
public async Task VoiceMute(StoopidTime time,IGuildUser user, [Leftover] string reason = "")
catch
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task ChatMute(IGuildUser user, [Leftover] string reason = "")
{
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Voice, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_voice_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
}
catch
{
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
await _service.MuteUser(user, ctx.User, MuteType.Chat, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_chat_mute(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.MuteMembers)]
public async Task VoiceUnmute(IGuildUser user, [Leftover] string reason = "")
catch (Exception ex)
{
try
{
await _service.UnmuteUser(user.GuildId, user.Id, ctx.User, MuteType.Voice, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_voice_unmute(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
Log.Warning(ex.ToString());
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[Priority(1)]
public async Task ChatMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
return;
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Chat, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_chat_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
}
catch (Exception ex)
{
Log.Warning(ex.ToString());
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
[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).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_chat_unmute(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.MuteMembers)]
[Priority(0)]
public async Task VoiceMute(IGuildUser user, [Leftover] string reason = "")
{
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.MuteUser(user, ctx.User, MuteType.Voice, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_voice_mute(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.MuteMembers)]
[Priority(1)]
public async Task VoiceMute(StoopidTime time,IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
return;
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Voice, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_voice_mute_time(Format.Bold(user.ToString()), (int)time.Time.TotalMinutes));
}
catch
{
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
[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).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_voice_unmute(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync(strs.mute_error).ConfigureAwait(false);
}
}
}
}
}

View File

@@ -1,68 +1,66 @@
using Discord.Commands;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Administration.Services;
using Discord;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class PlayingRotateCommands : NadekoSubmodule<PlayingRotateService>
{
[Group]
public class PlayingRotateCommands : NadekoSubmodule<PlayingRotateService>
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task RotatePlaying()
{
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task RotatePlaying()
if (_service.ToggleRotatePlaying())
await ReplyConfirmLocalizedAsync(strs.ropl_enabled).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.ropl_disabled).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task AddPlaying(ActivityType t, [Leftover] string status)
{
await _service.AddPlaying(t, status).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.ropl_added).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task ListPlaying()
{
var statuses = _service.GetRotatingStatuses();
if (!statuses.Any())
{
if (_service.ToggleRotatePlaying())
await ReplyConfirmLocalizedAsync(strs.ropl_enabled).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.ropl_disabled).ConfigureAwait(false);
await ReplyErrorLocalizedAsync(strs.ropl_not_set).ConfigureAwait(false);
}
else
{
var i = 1;
await ReplyConfirmLocalizedAsync(strs.ropl_list(
string.Join("\n\t", statuses.Select(rs => $"`{i++}.` *{rs.Type}* {rs.Status}"))));
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task AddPlaying(ActivityType t, [Leftover] string status)
{
await _service.AddPlaying(t, status).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.ropl_added).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task RemovePlaying(int index)
{
index -= 1;
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task ListPlaying()
{
var statuses = _service.GetRotatingStatuses();
var msg = await _service.RemovePlayingAsync(index).ConfigureAwait(false);
if (!statuses.Any())
{
await ReplyErrorLocalizedAsync(strs.ropl_not_set).ConfigureAwait(false);
}
else
{
var i = 1;
await ReplyConfirmLocalizedAsync(strs.ropl_list(
string.Join("\n\t", statuses.Select(rs => $"`{i++}.` *{rs.Type}* {rs.Status}"))));
}
if (msg is null)
return;
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task RemovePlaying(int index)
{
index -= 1;
var msg = await _service.RemovePlayingAsync(index).ConfigureAwait(false);
if (msg is null)
return;
await ReplyConfirmLocalizedAsync(strs.reprm(msg));
}
await ReplyConfirmLocalizedAsync(strs.reprm(msg));
}
}
}

View File

@@ -3,62 +3,61 @@ using Discord.Commands;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class PrefixCommands : NadekoSubmodule
{
[Group]
public class PrefixCommands : NadekoSubmodule
[NadekoCommand, Aliases]
[Priority(1)]
public async Task PrefixCommand()
{
[NadekoCommand, Aliases]
[Priority(1)]
public async Task PrefixCommand()
await ReplyConfirmLocalizedAsync(strs.prefix_current(Format.Code(CmdHandler.GetPrefix(ctx.Guild)))).ConfigureAwait(false);
}
public enum Set
{
Set
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(0)]
public Task PrefixCommand(Set _, [Leftover] string prefix)
=> PrefixCommand(prefix);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(0)]
public async Task PrefixCommand([Leftover]string prefix)
{
if (string.IsNullOrWhiteSpace(prefix))
return;
var oldPrefix = base.Prefix;
var newPrefix = CmdHandler.SetPrefix(ctx.Guild, prefix);
await ReplyConfirmLocalizedAsync(strs.prefix_new(Format.Code(oldPrefix), Format.Code(newPrefix))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task DefPrefix([Leftover]string prefix = null)
{
if (string.IsNullOrWhiteSpace(prefix))
{
await ReplyConfirmLocalizedAsync(strs.prefix_current(Format.Code(CmdHandler.GetPrefix(ctx.Guild)))).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.defprefix_current(CmdHandler.GetPrefix())).ConfigureAwait(false);
return;
}
public enum Set
{
Set
}
var oldPrefix = CmdHandler.GetPrefix();
var newPrefix = CmdHandler.SetDefaultPrefix(prefix);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(0)]
public Task PrefixCommand(Set _, [Leftover] string prefix)
=> PrefixCommand(prefix);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(0)]
public async Task PrefixCommand([Leftover]string prefix)
{
if (string.IsNullOrWhiteSpace(prefix))
return;
var oldPrefix = base.Prefix;
var newPrefix = CmdHandler.SetPrefix(ctx.Guild, prefix);
await ReplyConfirmLocalizedAsync(strs.prefix_new(Format.Code(oldPrefix), Format.Code(newPrefix))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task DefPrefix([Leftover]string prefix = null)
{
if (string.IsNullOrWhiteSpace(prefix))
{
await ReplyConfirmLocalizedAsync(strs.defprefix_current(CmdHandler.GetPrefix())).ConfigureAwait(false);
return;
}
var oldPrefix = CmdHandler.GetPrefix();
var newPrefix = CmdHandler.SetDefaultPrefix(prefix);
await ReplyConfirmLocalizedAsync(strs.defprefix_new(Format.Code(oldPrefix), Format.Code(newPrefix))).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.defprefix_new(Format.Code(oldPrefix), Format.Code(newPrefix))).ConfigureAwait(false);
}
}
}
}

View File

@@ -1,294 +1,291 @@
using System;
using Discord;
using Discord;
using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Common;
using NadekoBot.Modules.Administration.Services;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Common.TypeReaders.Models;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class ProtectionCommands : NadekoSubmodule<ProtectionService>
{
[Group]
public class ProtectionCommands : NadekoSubmodule<ProtectionService>
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task AntiAlt()
{
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task AntiAlt()
if (await _service.TryStopAntiAlt(ctx.Guild.Id))
{
if (await _service.TryStopAntiAlt(ctx.Guild.Id))
{
await ReplyErrorLocalizedAsync(strs.prot_disable("Anti-Alt"));
return;
}
await ReplyConfirmLocalizedAsync(strs.protection_not_running("Anti-Alt"));
await ReplyErrorLocalizedAsync(strs.prot_disable("Anti-Alt"));
return;
}
[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;
await ReplyConfirmLocalizedAsync(strs.protection_not_running("Anti-Alt"));
}
if (minAgeMinutes < 1 || punishTimeMinutes < 0)
return;
[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;
await _service.StartAntiAltAsync(ctx.Guild.Id, minAgeMinutes, action, (int?)punishTime?.Time.TotalMinutes ?? 0);
if (minAgeMinutes < 1 || punishTimeMinutes < 0)
return;
await ctx.OkAsync();
}
await _service.StartAntiAltAsync(ctx.Guild.Id, minAgeMinutes, action, (int?)punishTime?.Time.TotalMinutes ?? 0);
await ctx.OkAsync();
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover]IRole role)
{
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover]IRole role)
{
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
if (minAgeMinutes < 1)
return;
if (minAgeMinutes < 1)
return;
await _service.StartAntiAltAsync(ctx.Guild.Id, minAgeMinutes, action, roleId: role.Id);
await _service.StartAntiAltAsync(ctx.Guild.Id, minAgeMinutes, action, roleId: role.Id);
await ctx.OkAsync();
}
await ctx.OkAsync();
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public Task AntiRaid()
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public Task AntiRaid()
{
if (_service.TryStopAntiRaid(ctx.Guild.Id))
{
if (_service.TryStopAntiRaid(ctx.Guild.Id))
{
return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Raid"));
}
else
{
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Raid"));
}
return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Raid"));
}
[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);
[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)
else
{
if (action == PunishmentAction.AddRole)
{
await ReplyErrorLocalizedAsync(strs.punishment_unsupported(action));
return;
}
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Raid"));
}
}
[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);
[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)
{
if (action == PunishmentAction.AddRole)
{
await ReplyErrorLocalizedAsync(strs.punishment_unsupported(action));
return;
}
if (userThreshold < 2 || userThreshold > 30)
{
await ReplyErrorLocalizedAsync(strs.raid_cnt(2, 30));
return;
}
if (seconds < 2 || seconds > 300)
{
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;
if (time < 0 || time > 60 * 24)
return;
var stats = await _service.StartAntiRaidAsync(ctx.Guild.Id, userThreshold, seconds,
action, time).ConfigureAwait(false);
if (stats is null)
{
return;
}
await SendConfirmAsync(GetText(strs.prot_enable("Anti-Raid")),
$"{ctx.User.Mention} {GetAntiRaidString(stats)}")
.ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public Task AntiSpam()
if (userThreshold < 2 || userThreshold > 30)
{
if (_service.TryStopAntiSpam(ctx.Guild.Id))
await ReplyErrorLocalizedAsync(strs.raid_cnt(2, 30));
return;
}
if (seconds < 2 || seconds > 300)
{
await ReplyErrorLocalizedAsync(strs.raid_time(2, 300));
return;
}
if (punishTime is not null)
{
if (!_service.IsDurationAllowed(action))
{
return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Spam"));
}
else
{
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Spam"));
await ReplyErrorLocalizedAsync(strs.prot_cant_use_time);
}
}
var time = (int?) punishTime?.Time.TotalMinutes ?? 0;
if (time < 0 || time > 60 * 24)
return;
var stats = await _service.StartAntiRaidAsync(ctx.Guild.Id, userThreshold, seconds,
action, time).ConfigureAwait(false);
if (stats is null)
{
return;
}
await SendConfirmAsync(GetText(strs.prot_enable("Anti-Raid")),
$"{ctx.User.Mention} {GetAntiRaidString(stats)}")
.ConfigureAwait(false);
}
[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"));
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(0)]
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] IRole role)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(0)]
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] IRole role)
{
if (action != PunishmentAction.AddRole)
return Task.CompletedTask;
return InternalAntiSpam(messageCount, action, null, role);
}
[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);
[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)
{
if (messageCount < 2 || messageCount > 10)
return;
if (timeData is not null)
{
if (action != PunishmentAction.AddRole)
return Task.CompletedTask;
return InternalAntiSpam(messageCount, action, null, role);
}
[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);
[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)
{
if (messageCount < 2 || messageCount > 10)
return;
if (timeData is not null)
if (!_service.IsDurationAllowed(action))
{
if (!_service.IsDurationAllowed(action))
{
await ReplyErrorLocalizedAsync(strs.prot_cant_use_time);
}
await ReplyErrorLocalizedAsync(strs.prot_cant_use_time);
}
var time = (int?) timeData?.Time.TotalMinutes ?? 0;
if (time < 0 || time > 60 * 24)
return;
var stats = await _service.StartAntiSpamAsync(ctx.Guild.Id, messageCount, action, time, role?.Id).ConfigureAwait(false);
await SendConfirmAsync(GetText(strs.prot_enable("Anti-Spam")),
$"{ctx.User.Mention} {GetAntiSpamString(stats)}").ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task AntispamIgnore()
var time = (int?) timeData?.Time.TotalMinutes ?? 0;
if (time < 0 || time > 60 * 24)
return;
var stats = await _service.StartAntiSpamAsync(ctx.Guild.Id, messageCount, action, time, role?.Id).ConfigureAwait(false);
await SendConfirmAsync(GetText(strs.prot_enable("Anti-Spam")),
$"{ctx.User.Mention} {GetAntiSpamString(stats)}").ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task AntispamIgnore()
{
var added = await _service.AntiSpamIgnoreAsync(ctx.Guild.Id, ctx.Channel.Id).ConfigureAwait(false);
if(added is null)
{
var added = await _service.AntiSpamIgnoreAsync(ctx.Guild.Id, ctx.Channel.Id).ConfigureAwait(false);
if(added is null)
{
await ReplyErrorLocalizedAsync(strs.protection_not_running("Anti-Spam"));
return;
}
if (added.Value)
await ReplyConfirmLocalizedAsync(strs.spam_ignore("Anti-Spam"));
else
await ReplyConfirmLocalizedAsync(strs.spam_not_ignore("Anti-Spam"));
await ReplyErrorLocalizedAsync(strs.protection_not_running("Anti-Spam"));
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AntiList()
if (added.Value)
await ReplyConfirmLocalizedAsync(strs.spam_ignore("Anti-Spam"));
else
await ReplyConfirmLocalizedAsync(strs.spam_not_ignore("Anti-Spam"));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AntiList()
{
var (spam, raid, alt) = _service.GetAntiStats(ctx.Guild.Id);
if (spam is null && raid is null && alt is null)
{
var (spam, raid, alt) = _service.GetAntiStats(ctx.Guild.Id);
if (spam is null && raid is null && alt is null)
{
await ReplyConfirmLocalizedAsync(strs.prot_none).ConfigureAwait(false);
return;
}
var embed = _eb.Create().WithOkColor()
.WithTitle(GetText(strs.prot_active));
if (spam != null)
embed.AddField("Anti-Spam", GetAntiSpamString(spam).TrimTo(1024), true);
if (raid != null)
embed.AddField("Anti-Raid", GetAntiRaidString(raid).TrimTo(1024), true);
if (alt is not null)
embed.AddField("Anti-Alt", GetAntiAltString(alt), true);
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.prot_none).ConfigureAwait(false);
return;
}
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())));
var embed = _eb.Create().WithOkColor()
.WithTitle(GetText(strs.prot_active));
private string GetAntiSpamString(AntiSpamStats stats)
if (spam != null)
embed.AddField("Anti-Spam", GetAntiSpamString(spam).TrimTo(1024), true);
if (raid != null)
embed.AddField("Anti-Raid", GetAntiRaidString(raid).TrimTo(1024), true);
if (alt is not null)
embed.AddField("Anti-Alt", GetAntiAltString(alt), true);
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
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())));
private string GetAntiSpamString(AntiSpamStats stats)
{
var settings = stats.AntiSpamSettings;
var ignoredString = string.Join(", ", settings.IgnoredChannels.Select(c => $"<#{c.ChannelId}>"));
if (string.IsNullOrWhiteSpace(ignoredString))
ignoredString = "none";
string add = "";
if (settings.MuteTime > 0)
{
var settings = stats.AntiSpamSettings;
var ignoredString = string.Join(", ", settings.IgnoredChannels.Select(c => $"<#{c.ChannelId}>"));
if (string.IsNullOrWhiteSpace(ignoredString))
ignoredString = "none";
string add = "";
if (settings.MuteTime > 0)
{
add = $" ({TimeSpan.FromMinutes(settings.MuteTime):hh\\hmm\\m})";
}
return GetText(strs.spam_stats(
Format.Bold(settings.MessageThreshold.ToString()),
Format.Bold(settings.Action + add),
ignoredString));
add = $" ({TimeSpan.FromMinutes(settings.MuteTime):hh\\hmm\\m})";
}
private string GetAntiRaidString(AntiRaidStats stats)
return GetText(strs.spam_stats(
Format.Bold(settings.MessageThreshold.ToString()),
Format.Bold(settings.Action + add),
ignoredString));
}
private string GetAntiRaidString(AntiRaidStats stats)
{
var actionString = Format.Bold(stats.AntiRaidSettings.Action.ToString());
if (stats.AntiRaidSettings.PunishDuration > 0)
{
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()),
Format.Bold(stats.AntiRaidSettings.Seconds.ToString()),
actionString));
actionString += $" **({TimeSpan.FromMinutes(stats.AntiRaidSettings.PunishDuration):hh\\hmm\\m})**";
}
return GetText(strs.raid_stats(
Format.Bold(stats.AntiRaidSettings.UserThreshold.ToString()),
Format.Bold(stats.AntiRaidSettings.Seconds.ToString()),
actionString));
}
}
}

View File

@@ -1,85 +1,83 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using System;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Administration.Services;
using ITextChannel = Discord.ITextChannel;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class PruneCommands : NadekoSubmodule<PruneService>
{
[Group]
public class PruneCommands : NadekoSubmodule<PruneService>
private static readonly TimeSpan twoWeeks = TimeSpan.FromDays(14);
//delets her own messages, no perm required
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Prune(string parameter = null)
{
private static readonly TimeSpan twoWeeks = TimeSpan.FromDays(14);
var user = await ctx.Guild.GetCurrentUserAsync().ConfigureAwait(false);
//delets her own messages, no perm required
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Prune(string parameter = null)
{
var user = await ctx.Guild.GetCurrentUserAsync().ConfigureAwait(false);
if (parameter == "-s" || parameter == "--safe")
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, (x) => x.Author.Id == user.Id && !x.IsPinned).ConfigureAwait(false);
else
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, (x) => x.Author.Id == user.Id).ConfigureAwait(false);
ctx.Message.DeleteAfter(3);
}
// prune x
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
[Priority(1)]
public async Task Prune(int count, string parameter = null)
{
count++;
if (count < 1)
return;
if (count > 1000)
count = 1000;
if (parameter == "-s" || parameter == "--safe")
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, (x) => x.Author.Id == user.Id && !x.IsPinned).ConfigureAwait(false);
else
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, (x) => x.Author.Id == user.Id).ConfigureAwait(false);
ctx.Message.DeleteAfter(3);
}
// prune x
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
[Priority(1)]
public async Task Prune(int count, string parameter = null)
{
if (parameter == "-s" || parameter == "--safe")
await _service.PruneWhere((ITextChannel)ctx.Channel, count, (x) => !x.IsPinned).ConfigureAwait(false);
else
await _service.PruneWhere((ITextChannel)ctx.Channel, count, x => true).ConfigureAwait(false);
}
//prune @user [x]
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
[Priority(0)]
public Task Prune(IGuildUser user, int count = 100, string parameter = null)
=> Prune(user.Id, count, parameter);
//prune userid [x]
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
[Priority(0)]
public async Task Prune(ulong userId, int count = 100, string parameter = null)
{
if (userId == ctx.User.Id)
count++;
if (count < 1)
return;
if (count > 1000)
count = 1000;
if (parameter == "-s" || parameter == "--safe")
await _service.PruneWhere((ITextChannel)ctx.Channel, count, (x) => !x.IsPinned).ConfigureAwait(false);
else
await _service.PruneWhere((ITextChannel)ctx.Channel, count, x => true).ConfigureAwait(false);
}
if (count < 1)
return;
//prune @user [x]
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
[Priority(0)]
public Task Prune(IGuildUser user, int count = 100, string parameter = null)
=> Prune(user.Id, count, parameter);
if (count > 1000)
count = 1000;
//prune userid [x]
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
[Priority(0)]
public async Task Prune(ulong userId, int count = 100, string parameter = null)
{
if (userId == ctx.User.Id)
count++;
if (count < 1)
return;
if (count > 1000)
count = 1000;
if (parameter == "-s" || parameter == "--safe")
await _service.PruneWhere((ITextChannel)ctx.Channel, count, m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks && !m.IsPinned).ConfigureAwait(false);
else
await _service.PruneWhere((ITextChannel)ctx.Channel, count, m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks).ConfigureAwait(false);
}
if (parameter == "-s" || parameter == "--safe")
await _service.PruneWhere((ITextChannel)ctx.Channel, count, m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks && !m.IsPinned).ConfigureAwait(false);
else
await _service.PruneWhere((ITextChannel)ctx.Channel, count, m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < twoWeeks).ConfigureAwait(false);
}
}
}
}

View File

@@ -7,83 +7,80 @@ using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Serilog;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
public class RoleCommands : NadekoSubmodule<RoleCommandsService>
{
public class RoleCommands : NadekoSubmodule<RoleCommandsService>
private IServiceProvider _services;
public enum Exclude { Excl }
public RoleCommands(IServiceProvider services)
{
private IServiceProvider _services;
public enum Exclude { Excl }
_services = services;
}
public RoleCommands(IServiceProvider services)
{
_services = services;
}
public async Task InternalReactionRoles(bool exclusive, ulong? messageId, params string[] input)
{
var target = messageId is ulong msgId
? await ctx.Channel.GetMessageAsync(msgId).ConfigureAwait(false)
: (await ctx.Channel.GetMessagesAsync(2).FlattenAsync().ConfigureAwait(false))
.Skip(1)
.FirstOrDefault();
public async Task InternalReactionRoles(bool exclusive, ulong? messageId, params string[] input)
{
var target = messageId is ulong msgId
? await ctx.Channel.GetMessageAsync(msgId).ConfigureAwait(false)
: (await ctx.Channel.GetMessagesAsync(2).FlattenAsync().ConfigureAwait(false))
.Skip(1)
.FirstOrDefault();
if (input.Length % 2 != 0)
return;
if (input.Length % 2 != 0)
return;
var grp = 0;
var results = input
.GroupBy(x => grp++ / 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);
var all = await Task.WhenAll(results);
if (!all.Any())
return;
foreach (var x in all)
var grp = 0;
var results = input
.GroupBy(x => grp++ / 2)
.Select(async x =>
{
try
var inputRoleStr = x.First();
var roleReader = new RoleTypeReader<SocketRole>();
var roleResult = await roleReader.ReadAsync(ctx, inputRoleStr, _services);
if (!roleResult.IsSuccess)
{
await target.AddReactionAsync(x.emote, new RequestOptions()
{
RetryMode = RetryMode.Retry502 | RetryMode.RetryRatelimit
}).ConfigureAwait(false);
}
catch (Discord.Net.HttpException ex) when(ex.HttpCode == HttpStatusCode.BadRequest)
{
await ReplyErrorLocalizedAsync(strs.reaction_cant_access(Format.Code(x.emote.ToString())));
return;
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);
await Task.Delay(500).ConfigureAwait(false);
var all = await Task.WhenAll(results);
if (!all.Any())
return;
foreach (var x in all)
{
try
{
await target.AddReactionAsync(x.emote, new RequestOptions()
{
RetryMode = RetryMode.Retry502 | RetryMode.RetryRatelimit
}).ConfigureAwait(false);
}
catch (Discord.Net.HttpException ex) when(ex.HttpCode == HttpStatusCode.BadRequest)
{
await ReplyErrorLocalizedAsync(strs.reaction_cant_access(Format.Code(x.emote.ToString())));
return;
}
if (_service.Add(ctx.Guild.Id, new ReactionRoleMessage()
await Task.Delay(500).ConfigureAwait(false);
}
if (_service.Add(ctx.Guild.Id, new ReactionRoleMessage()
{
Exclusive = exclusive,
MessageId = target.Id,
@@ -97,267 +94,266 @@ namespace NadekoBot.Modules.Administration
};
}).ToList(),
}))
{
await ctx.OkAsync();
}
else
{
await ReplyErrorLocalizedAsync(strs.reaction_roles_full).ConfigureAwait(false);
}
{
await ctx.OkAsync();
}
else
{
await ReplyErrorLocalizedAsync(strs.reaction_roles_full).ConfigureAwait(false);
}
}
[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]
[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]
[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]
[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]
[RequireContext(ContextType.Guild)]
[NoPublicBot]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public Task ReactionRoles(params string[] input) =>
InternalReactionRoles(false, null, 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);
[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);
[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);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[NoPublicBot]
[UserPerm(GuildPerm.ManageRoles)]
public async Task ReactionRolesList()
[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));
}
else
{
var g = ((SocketGuild)ctx.Guild);
foreach (var rr in rrs)
{
embed.WithDescription(GetText(strs.no_reaction_roles));
}
else
{
var g = ((SocketGuild)ctx.Guild);
foreach (var rr in rrs)
var ch = g.GetTextChannel(rr.ChannelId);
IUserMessage msg = null;
if (ch is not null)
{
var ch = g.GetTextChannel(rr.ChannelId);
IUserMessage msg = null;
if (ch is not null)
{
msg = await ch.GetMessageAsync(rr.MessageId).ConfigureAwait(false) 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)));
msg = await ch.GetMessageAsync(rr.MessageId).ConfigureAwait(false) 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).ConfigureAwait(false);
}
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[NoPublicBot]
[UserPerm(GuildPerm.ManageRoles)]
public async Task ReactionRolesRemove(int index)
[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]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task SetRole(IGuildUser targetUser, [Leftover] IRole roleToAdd)
{
var runnerUser = (IGuildUser)ctx.User;
var runnerMaxRolePosition = runnerUser.GetRoles().Max(x => x.Position);
if ((ctx.User.Id != ctx.Guild.OwnerId) && runnerMaxRolePosition <= roleToAdd.Position)
return;
try
{
await targetUser.AddRoleAsync(roleToAdd).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(
strs.setrole(Format.Bold(roleToAdd.Name),
Format.Bold(targetUser.ToString())));
}
catch (Exception ex)
{
Log.Warning(ex, "Error in setrole command");
await ReplyErrorLocalizedAsync(strs.setrole_err).ConfigureAwait(false);
}
}
[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)
return;
try
{
await targetUser.RemoveRoleAsync(roleToRemove).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.remrole(Format.Bold(roleToRemove.Name), Format.Bold(targetUser.ToString()))).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync(strs.remrole_err).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
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)
return;
try
{
if (roleToEdit.Position > (await ctx.Guild.GetCurrentUserAsync().ConfigureAwait(false)).GetRoles().Max(r => r.Position))
{
await ReplyErrorLocalizedAsync(strs.renrole_perms).ConfigureAwait(false);
return;
}
index--;
var rr = rrs[index];
_service.Remove(ctx.Guild.Id, index);
await ReplyConfirmLocalizedAsync(strs.reaction_role_removed(index + 1));
await roleToEdit.ModifyAsync(g => g.Name = newname).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.renrole).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task SetRole(IGuildUser targetUser, [Leftover] IRole roleToAdd)
catch (Exception)
{
var runnerUser = (IGuildUser)ctx.User;
var runnerMaxRolePosition = runnerUser.GetRoles().Max(x => x.Position);
if ((ctx.User.Id != ctx.Guild.OwnerId) && runnerMaxRolePosition <= roleToAdd.Position)
return;
try
{
await targetUser.AddRoleAsync(roleToAdd).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(
strs.setrole(Format.Bold(roleToAdd.Name),
Format.Bold(targetUser.ToString())));
}
catch (Exception ex)
{
Log.Warning(ex, "Error in setrole command");
await ReplyErrorLocalizedAsync(strs.setrole_err).ConfigureAwait(false);
}
await ReplyErrorLocalizedAsync(strs.renrole_err).ConfigureAwait(false);
}
}
[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)
return;
try
{
await targetUser.RemoveRoleAsync(roleToRemove).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.remrole(Format.Bold(roleToRemove.Name), Format.Bold(targetUser.ToString()))).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync(strs.remrole_err).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task RemoveAllRoles([Leftover] IGuildUser user)
{
var guser = (IGuildUser)ctx.User;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
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)
return;
try
{
if (roleToEdit.Position > (await ctx.Guild.GetCurrentUserAsync().ConfigureAwait(false)).GetRoles().Max(r => r.Position))
{
await ReplyErrorLocalizedAsync(strs.renrole_perms).ConfigureAwait(false);
return;
}
await roleToEdit.ModifyAsync(g => g.Name = newname).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.renrole).ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalizedAsync(strs.renrole_err).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task RemoveAllRoles([Leftover] IGuildUser user)
{
var guser = (IGuildUser)ctx.User;
var userRoles = user.GetRoles()
.Where(x => !x.IsManaged && x != x.Guild.EveryoneRole)
.ToList();
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
{
await user.RemoveRolesAsync(userRoles).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.rar(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalizedAsync(strs.rar_err).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task CreateRole([Leftover] string roleName = null)
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
{
if (string.IsNullOrWhiteSpace(roleName))
return;
var r = await ctx.Guild.CreateRoleAsync(roleName, isMentionable: false).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.cr(Format.Bold(r.Name))).ConfigureAwait(false);
await user.RemoveRolesAsync(userRoles).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.rar(Format.Bold(user.ToString()))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task DeleteRole([Leftover] IRole role)
catch (Exception)
{
var guser = (IGuildUser)ctx.User;
if (ctx.User.Id != guser.Guild.OwnerId
&& guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
await role.DeleteAsync().ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.dr(Format.Bold(role.Name))).ConfigureAwait(false);
await ReplyErrorLocalizedAsync(strs.rar_err).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task RoleHoist(IRole role)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task CreateRole([Leftover] string roleName = null)
{
if (string.IsNullOrWhiteSpace(roleName))
return;
var r = await ctx.Guild.CreateRoleAsync(roleName, isMentionable: false).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.cr(Format.Bold(r.Name))).ConfigureAwait(false);
}
[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)
return;
await role.DeleteAsync().ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.dr(Format.Bold(role.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task RoleHoist(IRole role)
{
var newHoisted = !role.IsHoisted;
await role.ModifyAsync(r => r.Hoist = newHoisted).ConfigureAwait(false);
if (newHoisted)
{
var newHoisted = !role.IsHoisted;
await role.ModifyAsync(r => r.Hoist = newHoisted).ConfigureAwait(false);
if (newHoisted)
{
await ReplyConfirmLocalizedAsync(strs.rolehoist_enabled(Format.Bold(role.Name))).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.rolehoist_disabled(Format.Bold(role.Name))).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.rolehoist_enabled(Format.Bold(role.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task RoleColor([Leftover] IRole role)
else
{
await SendConfirmAsync("Role Color", role.Color.RawValue.ToString("x6")).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.rolehoist_disabled(Format.Bold(role.Name))).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task RoleColor(SixLabors.ImageSharp.Color color, [Leftover]IRole role)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task RoleColor([Leftover] IRole role)
{
await SendConfirmAsync("Role Color", role.Color.RawValue.ToString("x6")).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task RoleColor(SixLabors.ImageSharp.Color color, [Leftover]IRole role)
{
try
{
try
{
var rgba32 = color.ToPixel<Rgba32>();
await role.ModifyAsync(r => r.Color = new Color(rgba32.R, rgba32.G, rgba32.B)).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.rc(Format.Bold(role.Name))).ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalizedAsync(strs.rc_perms).ConfigureAwait(false);
}
var rgba32 = color.ToPixel<Rgba32>();
await role.ModifyAsync(r => r.Color = new Color(rgba32.R, rgba32.G, rgba32.B)).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.rc(Format.Bold(role.Name))).ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalizedAsync(strs.rc_perms).ConfigureAwait(false);
}
}
}

View File

@@ -1,5 +1,4 @@
using System.Linq;
using System.Text;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
@@ -7,265 +6,264 @@ using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Administration.Services;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class SelfAssignedRolesCommands : NadekoSubmodule<SelfAssignedRolesService>
{
[Group]
public class SelfAssignedRolesCommands : NadekoSubmodule<SelfAssignedRolesService>
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[BotPerm(GuildPerm.ManageMessages)]
public async Task AdSarm()
{
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[BotPerm(GuildPerm.ManageMessages)]
public async Task AdSarm()
{
var newVal = _service.ToggleAdSarm(ctx.Guild.Id);
var newVal = _service.ToggleAdSarm(ctx.Guild.Id);
if (newVal)
{
await ReplyConfirmLocalizedAsync(strs.adsarm_enable(Prefix));
}
else
{
await ReplyConfirmLocalizedAsync(strs.adsarm_disable(Prefix));
}
if (newVal)
{
await ReplyConfirmLocalizedAsync(strs.adsarm_enable(Prefix));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(1)]
public Task Asar([Leftover] IRole role) =>
Asar(0, role);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task Asar(int group, [Leftover] IRole role)
else
{
var guser = (IGuildUser)ctx.User;
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
var succ = _service.AddNew(ctx.Guild.Id, role, group);
if (succ)
{
await ReplyConfirmLocalizedAsync(strs.role_added(Format.Bold(role.Name), Format.Bold(group.ToString()))).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync(strs.role_in_list(Format.Bold(role.Name))).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.adsarm_disable(Prefix));
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task Sargn(int group, [Leftover] string name = null)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(1)]
public Task Asar([Leftover] IRole role) =>
Asar(0, role);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task Asar(int group, [Leftover] IRole role)
{
var guser = (IGuildUser)ctx.User;
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
var succ = _service.AddNew(ctx.Guild.Id, role, group);
if (succ)
{
var guser = (IGuildUser)ctx.User;
var set = await _service.SetNameAsync(ctx.Guild.Id, group, name).ConfigureAwait(false);
if (set)
{
await ReplyConfirmLocalizedAsync(strs.group_name_added(Format.Bold(group.ToString()), Format.Bold(name.ToString()))).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.group_name_removed(Format.Bold(group.ToString()))).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.role_added(Format.Bold(role.Name), Format.Bold(group.ToString()))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async Task Rsar([Leftover] IRole role)
else
{
var guser = (IGuildUser)ctx.User;
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
bool success = _service.RemoveSar(role.Guild.Id, role.Id);
if (!success)
{
await ReplyErrorLocalizedAsync(strs.self_assign_not).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.self_assign_rem(Format.Bold(role.Name))).ConfigureAwait(false);
}
await ReplyErrorLocalizedAsync(strs.role_in_list(Format.Bold(role.Name))).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Lsar(int page = 1)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task Sargn(int group, [Leftover] string name = null)
{
var guser = (IGuildUser)ctx.User;
var set = await _service.SetNameAsync(ctx.Guild.Id, group, name).ConfigureAwait(false);
if (set)
{
if (--page < 0)
return;
await ReplyConfirmLocalizedAsync(strs.group_name_added(Format.Bold(group.ToString()), Format.Bold(name.ToString()))).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.group_name_removed(Format.Bold(group.ToString()))).ConfigureAwait(false);
}
}
var (exclusive, roles, groups) = _service.GetRoles(ctx.Guild);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async Task Rsar([Leftover] IRole role)
{
var guser = (IGuildUser)ctx.User;
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
await ctx.SendPaginatedConfirmAsync(page, (cur) =>
bool success = _service.RemoveSar(role.Guild.Id, role.Id);
if (!success)
{
await ReplyErrorLocalizedAsync(strs.self_assign_not).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.self_assign_rem(Format.Bold(role.Name))).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Lsar(int page = 1)
{
if (--page < 0)
return;
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)
{
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)
var groupNameText = "";
if (!groups.TryGetValue(kvp.Key, out var name))
{
var groupNameText = "";
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)}");
}
rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫");
foreach (var (Model, Role) in kvp.AsEnumerable())
{
if (Role is null)
{
groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key)));
continue;
}
else
{
groupNameText = Format.Bold($"{kvp.Key} - {name.TrimTo(25, true)}");
}
rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫");
foreach (var (Model, Role) in kvp.AsEnumerable())
{
if (Role is null)
{
continue;
}
// first character is invisible space
if (Model.LevelRequirement == 0)
rolesStr.AppendLine(" " + Role.Name);
else
{
// first character is invisible space
if (Model.LevelRequirement == 0)
rolesStr.AppendLine(" " + Role.Name);
else
rolesStr.AppendLine(" " + Role.Name + $" (lvl {Model.LevelRequirement}+)");
}
rolesStr.AppendLine(" " + Role.Name + $" (lvl {Model.LevelRequirement}+)");
}
rolesStr.AppendLine();
}
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).ConfigureAwait(false);
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).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task Togglexclsar()
{
bool areExclusive = _service.ToggleEsar(ctx.Guild.Id);
if (areExclusive)
await ReplyConfirmLocalizedAsync(strs.self_assign_excl).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.self_assign_no_excl).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task RoleLevelReq(int level, [Leftover] IRole role)
{
if (level < 0)
return;
bool succ = _service.SetLevelReq(ctx.Guild.Id, role, level);
if (!succ)
{
await ReplyErrorLocalizedAsync(strs.self_assign_not).ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task Togglexclsar()
await ReplyConfirmLocalizedAsync(strs.self_assign_level_req(
Format.Bold(role.Name),
Format.Bold(level.ToString())));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Iam([Leftover] IRole role)
{
var guildUser = (IGuildUser)ctx.User;
var (result, autoDelete, extra) = await _service.Assign(guildUser, role).ConfigureAwait(false);
IUserMessage msg;
if (result == SelfAssignedRolesService.AssignResult.Err_Not_Assignable)
{
bool areExclusive = _service.ToggleEsar(ctx.Guild.Id);
if (areExclusive)
await ReplyConfirmLocalizedAsync(strs.self_assign_excl).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.self_assign_no_excl).ConfigureAwait(false);
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.AssignResult.Err_Lvl_Req)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not_level(Format.Bold(extra.ToString()))).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.AssignResult.Err_Already_Have)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_already(Format.Bold(role.Name))).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.AssignResult.Err_Not_Perms)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_perms).ConfigureAwait(false);
}
else
{
msg = await ReplyConfirmLocalizedAsync(strs.self_assign_success(Format.Bold(role.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task RoleLevelReq(int level, [Leftover] IRole role)
if (autoDelete)
{
if (level < 0)
return;
msg.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
}
bool succ = _service.SetLevelReq(ctx.Guild.Id, role, level);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Iamnot([Leftover] IRole role)
{
var guildUser = (IGuildUser)ctx.User;
if (!succ)
{
await ReplyErrorLocalizedAsync(strs.self_assign_not).ConfigureAwait(false);
return;
}
var (result, autoDelete) = await _service.Remove(guildUser, role).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.self_assign_level_req(
Format.Bold(role.Name),
Format.Bold(level.ToString())));
IUserMessage msg;
if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Assignable)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Have)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not_have(Format.Bold(role.Name))).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Perms)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_perms).ConfigureAwait(false);
}
else
{
msg = await ReplyConfirmLocalizedAsync(strs.self_assign_remove(Format.Bold(role.Name))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Iam([Leftover] IRole role)
if (autoDelete)
{
var guildUser = (IGuildUser)ctx.User;
var (result, autoDelete, extra) = await _service.Assign(guildUser, role).ConfigureAwait(false);
IUserMessage msg;
if (result == SelfAssignedRolesService.AssignResult.Err_Not_Assignable)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.AssignResult.Err_Lvl_Req)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not_level(Format.Bold(extra.ToString()))).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.AssignResult.Err_Already_Have)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_already(Format.Bold(role.Name))).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.AssignResult.Err_Not_Perms)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_perms).ConfigureAwait(false);
}
else
{
msg = await ReplyConfirmLocalizedAsync(strs.self_assign_success(Format.Bold(role.Name))).ConfigureAwait(false);
}
if (autoDelete)
{
msg.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Iamnot([Leftover] IRole role)
{
var guildUser = (IGuildUser)ctx.User;
var (result, autoDelete) = await _service.Remove(guildUser, role).ConfigureAwait(false);
IUserMessage msg;
if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Assignable)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Have)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_not_have(Format.Bold(role.Name))).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Perms)
{
msg = await ReplyErrorLocalizedAsync(strs.self_assign_perms).ConfigureAwait(false);
}
else
{
msg = await ReplyConfirmLocalizedAsync(strs.self_assign_remove(Format.Bold(role.Name))).ConfigureAwait(false);
}
if (autoDelete)
{
msg.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
msg.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
}
}

View File

@@ -2,118 +2,114 @@ using Discord;
using Discord.Commands;
using Discord.Net;
using Discord.WebSocket;
using NadekoBot.Common;
using NadekoBot.Common.Attributes;
using NadekoBot.Common.Replacements;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Services;
using Serilog;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class SelfCommands : NadekoSubmodule<SelfService>
{
[Group]
public class SelfCommands : NadekoSubmodule<SelfService>
private readonly DiscordSocketClient _client;
private readonly IBotStrings _strings;
private readonly ICoordinator _coord;
public SelfCommands(DiscordSocketClient client, IBotStrings strings, ICoordinator coord)
{
private readonly DiscordSocketClient _client;
private readonly IBotStrings _strings;
private readonly ICoordinator _coord;
_client = client;
_strings = strings;
_coord = coord;
}
public SelfCommands(DiscordSocketClient client, IBotStrings strings, ICoordinator coord)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task StartupCommandAdd([Leftover] string cmdText)
{
if (cmdText.StartsWith(Prefix + "die", StringComparison.InvariantCulture))
return;
var guser = (IGuildUser)ctx.User;
var cmd = new AutoCommand()
{
_client = client;
_strings = strings;
_coord = coord;
}
CommandText = cmdText,
ChannelId = ctx.Channel.Id,
ChannelName = ctx.Channel.Name,
GuildId = ctx.Guild?.Id,
GuildName = ctx.Guild?.Name,
VoiceChannelId = guser.VoiceChannel?.Id,
VoiceChannelName = guser.VoiceChannel?.Name,
Interval = 0,
};
_service.AddNewAutoCommand(cmd);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task StartupCommandAdd([Leftover] string cmdText)
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));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task AutoCommandAdd(int interval, [Leftover] string cmdText)
{
if (cmdText.StartsWith(Prefix + "die", StringComparison.InvariantCulture))
return;
if (interval < 5)
return;
var guser = (IGuildUser)ctx.User;
var cmd = new AutoCommand()
{
if (cmdText.StartsWith(Prefix + "die", StringComparison.InvariantCulture))
return;
CommandText = cmdText,
ChannelId = ctx.Channel.Id,
ChannelName = ctx.Channel.Name,
GuildId = ctx.Guild?.Id,
GuildName = ctx.Guild?.Name,
VoiceChannelId = guser.VoiceChannel?.Id,
VoiceChannelName = guser.VoiceChannel?.Name,
Interval = interval,
};
_service.AddNewAutoCommand(cmd);
var guser = (IGuildUser)ctx.User;
var cmd = new AutoCommand()
{
CommandText = cmdText,
ChannelId = ctx.Channel.Id,
ChannelName = ctx.Channel.Name,
GuildId = ctx.Guild?.Id,
GuildName = ctx.Guild?.Name,
VoiceChannelId = guser.VoiceChannel?.Id,
VoiceChannelName = guser.VoiceChannel?.Name,
Interval = 0,
};
_service.AddNewAutoCommand(cmd);
await ReplyConfirmLocalizedAsync(strs.autocmd_add(Format.Code(Format.Sanitize(cmdText)), cmd.Interval));
}
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));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommandsList(int page = 1)
{
if (page-- < 1)
return;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task AutoCommandAdd(int interval, [Leftover] string cmdText)
{
if (cmdText.StartsWith(Prefix + "die", StringComparison.InvariantCulture))
return;
if (interval < 5)
return;
var guser = (IGuildUser)ctx.User;
var cmd = new AutoCommand()
{
CommandText = cmdText,
ChannelId = ctx.Channel.Id,
ChannelName = ctx.Channel.Name,
GuildId = ctx.Guild?.Id,
GuildName = ctx.Guild?.Name,
VoiceChannelId = guser.VoiceChannel?.Id,
VoiceChannelName = guser.VoiceChannel?.Name,
Interval = interval,
};
_service.AddNewAutoCommand(cmd);
await ReplyConfirmLocalizedAsync(strs.autocmd_add(Format.Code(Format.Sanitize(cmdText)), cmd.Interval));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommandsList(int page = 1)
{
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).ConfigureAwait(false);
}
else
{
var i = 0;
await SendConfirmAsync(
if (scmds.Count == 0)
{
await ReplyErrorLocalizedAsync(strs.startcmdlist_none).ConfigureAwait(false);
}
else
{
var i = 0;
await SendConfirmAsync(
text: string.Join("\n", scmds
.Select(x => $@"```css
.Select(x => $@"```css
#{++i + page * 5}
[{GetText(strs.server)}]: {(x.GuildId.HasValue ? $"{x.GuildName} #{x.GuildId}" : "-")}
[{GetText(strs.channel)}]: {x.ChannelName} #{x.ChannelId}
@@ -121,31 +117,31 @@ namespace NadekoBot.Modules.Administration
title: string.Empty,
footer: GetText(strs.page(page + 1)))
.ConfigureAwait(false);
}
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task AutoCommandsList(int page = 1)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task AutoCommandsList(int page = 1)
{
if (page-- < 1)
return;
var scmds = _service.GetAutoCommands()
.Skip(page * 5)
.Take(5)
.ToList();
if (!scmds.Any())
{
if (page-- < 1)
return;
var scmds = _service.GetAutoCommands()
.Skip(page * 5)
.Take(5)
.ToList();
if (!scmds.Any())
{
await ReplyErrorLocalizedAsync(strs.autocmdlist_none).ConfigureAwait(false);
}
else
{
var i = 0;
await SendConfirmAsync(
await ReplyErrorLocalizedAsync(strs.autocmdlist_none).ConfigureAwait(false);
}
else
{
var i = 0;
await SendConfirmAsync(
text: string.Join("\n", scmds
.Select(x => $@"```css
.Select(x => $@"```css
#{++i + page * 5}
[{GetText(strs.server)}]: {(x.GuildId.HasValue ? $"{x.GuildName} #{x.GuildId}" : "-")}
[{GetText(strs.channel)}]: {x.ChannelName} #{x.ChannelId}
@@ -154,391 +150,390 @@ namespace NadekoBot.Modules.Administration
title: string.Empty,
footer: GetText(strs.page(page + 1)))
.ConfigureAwait(false);
}
}
}
private string GetIntervalText(int interval)
private string GetIntervalText(int interval)
{
return $"[{GetText(strs.interval)}]: {interval}";
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task Wait(int miliseconds)
{
if (miliseconds <= 0)
return;
ctx.Message.DeleteAfter(0);
try
{
return $"[{GetText(strs.interval)}]: {interval}";
var msg = await SendConfirmAsync($"⏲ {miliseconds}ms")
.ConfigureAwait(false);
msg.DeleteAfter(miliseconds / 1000);
}
catch { }
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task Wait(int miliseconds)
{
if (miliseconds <= 0)
return;
ctx.Message.DeleteAfter(0);
try
{
var msg = await SendConfirmAsync($"⏲ {miliseconds}ms")
.ConfigureAwait(false);
msg.DeleteAfter(miliseconds / 1000);
}
catch { }
await Task.Delay(miliseconds).ConfigureAwait(false);
}
await Task.Delay(miliseconds).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task AutoCommandRemove([Leftover] int index)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task AutoCommandRemove([Leftover] int index)
{
if (!_service.RemoveAutoCommand(--index, out _))
{
if (!_service.RemoveAutoCommand(--index, out _))
{
await ReplyErrorLocalizedAsync(strs.acrm_fail).ConfigureAwait(false);
return;
}
await ReplyErrorLocalizedAsync(strs.acrm_fail).ConfigureAwait(false);
return;
}
await ctx.OkAsync();
}
await ctx.OkAsync();
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommandRemove([Leftover] int index)
{
if (!_service.RemoveStartupCommand(--index, out _))
await ReplyErrorLocalizedAsync(strs.scrm_fail).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.scrm).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommandRemove([Leftover] int index)
{
if (!_service.RemoveStartupCommand(--index, out _))
await ReplyErrorLocalizedAsync(strs.scrm_fail).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.scrm).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task StartupCommandsClear()
{
_service.ClearStartupCommands();
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task StartupCommandsClear()
{
_service.ClearStartupCommands();
await ReplyConfirmLocalizedAsync(strs.startcmds_cleared).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.startcmds_cleared).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task ForwardMessages()
{
var enabled = _service.ForwardMessages();
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task ForwardMessages()
{
var enabled = _service.ForwardMessages();
if (enabled)
await ReplyConfirmLocalizedAsync(strs.fwdm_start).ConfigureAwait(false);
else
await ReplyPendingLocalizedAsync(strs.fwdm_stop).ConfigureAwait(false);
}
if (enabled)
await ReplyConfirmLocalizedAsync(strs.fwdm_start).ConfigureAwait(false);
else
await ReplyPendingLocalizedAsync(strs.fwdm_stop).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task ForwardToAll()
{
var enabled = _service.ForwardToAll();
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task ForwardToAll()
{
var enabled = _service.ForwardToAll();
if (enabled)
await ReplyConfirmLocalizedAsync(strs.fwall_start).ConfigureAwait(false);
else
await ReplyPendingLocalizedAsync(strs.fwall_stop).ConfigureAwait(false);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.fwall_start).ConfigureAwait(false);
else
await ReplyPendingLocalizedAsync(strs.fwall_stop).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
public async Task ShardStats(int page = 1)
{
if (--page < 0)
return;
[NadekoCommand, Aliases]
public async Task ShardStats(int page = 1)
{
if (--page < 0)
return;
var statuses = _coord.GetAllShardStatuses();
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 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 allShardStrings = statuses
.Select(st =>
{
var str = string.Join("\n", allShardStrings.Skip(25 * curPage).Take(25));
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));
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).ConfigureAwait(false);
return _eb.Create()
.WithOkColor()
.WithDescription($"{status}\n\n{str}");
}, allShardStrings.Length, 25).ConfigureAwait(false);
}
private static string ConnectionStateToEmoji(ShardStatus status)
{
var timeDiff = DateTime.UtcNow - status.LastUpdate;
return status.ConnectionState switch
{
ConnectionState.Connected => "✅",
ConnectionState.Disconnected => "🔻",
_ when timeDiff > TimeSpan.FromSeconds(30) => " ❗ ",
_ => " ⏳"
};
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task RestartShard(int shardId)
{
var success = _coord.RestartShard(shardId);
if (success)
{
await ReplyConfirmLocalizedAsync(strs.shard_reconnecting(Format.Bold("#" + shardId))).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync(strs.no_shard_id).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[OwnerOnly]
public Task Leave([Leftover] string guildStr)
{
return _service.LeaveGuild(guildStr);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task Die(bool graceful = false)
{
try
{
await ReplyConfirmLocalizedAsync(strs.shutting_down).ConfigureAwait(false);
}
catch
{
// ignored
}
await Task.Delay(2000).ConfigureAwait(false);
_coord.Die(graceful);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task Restart()
{
bool success = _coord.RestartBot();
if (!success)
{
await ReplyErrorLocalizedAsync(strs.restart_fail).ConfigureAwait(false);
return;
}
private static string ConnectionStateToEmoji(ShardStatus status)
try { await ReplyConfirmLocalizedAsync(strs.restarting).ConfigureAwait(false); } catch { }
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SetName([Leftover] string newName)
{
if (string.IsNullOrWhiteSpace(newName))
return;
try
{
var timeDiff = DateTime.UtcNow - status.LastUpdate;
return status.ConnectionState switch
{
ConnectionState.Connected => "✅",
ConnectionState.Disconnected => "🔻",
_ when timeDiff > TimeSpan.FromSeconds(30) => " ❗ ",
_ => " ⏳"
};
await _client.CurrentUser.ModifyAsync(u => u.Username = newName).ConfigureAwait(false);
}
catch (RateLimitedException)
{
Log.Warning("You've been ratelimited. Wait 2 hours to change your name");
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task RestartShard(int shardId)
await ReplyConfirmLocalizedAsync(strs.bot_name(Format.Bold(newName))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[UserPerm(GuildPerm.ManageNicknames)]
[BotPerm(GuildPerm.ChangeNickname)]
[Priority(0)]
public async Task SetNick([Leftover] string newNick = null)
{
if (string.IsNullOrWhiteSpace(newNick))
return;
var curUser = await ctx.Guild.GetCurrentUserAsync().ConfigureAwait(false);
await curUser.ModifyAsync(u => u.Nickname = newNick).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.bot_nick(Format.Bold(newNick) ?? "-"));
}
[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 success = _coord.RestartShard(shardId);
if (success)
{
await ReplyConfirmLocalizedAsync(strs.shard_reconnecting(Format.Bold("#" + shardId))).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync(strs.no_shard_id).ConfigureAwait(false);
}
await ReplyErrorLocalizedAsync(strs.insuf_perms_i);
return;
}
[NadekoCommand, Aliases]
[OwnerOnly]
public Task Leave([Leftover] string guildStr)
{
return _service.LeaveGuild(guildStr);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task Die(bool graceful = false)
{
try
{
await ReplyConfirmLocalizedAsync(strs.shutting_down).ConfigureAwait(false);
}
catch
{
// ignored
}
await Task.Delay(2000).ConfigureAwait(false);
_coord.Die(graceful);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task Restart()
{
bool success = _coord.RestartBot();
if (!success)
{
await ReplyErrorLocalizedAsync(strs.restart_fail).ConfigureAwait(false);
return;
}
try { await ReplyConfirmLocalizedAsync(strs.restarting).ConfigureAwait(false); } catch { }
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SetName([Leftover] string newName)
{
if (string.IsNullOrWhiteSpace(newName))
return;
try
{
await _client.CurrentUser.ModifyAsync(u => u.Username = newName).ConfigureAwait(false);
}
catch (RateLimitedException)
{
Log.Warning("You've been ratelimited. Wait 2 hours to change your name");
}
await ReplyConfirmLocalizedAsync(strs.bot_name(Format.Bold(newName))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[UserPerm(GuildPerm.ManageNicknames)]
[BotPerm(GuildPerm.ChangeNickname)]
[Priority(0)]
public async Task SetNick([Leftover] string newNick = null)
{
if (string.IsNullOrWhiteSpace(newNick))
return;
var curUser = await ctx.Guild.GetCurrentUserAsync().ConfigureAwait(false);
await curUser.ModifyAsync(u => u.Nickname = newNick).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.bot_nick(Format.Bold(newNick) ?? "-"));
}
[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))
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_i);
return;
}
await gu.ModifyAsync(u => u.Nickname = newNick).ConfigureAwait(false);
await gu.ModifyAsync(u => u.Nickname = newNick).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.user_nick(Format.Bold(gu.ToString()), Format.Bold(newNick) ?? "-"));
}
await ReplyConfirmLocalizedAsync(strs.user_nick(Format.Bold(gu.ToString()), Format.Bold(newNick) ?? "-"));
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SetStatus([Leftover] SettableUserStatus status)
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SetStatus([Leftover] SettableUserStatus status)
{
await _client.SetStatusAsync(SettableUserStatusToUserStatus(status)).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.bot_status(Format.Bold(status.ToString()))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SetAvatar([Leftover] string img = null)
{
var success = await _service.SetAvatar(img);
if (success)
{
await _client.SetStatusAsync(SettableUserStatusToUserStatus(status)).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.bot_status(Format.Bold(status.ToString()))).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.set_avatar).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SetAvatar([Leftover] string img = null)
{
var success = await _service.SetAvatar(img);
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SetGame(ActivityType type, [Leftover] string game = null)
{
var rep = new ReplacementBuilder()
.WithDefault(Context)
.Build();
if (success)
{
await ReplyConfirmLocalizedAsync(strs.set_avatar).ConfigureAwait(false);
}
}
await _service.SetGameAsync(game is null ? game : rep.Replace(game), type).ConfigureAwait(false);
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SetGame(ActivityType type, [Leftover] string game = null)
{
var rep = new ReplacementBuilder()
.WithDefault(Context)
.Build();
await ReplyConfirmLocalizedAsync(strs.set_game).ConfigureAwait(false);
}
await _service.SetGameAsync(game is null ? game : rep.Replace(game), type).ConfigureAwait(false);
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SetStream(string url, [Leftover] string name = null)
{
name = name ?? "";
await ReplyConfirmLocalizedAsync(strs.set_game).ConfigureAwait(false);
}
await _service.SetStreamAsync(name, url).ConfigureAwait(false);
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SetStream(string url, [Leftover] string name = null)
{
name = name ?? "";
await ReplyConfirmLocalizedAsync(strs.set_stream).ConfigureAwait(false);
}
await _service.SetStreamAsync(name, url).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.set_stream).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task Send(string where, [Leftover] SmartText text = null)
{
var ids = where.Split('|');
if (ids.Length != 2)
return;
[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);
var sid = ulong.Parse(ids[0]);
var server = _client.Guilds.FirstOrDefault(s => s.Id == sid);
if (server is null)
return;
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))
{
var cid = ulong.Parse(ids[1].Substring(2));
var ch = server.TextChannels.FirstOrDefault(c => c.Id == cid);
if (ch is null)
return;
text = rep.Replace(text);
await ch.SendAsync(text, sanitizeAll: false);
}
else if (ids[1].ToUpperInvariant().StartsWith("U:", StringComparison.InvariantCulture))
{
var uid = ulong.Parse(ids[1].Substring(2));
var user = server.Users.FirstOrDefault(u => u.Id == uid);
if (user is null)
return;
var ch = await user.GetOrCreateDMChannelAsync();
text = rep.Replace(text);
await ch.SendAsync(text);
}
else
{
await ReplyErrorLocalizedAsync(strs.invalid_format).ConfigureAwait(false);
return;
}
await ReplyConfirmLocalizedAsync(strs.message_sent).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task ImagesReload()
if (ids[1].ToUpperInvariant().StartsWith("C:", StringComparison.InvariantCulture))
{
await _service.ReloadImagesAsync();
await ReplyConfirmLocalizedAsync(strs.images_loading);
var cid = ulong.Parse(ids[1].Substring(2));
var ch = server.TextChannels.FirstOrDefault(c => c.Id == cid);
if (ch is null)
return;
text = rep.Replace(text);
await ch.SendAsync(text, sanitizeAll: false);
}
else if (ids[1].ToUpperInvariant().StartsWith("U:", StringComparison.InvariantCulture))
{
var uid = ulong.Parse(ids[1].Substring(2));
var user = server.Users.FirstOrDefault(u => u.Id == uid);
if (user is null)
return;
var ch = await user.GetOrCreateDMChannelAsync();
text = rep.Replace(text);
await ch.SendAsync(text);
}
else
{
await ReplyErrorLocalizedAsync(strs.invalid_format).ConfigureAwait(false);
return;
}
await ReplyConfirmLocalizedAsync(strs.message_sent).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task ImagesReload()
{
await _service.ReloadImagesAsync();
await ReplyConfirmLocalizedAsync(strs.images_loading);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task StringsReload()
{
_strings.Reload();
await ReplyConfirmLocalizedAsync(strs.bot_strings_reloaded).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task StringsReload()
{
_strings.Reload();
await ReplyConfirmLocalizedAsync(strs.bot_strings_reloaded).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task CoordReload()
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task CoordReload()
{
await _coord.Reload();
await ctx.OkAsync();
}
private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)
{
switch (sus)
{
await _coord.Reload();
await ctx.OkAsync();
case SettableUserStatus.Online:
return UserStatus.Online;
case SettableUserStatus.Invisible:
return UserStatus.Invisible;
case SettableUserStatus.Idle:
return UserStatus.AFK;
case SettableUserStatus.Dnd:
return UserStatus.DoNotDisturb;
}
private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)
{
switch (sus)
{
case SettableUserStatus.Online:
return UserStatus.Online;
case SettableUserStatus.Invisible:
return UserStatus.Invisible;
case SettableUserStatus.Idle:
return UserStatus.AFK;
case SettableUserStatus.Dnd:
return UserStatus.DoNotDisturb;
}
return UserStatus.Online;
}
return UserStatus.Online;
}
public enum SettableUserStatus
{
Online,
Invisible,
Idle,
Dnd
}
public enum SettableUserStatus
{
Online,
Invisible,
Idle,
Dnd
}
}
}

View File

@@ -5,269 +5,268 @@ using NadekoBot.Services;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class ServerGreetCommands : NadekoSubmodule<GreetSettingsService>
{
[Group]
public class ServerGreetCommands : NadekoSubmodule<GreetSettingsService>
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Boost()
{
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Boost()
{
var enabled = await _service.ToggleBoost(ctx.Guild.Id, ctx.Channel.Id);
var enabled = await _service.ToggleBoost(ctx.Guild.Id, ctx.Channel.Id);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.boost_on).ConfigureAwait(false);
else
await ReplyPendingLocalizedAsync(strs.boost_off).ConfigureAwait(false);
}
if (enabled)
await ReplyConfirmLocalizedAsync(strs.boost_on).ConfigureAwait(false);
else
await ReplyPendingLocalizedAsync(strs.boost_off).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task BoostDel(int timer = 30)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task BoostDel(int timer = 30)
{
if (timer < 0 || timer > 600)
return;
await _service.SetBoostDel(ctx.Guild.Id, timer);
if (timer > 0)
await ReplyConfirmLocalizedAsync(strs.boostdel_on(timer));
else
await ReplyPendingLocalizedAsync(strs.boostdel_off).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task BoostMsg()
{
var boostMessage = _service.GetBoostMessage(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync(strs.boostmsg_cur(boostMessage?.SanitizeMentions()));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task BoostMsg([Leftover] string text)
{
if (string.IsNullOrWhiteSpace(text))
{
if (timer < 0 || timer > 600)
return;
await _service.SetBoostDel(ctx.Guild.Id, timer);
if (timer > 0)
await ReplyConfirmLocalizedAsync(strs.boostdel_on(timer));
else
await ReplyPendingLocalizedAsync(strs.boostdel_off).ConfigureAwait(false);
await BoostMsg().ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task BoostMsg()
var sendBoostEnabled = _service.SetBoostMessage(ctx.Guild.Id, ref text);
await ReplyConfirmLocalizedAsync(strs.boostmsg_new).ConfigureAwait(false);
if (!sendBoostEnabled)
await ReplyPendingLocalizedAsync(strs.boostmsg_enable($"`{Prefix}boost`"));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDel(int timer = 30)
{
if (timer < 0 || timer > 600)
return;
await _service.SetGreetDel(ctx.Guild.Id, timer).ConfigureAwait(false);
if (timer > 0)
await ReplyConfirmLocalizedAsync(strs.greetdel_on(timer));
else
await ReplyPendingLocalizedAsync(strs.greetdel_off).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Greet()
{
var enabled = await _service.SetGreet(ctx.Guild.Id, ctx.Channel.Id).ConfigureAwait(false);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.greet_on).ConfigureAwait(false);
else
await ReplyPendingLocalizedAsync(strs.greet_off).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetMsg()
{
var greetMsg = _service.GetGreetMsg(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync(strs.greetmsg_cur(greetMsg?.SanitizeMentions()));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetMsg([Leftover] string text)
{
if (string.IsNullOrWhiteSpace(text))
{
var boostMessage = _service.GetBoostMessage(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync(strs.boostmsg_cur(boostMessage?.SanitizeMentions()));
await GreetMsg().ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task BoostMsg([Leftover] string text)
var sendGreetEnabled = _service.SetGreetMessage(ctx.Guild.Id, ref text);
await ReplyConfirmLocalizedAsync(strs.greetmsg_new).ConfigureAwait(false);
if (!sendGreetEnabled)
await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDm()
{
var enabled = await _service.SetGreetDm(ctx.Guild.Id).ConfigureAwait(false);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.greetdm_on).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.greetdm_off).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDmMsg()
{
var dmGreetMsg = _service.GetDmGreetMsg(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync(strs.greetdmmsg_cur(dmGreetMsg?.SanitizeMentions()));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDmMsg([Leftover] string text = null)
{
if (string.IsNullOrWhiteSpace(text))
{
if (string.IsNullOrWhiteSpace(text))
{
await BoostMsg().ConfigureAwait(false);
return;
}
var sendBoostEnabled = _service.SetBoostMessage(ctx.Guild.Id, ref text);
await ReplyConfirmLocalizedAsync(strs.boostmsg_new).ConfigureAwait(false);
if (!sendBoostEnabled)
await ReplyPendingLocalizedAsync(strs.boostmsg_enable($"`{Prefix}boost`"));
await GreetDmMsg().ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDel(int timer = 30)
var sendGreetEnabled = _service.SetGreetDmMessage(ctx.Guild.Id, ref text);
await ReplyConfirmLocalizedAsync(strs.greetdmmsg_new).ConfigureAwait(false);
if (!sendGreetEnabled)
await ReplyPendingLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`"));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Bye()
{
var enabled = await _service.SetBye(ctx.Guild.Id, ctx.Channel.Id).ConfigureAwait(false);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.bye_on).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.bye_off).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeMsg()
{
var byeMsg = _service.GetByeMessage(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync(strs.byemsg_cur(byeMsg?.SanitizeMentions()));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task ByeMsg([Leftover] string text)
{
if (string.IsNullOrWhiteSpace(text))
{
if (timer < 0 || timer > 600)
return;
await _service.SetGreetDel(ctx.Guild.Id, timer).ConfigureAwait(false);
if (timer > 0)
await ReplyConfirmLocalizedAsync(strs.greetdel_on(timer));
else
await ReplyPendingLocalizedAsync(strs.greetdel_off).ConfigureAwait(false);
await ByeMsg().ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Greet()
{
var enabled = await _service.SetGreet(ctx.Guild.Id, ctx.Channel.Id).ConfigureAwait(false);
var sendByeEnabled = _service.SetByeMessage(ctx.Guild.Id, ref text);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.greet_on).ConfigureAwait(false);
else
await ReplyPendingLocalizedAsync(strs.greet_off).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.byemsg_new).ConfigureAwait(false);
if (!sendByeEnabled)
await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetMsg()
{
var greetMsg = _service.GetGreetMsg(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync(strs.greetmsg_cur(greetMsg?.SanitizeMentions()));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task ByeDel(int timer = 30)
{
await _service.SetByeDel(ctx.Guild.Id, timer).ConfigureAwait(false);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetMsg([Leftover] string text)
{
if (string.IsNullOrWhiteSpace(text))
{
await GreetMsg().ConfigureAwait(false);
return;
}
var sendGreetEnabled = _service.SetGreetMessage(ctx.Guild.Id, ref text);
await ReplyConfirmLocalizedAsync(strs.greetmsg_new).ConfigureAwait(false);
if (!sendGreetEnabled)
await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDm()
{
var enabled = await _service.SetGreetDm(ctx.Guild.Id).ConfigureAwait(false);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.greetdm_on).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.greetdm_off).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDmMsg()
{
var dmGreetMsg = _service.GetDmGreetMsg(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync(strs.greetdmmsg_cur(dmGreetMsg?.SanitizeMentions()));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDmMsg([Leftover] string text = null)
{
if (string.IsNullOrWhiteSpace(text))
{
await GreetDmMsg().ConfigureAwait(false);
return;
}
var sendGreetEnabled = _service.SetGreetDmMessage(ctx.Guild.Id, ref text);
await ReplyConfirmLocalizedAsync(strs.greetdmmsg_new).ConfigureAwait(false);
if (!sendGreetEnabled)
await ReplyPendingLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`"));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Bye()
{
var enabled = await _service.SetBye(ctx.Guild.Id, ctx.Channel.Id).ConfigureAwait(false);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.bye_on).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.bye_off).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeMsg()
{
var byeMsg = _service.GetByeMessage(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync(strs.byemsg_cur(byeMsg?.SanitizeMentions()));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task ByeMsg([Leftover] string text)
{
if (string.IsNullOrWhiteSpace(text))
{
await ByeMsg().ConfigureAwait(false);
return;
}
var sendByeEnabled = _service.SetByeMessage(ctx.Guild.Id, ref text);
await ReplyConfirmLocalizedAsync(strs.byemsg_new).ConfigureAwait(false);
if (!sendByeEnabled)
await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task ByeDel(int timer = 30)
{
await _service.SetByeDel(ctx.Guild.Id, timer).ConfigureAwait(false);
if (timer > 0)
await ReplyConfirmLocalizedAsync(strs.byedel_on(timer));
else
await ReplyPendingLocalizedAsync(strs.byedel_off).ConfigureAwait(false);
}
if (timer > 0)
await ReplyConfirmLocalizedAsync(strs.byedel_on(timer));
else
await ReplyPendingLocalizedAsync(strs.byedel_off).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task ByeTest([Leftover] IGuildUser user = null)
{
user = user ?? (IGuildUser) ctx.User;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task ByeTest([Leftover] IGuildUser user = null)
{
user = user ?? (IGuildUser) ctx.User;
await _service.ByeTest((ITextChannel)ctx.Channel, user);
var enabled = _service.GetByeEnabled(ctx.Guild.Id);
if (!enabled)
{
await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
}
await _service.ByeTest((ITextChannel)ctx.Channel, user);
var enabled = _service.GetByeEnabled(ctx.Guild.Id);
if (!enabled)
{
await ReplyPendingLocalizedAsync(strs.byemsg_enable($"`{Prefix}bye`"));
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task GreetTest([Leftover] IGuildUser user = null)
{
user = user ?? (IGuildUser) ctx.User;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task GreetTest([Leftover] IGuildUser user = null)
{
user = user ?? (IGuildUser) ctx.User;
await _service.GreetTest((ITextChannel)ctx.Channel, user);
var enabled = _service.GetGreetEnabled(ctx.Guild.Id);
if (!enabled)
{
await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
}
await _service.GreetTest((ITextChannel)ctx.Channel, user);
var enabled = _service.GetGreetEnabled(ctx.Guild.Id);
if (!enabled)
{
await ReplyPendingLocalizedAsync(strs.greetmsg_enable($"`{Prefix}greet`"));
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task GreetDmTest([Leftover] IGuildUser user = null)
{
user = user ?? (IGuildUser) ctx.User;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task GreetDmTest([Leftover] IGuildUser user = null)
{
user = user ?? (IGuildUser) ctx.User;
var channel = await user.GetOrCreateDMChannelAsync();
var success = await _service.GreetDmTest(channel, user);
if (success)
await ctx.OkAsync();
else
await ctx.WarningAsync();
var enabled = _service.GetGreetDmEnabled(ctx.Guild.Id);
if (!enabled)
await ReplyPendingLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`"));
}
var channel = await user.GetOrCreateDMChannelAsync();
var success = await _service.GreetDmTest(channel, user);
if (success)
await ctx.OkAsync();
else
await ctx.WarningAsync();
var enabled = _service.GetGreetDmEnabled(ctx.Guild.Id);
if (!enabled)
await ReplyPendingLocalizedAsync(strs.greetdmmsg_enable($"`{Prefix}greetdm`"));
}
}
}

View File

@@ -8,164 +8,161 @@ using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Db;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class AdministrationService : INService
{
public class AdministrationService : INService
public ConcurrentHashSet<ulong> DeleteMessagesOnCommand { get; }
public ConcurrentDictionary<ulong, bool> DeleteMessagesOnCommandChannels { get; }
private readonly DbService _db;
private readonly ILogCommandService _logService;
public AdministrationService(Bot bot, CommandHandler cmdHandler, DbService db, ILogCommandService logService)
{
public ConcurrentHashSet<ulong> DeleteMessagesOnCommand { get; }
public ConcurrentDictionary<ulong, bool> DeleteMessagesOnCommandChannels { get; }
_db = db;
_logService = logService;
private readonly DbService _db;
private readonly ILogCommandService _logService;
DeleteMessagesOnCommand = new ConcurrentHashSet<ulong>(bot.AllGuildConfigs
.Where(g => g.DeleteMessageOnCommand)
.Select(g => g.GuildId));
public AdministrationService(Bot bot, CommandHandler cmdHandler, DbService db, ILogCommandService logService)
DeleteMessagesOnCommandChannels = new ConcurrentDictionary<ulong, bool>(bot.AllGuildConfigs
.SelectMany(x => x.DelMsgOnCmdChannels)
.ToDictionary(x => x.ChannelId, x => x.State)
.ToConcurrent());
cmdHandler.CommandExecuted += DelMsgOnCmd_Handler;
}
public (bool DelMsgOnCmd, IEnumerable<DelMsgOnCmdChannel> channels) GetDelMsgOnCmdData(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
_db = db;
_logService = logService;
var conf = uow.GuildConfigsForId(guildId,
set => set.Include(x => x.DelMsgOnCmdChannels));
DeleteMessagesOnCommand = new ConcurrentHashSet<ulong>(bot.AllGuildConfigs
.Where(g => g.DeleteMessageOnCommand)
.Select(g => g.GuildId));
DeleteMessagesOnCommandChannels = new ConcurrentDictionary<ulong, bool>(bot.AllGuildConfigs
.SelectMany(x => x.DelMsgOnCmdChannels)
.ToDictionary(x => x.ChannelId, x => x.State)
.ToConcurrent());
cmdHandler.CommandExecuted += DelMsgOnCmd_Handler;
return (conf.DeleteMessageOnCommand, conf.DelMsgOnCmdChannels);
}
}
public (bool DelMsgOnCmd, IEnumerable<DelMsgOnCmdChannel> channels) GetDelMsgOnCmdData(ulong guildId)
private Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
{
var _ = Task.Run(async () =>
{
using (var uow = _db.GetDbContext())
if (!(msg.Channel is SocketTextChannel channel))
return;
//wat ?!
if (DeleteMessagesOnCommandChannels.TryGetValue(channel.Id, out var state))
{
var conf = uow.GuildConfigsForId(guildId,
set => set.Include(x => x.DelMsgOnCmdChannels));
return (conf.DeleteMessageOnCommand, conf.DelMsgOnCmdChannels);
}
}
private Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
{
var _ = Task.Run(async () =>
{
if (!(msg.Channel is SocketTextChannel channel))
return;
//wat ?!
if (DeleteMessagesOnCommandChannels.TryGetValue(channel.Id, out var state))
{
if (state && cmd.Name != "prune" && cmd.Name != "pick")
{
_logService.AddDeleteIgnore(msg.Id);
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { }
}
//if state is false, that means do not do it
}
else if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune" && cmd.Name != "pick")
if (state && cmd.Name != "prune" && cmd.Name != "pick")
{
_logService.AddDeleteIgnore(msg.Id);
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { }
}
});
return Task.CompletedTask;
}
//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().ConfigureAwait(false); } catch { }
}
});
return Task.CompletedTask;
}
public bool ToggleDeleteMessageOnCommand(ulong guildId)
public bool ToggleDeleteMessageOnCommand(ulong guildId)
{
bool enabled;
using (var uow = _db.GetDbContext())
{
bool enabled;
using (var uow = _db.GetDbContext())
{
var conf = uow.GuildConfigsForId(guildId, set => set);
enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand;
var conf = uow.GuildConfigsForId(guildId, set => set);
enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand;
uow.SaveChanges();
}
return enabled;
uow.SaveChanges();
}
return enabled;
}
public async Task SetDelMsgOnCmdState(ulong guildId, ulong chId, Administration.State newState)
public async Task SetDelMsgOnCmdState(ulong guildId, ulong chId, Administration.State newState)
{
using (var uow = _db.GetDbContext())
{
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)
var old = conf.DelMsgOnCmdChannels.FirstOrDefault(x => x.ChannelId == chId);
if (newState == Administration.State.Inherit)
{
if (old is not null)
{
if (old is not null)
{
conf.DelMsgOnCmdChannels.Remove(old);
uow.Remove(old);
}
conf.DelMsgOnCmdChannels.Remove(old);
uow.Remove(old);
}
else
{
if (old is null)
{
old = new DelMsgOnCmdChannel { ChannelId = chId };
conf.DelMsgOnCmdChannels.Add(old);
}
old.State = newState == Administration.State.Enable;
DeleteMessagesOnCommandChannels[chId] = newState == Administration.State.Enable;
}
await uow.SaveChangesAsync();
}
if (newState == Administration.State.Disable)
{
}
else if (newState == Administration.State.Enable)
{
DeleteMessagesOnCommandChannels[chId] = true;
}
else
{
DeleteMessagesOnCommandChannels.TryRemove(chId, out var _);
if (old is null)
{
old = new DelMsgOnCmdChannel { ChannelId = chId };
conf.DelMsgOnCmdChannels.Add(old);
}
old.State = newState == Administration.State.Enable;
DeleteMessagesOnCommandChannels[chId] = newState == Administration.State.Enable;
}
await uow.SaveChangesAsync();
}
public async Task DeafenUsers(bool value, params IGuildUser[] users)
if (newState == Administration.State.Disable)
{
if (!users.Any())
return;
foreach (var u in users)
{
try
{
await u.ModifyAsync(usr => usr.Deaf = value).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
public async Task EditMessage(ICommandContext context, ITextChannel chanl, ulong messageId, string input)
else if (newState == Administration.State.Enable)
{
var msg = await chanl.GetMessageAsync(messageId);
if (!(msg is IUserMessage umsg) || msg.Author.Id != context.Client.CurrentUser.Id)
return;
var rep = new ReplacementBuilder()
.WithDefault(context)
.Build();
var text = SmartText.CreateFrom(input);
text = rep.Replace(text);
await umsg.EditAsync(text);
DeleteMessagesOnCommandChannels[chId] = true;
}
else
{
DeleteMessagesOnCommandChannels.TryRemove(chId, out var _);
}
}
}
public async Task DeafenUsers(bool value, params IGuildUser[] users)
{
if (!users.Any())
return;
foreach (var u in users)
{
try
{
await u.ModifyAsync(usr => usr.Deaf = value).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
public async Task EditMessage(ICommandContext context, ITextChannel chanl, ulong messageId, string input)
{
var msg = await chanl.GetMessageAsync(messageId);
if (!(msg is IUserMessage umsg) || msg.Author.Id != context.Client.CurrentUser.Id)
return;
var rep = new ReplacementBuilder()
.WithDefault(context)
.Build();
var text = SmartText.CreateFrom(input);
text = rep.Replace(text);
await umsg.EditAsync(text);
}
}

View File

@@ -1,171 +1,166 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Services;
using System.Collections.Generic;
using System.Threading.Channels;
using LinqToDB;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Services.Database.Models;
using NadekoBot.Db;
using NadekoBot.Extensions;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public sealed class AutoAssignRoleService : INService
{
public sealed class AutoAssignRoleService : INService
{
private readonly DiscordSocketClient _client;
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly DbService _db;
//guildid/roleid
private readonly ConcurrentDictionary<ulong, IReadOnlyList<ulong>> _autoAssignableRoles;
//guildid/roleid
private readonly ConcurrentDictionary<ulong, IReadOnlyList<ulong>> _autoAssignableRoles;
private Channel<SocketGuildUser> _assignQueue = Channel.CreateBounded<SocketGuildUser>(
new BoundedChannelOptions(100)
{
FullMode = BoundedChannelFullMode.DropOldest,
SingleReader = true,
SingleWriter = false,
});
public AutoAssignRoleService(DiscordSocketClient client, Bot bot, DbService db)
private Channel<SocketGuildUser> _assignQueue = Channel.CreateBounded<SocketGuildUser>(
new BoundedChannelOptions(100)
{
_client = client;
_db = db;
FullMode = BoundedChannelFullMode.DropOldest,
SingleReader = true,
SingleWriter = false,
});
_autoAssignableRoles = bot.AllGuildConfigs
.Where(x => !string.IsNullOrWhiteSpace(x.AutoAssignRoleIds))
.ToDictionary<GuildConfig, ulong, IReadOnlyList<ulong>>(k => k.GuildId, v => v.GetAutoAssignableRoles())
.ToConcurrent();
public AutoAssignRoleService(DiscordSocketClient client, Bot bot, DbService db)
{
_client = client;
_db = db;
_ = Task.Run(async () =>
_autoAssignableRoles = bot.AllGuildConfigs
.Where(x => !string.IsNullOrWhiteSpace(x.AutoAssignRoleIds))
.ToDictionary<GuildConfig, ulong, IReadOnlyList<ulong>>(k => k.GuildId, v => v.GetAutoAssignableRoles())
.ToConcurrent();
_ = Task.Run(async () =>
{
while (true)
{
while (true)
{
var user = await _assignQueue.Reader.ReadAsync();
if (!_autoAssignableRoles.TryGetValue(user.Guild.Id, out var savedRoleIds))
continue;
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();
try
{
var roleIds = savedRoleIds
.Select(roleId => user.Guild.GetRole(roleId))
.Where(x => x is not null)
.ToList();
if (roleIds.Any())
{
await user.AddRolesAsync(roleIds).ConfigureAwait(false);
await Task.Delay(250).ConfigureAwait(false);
}
else
{
Log.Warning(
"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)
if (roleIds.Any())
{
Log.Warning("Disabled 'Auto assign role' feature on {GuildName} [{GuildId}] server because I don't have role management permissions",
await user.AddRolesAsync(roleIds).ConfigureAwait(false);
await Task.Delay(250).ConfigureAwait(false);
}
else
{
Log.Warning(
"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 (Exception ex)
{
Log.Warning(ex, "Error in aar. Probably one of the roles doesn't exist");
}
}
});
_client.UserJoined += OnClientOnUserJoined;
_client.RoleDeleted += OnClientRoleDeleted;
}
private async Task OnClientRoleDeleted(SocketRole role)
{
if (_autoAssignableRoles.TryGetValue(role.Guild.Id, out var roles)
&& roles.Contains(role.Id))
{
await ToggleAarAsync(role.Guild.Id, role.Id);
catch (Discord.Net.HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
{
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)
{
Log.Warning(ex, "Error in aar. Probably one of the roles doesn't exist");
}
}
}
});
private async Task OnClientOnUserJoined(SocketGuildUser user)
{
if (_autoAssignableRoles.TryGetValue(user.Guild.Id, out _))
await _assignQueue.Writer.WriteAsync(user);
}
_client.UserJoined += OnClientOnUserJoined;
_client.RoleDeleted += OnClientRoleDeleted;
}
public async Task<IReadOnlyList<ulong>> ToggleAarAsync(ulong guildId, ulong roleId)
private async Task OnClientRoleDeleted(SocketRole role)
{
if (_autoAssignableRoles.TryGetValue(role.Guild.Id, out var roles)
&& roles.Contains(role.Id))
{
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set);
var roles = gc.GetAutoAssignableRoles();
if(!roles.Remove(roleId) && roles.Count < 3)
roles.Add(roleId);
await ToggleAarAsync(role.Guild.Id, role.Id);
}
}
private async Task OnClientOnUserJoined(SocketGuildUser user)
{
if (_autoAssignableRoles.TryGetValue(user.Guild.Id, out _))
await _assignQueue.Writer.WriteAsync(user);
}
public async Task<IReadOnlyList<ulong>> ToggleAarAsync(ulong guildId, ulong roleId)
{
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set);
var roles = gc.GetAutoAssignableRoles();
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)
{
using var uow = _db.GetDbContext();
await uow
.GuildConfigs
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.UpdateAsync(_ => new GuildConfig(){ AutoAssignRoleIds = null});
gc.SetAutoAssignableRoles(roles);
await uow.SaveChangesAsync();
if (roles.Count > 0)
_autoAssignableRoles[guildId] = roles;
else
_autoAssignableRoles.TryRemove(guildId, out _);
await uow.SaveChangesAsync();
}
public async Task SetAarRolesAsync(ulong guildId, IEnumerable<ulong> newRoles)
{
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set);
gc.SetAutoAssignableRoles(newRoles);
await uow.SaveChangesAsync();
}
public bool TryGetRoles(ulong guildId, out IReadOnlyList<ulong> roles)
=> _autoAssignableRoles.TryGetValue(guildId, out roles);
return roles;
}
public static class GuildConfigExtensions
public async Task DisableAarAsync(ulong guildId)
{
public static List<ulong> GetAutoAssignableRoles(this GuildConfig gc)
{
if (string.IsNullOrWhiteSpace(gc.AutoAssignRoleIds))
return new List<ulong>();
return gc.AutoAssignRoleIds.Split(',').Select(ulong.Parse).ToList();
}
public static void SetAutoAssignableRoles(this GuildConfig gc, IEnumerable<ulong> roles)
{
gc.AutoAssignRoleIds = roles.JoinWith(',');
}
using var uow = _db.GetDbContext();
await uow
.GuildConfigs
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.UpdateAsync(_ => new GuildConfig(){ AutoAssignRoleIds = null});
_autoAssignableRoles.TryRemove(guildId, out _);
await uow.SaveChangesAsync();
}
public async Task SetAarRolesAsync(ulong guildId, IEnumerable<ulong> newRoles)
{
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set);
gc.SetAutoAssignableRoles(newRoles);
await uow.SaveChangesAsync();
}
public bool TryGetRoles(ulong guildId, out IReadOnlyList<ulong> roles)
=> _autoAssignableRoles.TryGetValue(guildId, out roles);
}
public static class GuildConfigExtensions
{
public static List<ulong> GetAutoAssignableRoles(this GuildConfig gc)
{
if (string.IsNullOrWhiteSpace(gc.AutoAssignRoleIds))
return new List<ulong>();
return gc.AutoAssignRoleIds.Split(',').Select(ulong.Parse).ToList();
}
public static void SetAutoAssignableRoles(this GuildConfig gc, IEnumerable<ulong> roles)
{
gc.AutoAssignRoleIds = roles.JoinWith(',');
}
}

View File

@@ -1,5 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Services;
@@ -7,20 +5,20 @@ using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class DangerousCommandsService : INService
{
public class DangerousCommandsService : INService
{
public const string WaifusDeleteSql = @"DELETE FROM WaifuUpdates;
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 MusicPlaylistDeleteSql = "DELETE FROM MusicPlaylists;";
public const string XpDeleteSql = @"DELETE FROM UserXpStats;
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,
IsClubAdmin=0,
@@ -34,112 +32,111 @@ DELETE FROM Clubs;";
//DELETE FROM Quotes
//WHERE UseCount=0 AND (DateAdded < date('now', '-7 day') OR DateAdded is null);";
private readonly DbService _db;
private readonly DbService _db;
public DangerousCommandsService(DbService db)
public DangerousCommandsService(DbService db)
{
_db = db;
}
public async Task<int> ExecuteSql(string sql)
{
int res;
using (var uow = _db.GetDbContext())
{
_db = db;
res = await uow.Database.ExecuteSqlRawAsync(sql);
}
return res;
}
public async Task<int> ExecuteSql(string sql)
public class SelectResult
{
public List<string> ColumnNames { get; set; }
public List<string[]> Results { get; set; }
}
public SelectResult SelectSql(string sql)
{
var result = new SelectResult()
{
int res;
using (var uow = _db.GetDbContext())
{
res = await uow.Database.ExecuteSqlRawAsync(sql);
}
return res;
}
ColumnNames = new List<string>(),
Results = new List<string[]>(),
};
public class SelectResult
using (var uow = _db.GetDbContext())
{
public List<string> ColumnNames { get; set; }
public List<string[]> Results { get; set; }
}
public SelectResult SelectSql(string sql)
{
var result = new SelectResult()
var conn = uow.Database.GetDbConnection();
using (var cmd = conn.CreateCommand())
{
ColumnNames = new List<string>(),
Results = new List<string[]>(),
};
using (var uow = _db.GetDbContext())
{
var conn = uow.Database.GetDbConnection();
using (var cmd = conn.CreateCommand())
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
if (reader.HasRows)
{
if (reader.HasRows)
for (int i = 0; i < reader.FieldCount; i++)
{
for (int i = 0; i < reader.FieldCount; i++)
{
result.ColumnNames.Add(reader.GetName(i));
}
while (reader.Read())
{
var obj = new object[reader.FieldCount];
reader.GetValues(obj);
result.Results.Add(obj.Select(x => x.ToString()).ToArray());
}
result.ColumnNames.Add(reader.GetName(i));
}
while (reader.Read())
{
var obj = new object[reader.FieldCount];
reader.GetValues(obj);
result.Results.Add(obj.Select(x => x.ToString()).ToArray());
}
}
}
}
return result;
}
public async Task PurgeUserAsync(ulong userId)
{
using var uow = _db.GetDbContext();
// get waifu info
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);
// delete all items this waifu owns
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 WaifuInfo() {ClaimerId = null});
// all affinities set to this waifu are reset
await uow
.Set<WaifuInfo>()
.AsQueryable()
.Where(x => x.Affinity.UserId == userId)
.UpdateAsync(x => new WaifuInfo() {AffinityId = null});
}
// delete guild xp
await uow
.UserXpStats
.DeleteAsync(x => x.UserId == userId);
// delete currency transactions
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);
}
return result;
}
}
public async Task PurgeUserAsync(ulong userId)
{
using var uow = _db.GetDbContext();
// get waifu info
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);
// delete all items this waifu owns
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 WaifuInfo() {ClaimerId = null});
// all affinities set to this waifu are reset
await uow
.Set<WaifuInfo>()
.AsQueryable()
.Where(x => x.Affinity.UserId == userId)
.UpdateAsync(x => new WaifuInfo() {AffinityId = null});
}
// delete guild xp
await uow
.UserXpStats
.DeleteAsync(x => x.UserId == userId);
// delete currency transactions
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);
}
}

View File

@@ -1,158 +1,153 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class DiscordPermOverrideService : INService, ILateBlocker
{
public class DiscordPermOverrideService : INService, ILateBlocker
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)
{
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)
{
_db = db;
_services = services;
using var uow = _db.GetDbContext();
_overrides = uow.DiscordPermOverrides
.AsNoTracking()
.AsEnumerable()
.ToDictionary(o => (o.GuildId ?? 0, o.Command), o => o)
.ToConcurrent();
}
_db = db;
_services = services;
using var uow = _db.GetDbContext();
_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)
public bool TryGetOverrides(ulong guildId, string commandName, out GuildPerm? perm)
{
commandName = commandName.ToLowerInvariant();
if (_overrides.TryGetValue((guildId, commandName), out var dpo))
{
commandName = commandName.ToLowerInvariant();
if (_overrides.TryGetValue((guildId, commandName), out var dpo))
{
perm = dpo.Perm;
return true;
}
perm = null;
return false;
perm = dpo.Perm;
return true;
}
public Task<PreconditionResult> ExecuteOverrides(ICommandContext ctx, CommandInfo command,
GuildPerm perms, IServiceProvider services)
{
var rupa = new RequireUserPermissionAttribute((GuildPermission) perms);
return rupa.CheckPermissionsAsync(ctx, command, services);
}
perm = null;
return false;
}
public async Task AddOverride(ulong guildId, string commandName, GuildPerm perm)
public Task<PreconditionResult> ExecuteOverrides(ICommandContext ctx, CommandInfo command,
GuildPerm perms, IServiceProvider services)
{
var rupa = new RequireUserPermissionAttribute((GuildPermission) perms);
return rupa.CheckPermissionsAsync(ctx, command, services);
}
public async Task AddOverride(ulong guildId, string commandName, GuildPerm perm)
{
commandName = commandName.ToLowerInvariant();
using (var uow = _db.GetDbContext())
{
commandName = commandName.ToLowerInvariant();
using (var uow = _db.GetDbContext())
var over = await uow
.Set<DiscordPermOverride>()
.AsQueryable()
.FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command);
if (over is null)
{
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 DiscordPermOverride()
{
Command = commandName,
Perm = perm,
GuildId = guildId,
});
}
else
{
over.Perm = perm;
}
_overrides[(guildId, commandName)] = over;
await uow.SaveChangesAsync();
uow.Set<DiscordPermOverride>()
.Add(over = new DiscordPermOverride()
{
Command = commandName,
Perm = perm,
GuildId = guildId,
});
}
}
public async Task ClearAllOverrides(ulong guildId)
{
using (var uow = _db.GetDbContext())
else
{
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 _);
}
}
}
public async Task RemoveOverride(ulong guildId, string commandName)
{
commandName = commandName.ToLowerInvariant();
using (var uow = _db.GetDbContext())
{
var over = await uow
.Set<DiscordPermOverride>()
.AsQueryable()
.AsNoTracking()
.FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName);
if (over is null)
return;
uow.Remove(over);
await uow.SaveChangesAsync();
_overrides.TryRemove((guildId, commandName), out _);
}
}
public Task<List<DiscordPermOverride>> GetAllOverrides(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
return 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);
return !result.IsSuccess;
over.Perm = perm;
}
return false;
_overrides[(guildId, commandName)] = over;
await uow.SaveChangesAsync();
}
}
public async Task ClearAllOverrides(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
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 _);
}
}
}
public async Task RemoveOverride(ulong guildId, string commandName)
{
commandName = commandName.ToLowerInvariant();
using (var uow = _db.GetDbContext())
{
var over = await uow
.Set<DiscordPermOverride>()
.AsQueryable()
.AsNoTracking()
.FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName);
if (over is null)
return;
uow.Remove(over);
await uow.SaveChangesAsync();
_overrides.TryRemove((guildId, commandName), out _);
}
}
public Task<List<DiscordPermOverride>> GetAllOverrides(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
return 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);
return !result.IsSuccess;
}
return false;
}
}

View File

@@ -1,133 +1,129 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Common.Collections;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Db;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class GameVoiceChannelService : INService
{
public class GameVoiceChannelService : INService
public ConcurrentHashSet<ulong> GameVoiceChannels { get; } = new ConcurrentHashSet<ulong>();
private readonly DbService _db;
private readonly DiscordSocketClient _client;
public GameVoiceChannelService(DiscordSocketClient client, DbService db, Bot bot)
{
public ConcurrentHashSet<ulong> GameVoiceChannels { get; } = new ConcurrentHashSet<ulong>();
_db = db;
_client = client;
private readonly DbService _db;
private readonly DiscordSocketClient _client;
GameVoiceChannels = new ConcurrentHashSet<ulong>(
bot.AllGuildConfigs.Where(gc => gc.GameVoiceChannel != null)
.Select(gc => gc.GameVoiceChannel.Value));
public GameVoiceChannelService(DiscordSocketClient client, DbService db, Bot bot)
{
_db = db;
_client = client;
GameVoiceChannels = new ConcurrentHashSet<ulong>(
bot.AllGuildConfigs.Where(gc => gc.GameVoiceChannel != null)
.Select(gc => gc.GameVoiceChannel.Value));
_client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
_client.GuildMemberUpdated += _client_GuildMemberUpdated;
}
private Task _client_GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after)
{
var _ = Task.Run(async () =>
{
try
{
//if the user is in the voice channel and that voice channel is gvc
var vc = after.VoiceChannel;
if (vc is null || !GameVoiceChannels.Contains(vc.Id))
return;
//if the activity has changed, and is a playing activity
if (before.Activity != after.Activity
&& after.Activity != null
&& after.Activity.Type == Discord.ActivityType.Playing)
{
//trigger gvc
await TriggerGvc(after, after.Activity.Name);
}
}
catch (Exception ex)
{
Log.Warning(ex, "Error running GuildMemberUpdated in gvc");
}
});
return Task.CompletedTask;
}
public ulong? ToggleGameVoiceChannel(ulong guildId, ulong vchId)
{
ulong? id;
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set);
if (gc.GameVoiceChannel == vchId)
{
GameVoiceChannels.TryRemove(vchId);
id = gc.GameVoiceChannel = null;
}
else
{
if (gc.GameVoiceChannel != null)
GameVoiceChannels.TryRemove(gc.GameVoiceChannel.Value);
GameVoiceChannels.Add(vchId);
id = gc.GameVoiceChannel = vchId;
}
uow.SaveChanges();
}
return id;
}
private Task Client_UserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState, SocketVoiceState newState)
{
var _ = Task.Run(async () =>
{
try
{
if (!(usr is SocketGuildUser gUser))
return;
var game = gUser.Activity?.Name;
if (oldState.VoiceChannel == newState.VoiceChannel ||
newState.VoiceChannel is null)
return;
if (!GameVoiceChannels.Contains(newState.VoiceChannel.Id) ||
string.IsNullOrWhiteSpace(game))
return;
await TriggerGvc(gUser, game);
}
catch (Exception ex)
{
Log.Warning(ex, "Error running VoiceStateUpdate in gvc");
}
});
return Task.CompletedTask;
}
private async Task TriggerGvc(SocketGuildUser gUser, string game)
{
if (string.IsNullOrWhiteSpace(game))
return;
game = game.TrimTo(50).ToLowerInvariant();
var vch = gUser.Guild.VoiceChannels
.FirstOrDefault(x => x.Name.ToLowerInvariant() == game);
if (vch is null)
return;
await Task.Delay(1000).ConfigureAwait(false);
await gUser.ModifyAsync(gu => gu.Channel = vch).ConfigureAwait(false);
}
_client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
_client.GuildMemberUpdated += _client_GuildMemberUpdated;
}
}
private Task _client_GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after)
{
var _ = Task.Run(async () =>
{
try
{
//if the user is in the voice channel and that voice channel is gvc
var vc = after.VoiceChannel;
if (vc is null || !GameVoiceChannels.Contains(vc.Id))
return;
//if the activity has changed, and is a playing activity
if (before.Activity != after.Activity
&& after.Activity != null
&& after.Activity.Type == Discord.ActivityType.Playing)
{
//trigger gvc
await TriggerGvc(after, after.Activity.Name);
}
}
catch (Exception ex)
{
Log.Warning(ex, "Error running GuildMemberUpdated in gvc");
}
});
return Task.CompletedTask;
}
public ulong? ToggleGameVoiceChannel(ulong guildId, ulong vchId)
{
ulong? id;
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set);
if (gc.GameVoiceChannel == vchId)
{
GameVoiceChannels.TryRemove(vchId);
id = gc.GameVoiceChannel = null;
}
else
{
if (gc.GameVoiceChannel != null)
GameVoiceChannels.TryRemove(gc.GameVoiceChannel.Value);
GameVoiceChannels.Add(vchId);
id = gc.GameVoiceChannel = vchId;
}
uow.SaveChanges();
}
return id;
}
private Task Client_UserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState, SocketVoiceState newState)
{
var _ = Task.Run(async () =>
{
try
{
if (!(usr is SocketGuildUser gUser))
return;
var game = gUser.Activity?.Name;
if (oldState.VoiceChannel == newState.VoiceChannel ||
newState.VoiceChannel is null)
return;
if (!GameVoiceChannels.Contains(newState.VoiceChannel.Id) ||
string.IsNullOrWhiteSpace(game))
return;
await TriggerGvc(gUser, game);
}
catch (Exception ex)
{
Log.Warning(ex, "Error running VoiceStateUpdate in gvc");
}
});
return Task.CompletedTask;
}
private async Task TriggerGvc(SocketGuildUser gUser, string game)
{
if (string.IsNullOrWhiteSpace(game))
return;
game = game.TrimTo(50).ToLowerInvariant();
var vch = gUser.Guild.VoiceChannels
.FirstOrDefault(x => x.Name.ToLowerInvariant() == game);
if (vch is null)
return;
await Task.Delay(1000).ConfigureAwait(false);
await gUser.ModifyAsync(gu => gu.Channel = vch).ConfigureAwait(false);
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Collections.Concurrent;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services;
@@ -8,79 +6,78 @@ using NadekoBot.Services.Database.Models;
using System.Threading.Tasks;
using NadekoBot.Db;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class GuildTimezoneService : INService
{
public class GuildTimezoneService : INService
public static ConcurrentDictionary<ulong, GuildTimezoneService> AllServices { get; } = new ConcurrentDictionary<ulong, GuildTimezoneService>();
private readonly ConcurrentDictionary<ulong, TimeZoneInfo> _timezones;
private readonly DbService _db;
public GuildTimezoneService(DiscordSocketClient client, Bot bot, DbService db)
{
public static ConcurrentDictionary<ulong, GuildTimezoneService> AllServices { get; } = new ConcurrentDictionary<ulong, GuildTimezoneService>();
private readonly ConcurrentDictionary<ulong, TimeZoneInfo> _timezones;
private readonly DbService _db;
_timezones = bot.AllGuildConfigs
.Select(GetTimzezoneTuple)
.Where(x => x.Timezone != null)
.ToDictionary(x => x.GuildId, x => x.Timezone)
.ToConcurrent();
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();
var curUser = client.CurrentUser;
if (curUser != null)
AllServices.TryAdd(curUser.Id, this);
_db = db;
var curUser = client.CurrentUser;
if (curUser != null)
AllServices.TryAdd(curUser.Id, this);
_db = db;
bot.JoinedGuild += Bot_JoinedGuild;
}
private Task Bot_JoinedGuild(GuildConfig arg)
{
var (guildId, tz) = GetTimzezoneTuple(arg);
if (tz != null)
_timezones.TryAdd(guildId, tz);
return Task.CompletedTask;
}
private static (ulong GuildId, TimeZoneInfo Timezone) GetTimzezoneTuple(GuildConfig x)
{
TimeZoneInfo tz;
try
{
if (x.TimeZoneId is null)
tz = null;
else
tz = TimeZoneInfo.FindSystemTimeZoneById(x.TimeZoneId);
}
catch
{
tz = null;
}
return (x.GuildId, Timezone: tz);
}
public TimeZoneInfo GetTimeZoneOrDefault(ulong guildId)
{
if (_timezones.TryGetValue(guildId, out var tz))
return tz;
return null;
}
public void SetTimeZone(ulong guildId, TimeZoneInfo tz)
{
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set);
gc.TimeZoneId = tz?.Id;
uow.SaveChanges();
if (tz is null)
_timezones.TryRemove(guildId, out tz);
else
_timezones.AddOrUpdate(guildId, tz, (key, old) => tz);
}
}
public TimeZoneInfo GetTimeZoneOrUtc(ulong guildId)
=> GetTimeZoneOrDefault(guildId) ?? TimeZoneInfo.Utc;
bot.JoinedGuild += Bot_JoinedGuild;
}
}
private Task Bot_JoinedGuild(GuildConfig arg)
{
var (guildId, tz) = GetTimzezoneTuple(arg);
if (tz != null)
_timezones.TryAdd(guildId, tz);
return Task.CompletedTask;
}
private static (ulong GuildId, TimeZoneInfo Timezone) GetTimzezoneTuple(GuildConfig x)
{
TimeZoneInfo tz;
try
{
if (x.TimeZoneId is null)
tz = null;
else
tz = TimeZoneInfo.FindSystemTimeZoneById(x.TimeZoneId);
}
catch
{
tz = null;
}
return (x.GuildId, Timezone: tz);
}
public TimeZoneInfo GetTimeZoneOrDefault(ulong guildId)
{
if (_timezones.TryGetValue(guildId, out var tz))
return tz;
return null;
}
public void SetTimeZone(ulong guildId, TimeZoneInfo tz)
{
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set);
gc.TimeZoneId = tz?.Id;
uow.SaveChanges();
if (tz is null)
_timezones.TryRemove(guildId, out tz);
else
_timezones.AddOrUpdate(guildId, tz, (key, old) => tz);
}
}
public TimeZoneInfo GetTimeZoneOrUtc(ulong guildId)
=> GetTimeZoneOrDefault(guildId) ?? TimeZoneInfo.Utc;
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Collections.Concurrent;
using System.Net;
using System.Threading.Channels;
using System.Threading.Tasks;
@@ -13,175 +11,173 @@ using NadekoBot.Common.Collections;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions;
using NadekoBot.Services;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public sealed class ImageOnlyChannelService : IEarlyBehavior
{
public sealed class ImageOnlyChannelService : IEarlyBehavior
private readonly IMemoryCache _ticketCache;
private readonly DiscordSocketClient _client;
private readonly DbService _db;
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _enabledOn;
private Channel<IUserMessage> _deleteQueue = Channel.CreateBounded<IUserMessage>(new BoundedChannelOptions(100)
{
private readonly IMemoryCache _ticketCache;
private readonly DiscordSocketClient _client;
private readonly DbService _db;
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _enabledOn;
private Channel<IUserMessage> _deleteQueue = Channel.CreateBounded<IUserMessage>(new BoundedChannelOptions(100)
{
FullMode = BoundedChannelFullMode.DropOldest,
SingleReader = true,
SingleWriter = false,
});
FullMode = BoundedChannelFullMode.DropOldest,
SingleReader = true,
SingleWriter = false,
});
public ImageOnlyChannelService(IMemoryCache ticketCache, DiscordSocketClient client, DbService db)
{
_ticketCache = ticketCache;
_client = client;
_db = db;
public ImageOnlyChannelService(IMemoryCache ticketCache, DiscordSocketClient client, DbService db)
{
_ticketCache = ticketCache;
_client = client;
_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();
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();
_ = Task.Run(DeleteQueueRunner);
_ = Task.Run(DeleteQueueRunner);
_client.ChannelDestroyed += ClientOnChannelDestroyed;
}
private Task ClientOnChannelDestroyed(SocketChannel ch)
{
if (ch is not IGuildChannel gch)
return Task.CompletedTask;
if (_enabledOn.TryGetValue(gch.GuildId, out var channels) && channels.TryRemove(ch.Id))
ToggleImageOnlyChannel(gch.GuildId, ch.Id, true);
_client.ChannelDestroyed += ClientOnChannelDestroyed;
}
private Task ClientOnChannelDestroyed(SocketChannel ch)
{
if (ch is not IGuildChannel gch)
return Task.CompletedTask;
}
private async Task DeleteQueueRunner()
if (_enabledOn.TryGetValue(gch.GuildId, out var channels) && channels.TryRemove(ch.Id))
ToggleImageOnlyChannel(gch.GuildId, ch.Id, true);
return Task.CompletedTask;
}
private async Task DeleteQueueRunner()
{
while (true)
{
while (true)
{
var toDelete = await _deleteQueue.Reader.ReadAsync();
try
{
await toDelete.DeleteAsync();
await Task.Delay(1000);
}
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
{
// disable if bot can't delete messages in the channel
ToggleImageOnlyChannel(((ITextChannel)toDelete.Channel).GuildId, toDelete.Channel.Id, true);
}
}
}
public bool ToggleImageOnlyChannel(ulong guildId, ulong channelId, bool forceDisable = false)
{
var newState = false;
using var uow = _db.GetDbContext();
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
});
channels = _enabledOn.GetOrAdd(guildId, new ConcurrentHashSet<ulong>());
channels.Add(channelId);
newState = true;
}
uow.SaveChanges();
return newState;
}
public async Task<bool> RunBehavior(IGuild guild, IUserMessage msg)
{
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))
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)
{
Log.Information("Image-Only: Ignoring owner od admin ({ChannelId})", msg.Channel.Id);
return false;
}
// ignore users higher in hierarchy
var botUser = await tch.Guild.GetCurrentUserAsync();
if (user.GetRoles().Max(x => x.Position) >= botUser.GetRoles().Max(x => x.Position))
return false;
// can't modify channel perms if not admin apparently
if (!botUser.GuildPermissions.ManageGuild)
{
ToggleImageOnlyChannel( tch.GuildId, tch.Id, true);;
return false;
}
var shouldLock = AddUserTicket(tch.GuildId, msg.Author.Id);
if (shouldLock)
{
await tch.AddPermissionOverwriteAsync(msg.Author, new(sendMessages: PermValue.Deny));
Log.Warning("Image-Only: User {User} [{UserId}] has been banned from typing in the channel [{ChannelId}]",
msg.Author,
msg.Author.Id,
msg.Channel.Id);
}
var toDelete = await _deleteQueue.Reader.ReadAsync();
try
{
await _deleteQueue.Writer.WriteAsync(msg);
await toDelete.DeleteAsync();
await Task.Delay(1000);
}
catch (Exception ex)
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
{
Log.Error(ex, "Error deleting message {MessageId} in image-only channel {ChannelId}.",
msg.Id,
tch.Id);
// disable if bot can't delete messages in the channel
ToggleImageOnlyChannel(((ITextChannel)toDelete.Channel).GuildId, toDelete.Channel.Id, true);
}
return true;
}
}
private bool AddUserTicket(ulong guildId, ulong userId)
public bool ToggleImageOnlyChannel(ulong guildId, ulong channelId, bool forceDisable = false)
{
var newState = false;
using var uow = _db.GetDbContext();
if (forceDisable
|| (_enabledOn.TryGetValue(guildId, out var channels)
&& channels.TryRemove(channelId)))
{
var old = _ticketCache.GetOrCreate($"{guildId}_{userId}", entry =>
uow.ImageOnlyChannels.Delete(x => x.ChannelId == channelId);
}
else
{
uow.ImageOnlyChannels.Add(new()
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1);
return 0;
GuildId = guildId,
ChannelId = channelId
});
_ticketCache.Set($"{guildId}_{userId}", ++old);
channels = _enabledOn.GetOrAdd(guildId, new ConcurrentHashSet<ulong>());
channels.Add(channelId);
newState = true;
}
// if this is the third time that the user posts a
// non image in an image-only channel on this server
return old > 2;
uow.SaveChanges();
return newState;
}
public async Task<bool> RunBehavior(IGuild guild, IUserMessage msg)
{
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))
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)
{
Log.Information("Image-Only: Ignoring owner od admin ({ChannelId})", msg.Channel.Id);
return false;
}
public int Priority { get; } = 0;
// ignore users higher in hierarchy
var botUser = await tch.Guild.GetCurrentUserAsync();
if (user.GetRoles().Max(x => x.Position) >= botUser.GetRoles().Max(x => x.Position))
return false;
// can't modify channel perms if not admin apparently
if (!botUser.GuildPermissions.ManageGuild)
{
ToggleImageOnlyChannel( tch.GuildId, tch.Id, true);;
return false;
}
var shouldLock = AddUserTicket(tch.GuildId, msg.Author.Id);
if (shouldLock)
{
await tch.AddPermissionOverwriteAsync(msg.Author, new(sendMessages: PermValue.Deny));
Log.Warning("Image-Only: User {User} [{UserId}] has been banned from typing in the channel [{ChannelId}]",
msg.Author,
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);
}
return true;
}
private bool AddUserTicket(ulong guildId, ulong userId)
{
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,6 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Discord;
@@ -11,470 +9,468 @@ using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Db;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public enum MuteType
{
public enum MuteType
Voice,
Chat,
All
}
public class MuteService : INService
{
public ConcurrentDictionary<ulong, string> GuildMuteRoles { get; }
public ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; }
public ConcurrentDictionary<ulong, ConcurrentDictionary<(ulong, TimerType), Timer>> Un_Timers { get; }
= new ConcurrentDictionary<ulong, ConcurrentDictionary<(ulong, TimerType), Timer>>();
public event Action<IGuildUser, IUser, MuteType, string> UserMuted = delegate { };
public event Action<IGuildUser, IUser, MuteType, string> UserUnmuted = delegate { };
private static readonly OverwritePermissions denyOverwrite =
new OverwritePermissions(addReactions: PermValue.Deny, sendMessages: PermValue.Deny,
attachFiles: PermValue.Deny);
private readonly DiscordSocketClient _client;
private readonly DbService _db;
private readonly IEmbedBuilderService _eb;
public MuteService(DiscordSocketClient client, DbService db, IEmbedBuilderService eb)
{
Voice,
Chat,
All
}
_client = client;
_db = db;
_eb = eb;
public class MuteService : INService
{
public ConcurrentDictionary<ulong, string> GuildMuteRoles { get; }
public ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; }
public ConcurrentDictionary<ulong, ConcurrentDictionary<(ulong, TimerType), Timer>> Un_Timers { get; }
= new ConcurrentDictionary<ulong, ConcurrentDictionary<(ulong, TimerType), Timer>>();
public event Action<IGuildUser, IUser, MuteType, string> UserMuted = delegate { };
public event Action<IGuildUser, IUser, MuteType, string> UserUnmuted = delegate { };
private static readonly OverwritePermissions denyOverwrite =
new OverwritePermissions(addReactions: PermValue.Deny, sendMessages: PermValue.Deny,
attachFiles: PermValue.Deny);
private readonly DiscordSocketClient _client;
private readonly DbService _db;
private readonly IEmbedBuilderService _eb;
public MuteService(DiscordSocketClient client, DbService db, IEmbedBuilderService eb)
using (var uow = db.GetDbContext())
{
_client = client;
_db = db;
_eb = eb;
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();
using (var uow = db.GetDbContext())
GuildMuteRoles = configs
.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
.ToDictionary(c => c.GuildId, c => c.MuteRoleName)
.ToConcurrent();
MutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(configs
.ToDictionary(
k => k.GuildId,
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
));
var max = TimeSpan.FromDays(49);
foreach (var conf in configs)
{
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();
GuildMuteRoles = configs
.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
.ToDictionary(c => c.GuildId, c => c.MuteRoleName)
.ToConcurrent();
MutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(configs
.ToDictionary(
k => k.GuildId,
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
));
var max = TimeSpan.FromDays(49);
foreach (var conf in configs)
foreach (var x in conf.UnmuteTimers)
{
foreach (var x in conf.UnmuteTimers)
TimeSpan after;
if (x.UnmuteAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
{
TimeSpan after;
if (x.UnmuteAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
{
after = TimeSpan.FromMinutes(2);
}
else
{
var unmute = x.UnmuteAt - DateTime.UtcNow;
after = unmute > max ? max : unmute;
}
StartUn_Timer(conf.GuildId, x.UserId, after, TimerType.Mute);
after = TimeSpan.FromMinutes(2);
}
else
{
var unmute = x.UnmuteAt - DateTime.UtcNow;
after = unmute > max ? max : unmute;
}
foreach (var x in conf.UnbanTimer)
{
TimeSpan after;
if (x.UnbanAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
{
after = TimeSpan.FromMinutes(2);
}
else
{
var unban = x.UnbanAt - DateTime.UtcNow;
after = unban > max ? max : unban;
}
StartUn_Timer(conf.GuildId, x.UserId, after, TimerType.Ban);
}
foreach (var x in conf.UnroleTimer)
{
TimeSpan after;
if (x.UnbanAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
{
after = TimeSpan.FromMinutes(2);
}
else
{
var unban = x.UnbanAt - DateTime.UtcNow;
after = unban > max ? max : unban;
}
StartUn_Timer(conf.GuildId, x.UserId, after, TimerType.AddRole, x.RoleId);
}
StartUn_Timer(conf.GuildId, x.UserId, after, TimerType.Mute);
}
_client.UserJoined += Client_UserJoined;
foreach (var x in conf.UnbanTimer)
{
TimeSpan after;
if (x.UnbanAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
{
after = TimeSpan.FromMinutes(2);
}
else
{
var unban = x.UnbanAt - DateTime.UtcNow;
after = unban > max ? max : unban;
}
StartUn_Timer(conf.GuildId, x.UserId, after, TimerType.Ban);
}
foreach (var x in conf.UnroleTimer)
{
TimeSpan after;
if (x.UnbanAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
{
after = TimeSpan.FromMinutes(2);
}
else
{
var unban = x.UnbanAt - DateTime.UtcNow;
after = unban > max ? max : unban;
}
StartUn_Timer(conf.GuildId, x.UserId, after, TimerType.AddRole, x.RoleId);
}
}
UserMuted += OnUserMuted;
UserUnmuted += OnUserUnmuted;
_client.UserJoined += Client_UserJoined;
}
private void OnUserMuted(IGuildUser user, IUser mod, MuteType type, string reason)
{
if (string.IsNullOrWhiteSpace(reason))
return;
UserMuted += OnUserMuted;
UserUnmuted += OnUserUnmuted;
}
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()));
}
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()));
}
private void OnUserUnmuted(IGuildUser user, IUser mod, MuteType type, string reason)
{
if (string.IsNullOrWhiteSpace(reason))
return;
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()));
}
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()));
}
private Task Client_UserJoined(IGuildUser usr)
private Task Client_UserJoined(IGuildUser usr)
{
try
{
MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted);
if (muted is null || !muted.Contains(usr.Id))
return Task.CompletedTask;
var _ = Task.Run(() => MuteUser(usr, _client.CurrentUser, reason: "Sticky mute").ConfigureAwait(false));
}
catch (Exception ex)
{
Log.Warning(ex, "Error in MuteService UserJoined event");
}
return Task.CompletedTask;
}
public async Task SetMuteRoleAsync(ulong guildId, string name)
{
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guildId, set => set);
config.MuteRoleName = name;
GuildMuteRoles.AddOrUpdate(guildId, name, (id, old) => name);
await uow.SaveChangesAsync();
}
}
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).ConfigureAwait(false); } catch { }
var muteRole = await GetMuteRole(usr.Guild).ConfigureAwait(false);
if (!usr.RoleIds.Contains(muteRole.Id))
await usr.AddRoleAsync(muteRole).ConfigureAwait(false);
StopTimer(usr.GuildId, usr.Id, TimerType.Mute);
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 MutedUserId()
{
UserId = usr.Id
});
if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
muted.Add(usr.Id);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
await uow.SaveChangesAsync();
}
UserMuted(usr, mod, MuteType.All, reason);
}
else if (type == MuteType.Voice)
{
try
{
MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted);
if (muted is null || !muted.Contains(usr.Id))
return Task.CompletedTask;
var _ = Task.Run(() => MuteUser(usr, _client.CurrentUser, reason: "Sticky mute").ConfigureAwait(false));
await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
UserMuted(usr, mod, MuteType.Voice, reason);
}
catch (Exception ex)
{
Log.Warning(ex, "Error in MuteService UserJoined event");
}
return Task.CompletedTask;
catch { }
}
public async Task SetMuteRoleAsync(ulong guildId, string name)
else if (type == MuteType.Chat)
{
await usr.AddRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserMuted(usr, mod, MuteType.Chat, 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)
{
StopTimer(guildId, usrId, TimerType.Mute);
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guildId, set => set);
config.MuteRoleName = name;
GuildMuteRoles.AddOrUpdate(guildId, name, (id, old) => name);
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 (MutedUsers.TryGetValue(guildId, out ConcurrentHashSet<ulong> muted))
muted.TryRemove(usrId);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usrId);
await uow.SaveChangesAsync();
}
}
public async Task MuteUser(IGuildUser usr, IUser mod, MuteType type = MuteType.All, string reason = "")
{
if (type == MuteType.All)
if (usr != null)
{
try { await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false); } catch { }
var muteRole = await GetMuteRole(usr.Guild).ConfigureAwait(false);
if (!usr.RoleIds.Contains(muteRole.Id))
await usr.AddRoleAsync(muteRole).ConfigureAwait(false);
StopTimer(usr.GuildId, usr.Id, TimerType.Mute);
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 MutedUserId()
{
UserId = usr.Id
});
if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
muted.Add(usr.Id);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
await uow.SaveChangesAsync();
}
UserMuted(usr, mod, MuteType.All, reason);
}
else if (type == MuteType.Voice)
{
try
{
await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
UserMuted(usr, mod, MuteType.Voice, reason);
}
catch { }
}
else if (type == MuteType.Chat)
{
await usr.AddRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserMuted(usr, mod, MuteType.Chat, reason);
try { await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false); } catch { }
try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false); } catch { /*ignore*/ }
UserUnmuted(usr, mod, MuteType.All, reason);
}
}
public async Task UnmuteUser(ulong guildId, ulong usrId, IUser mod, MuteType type = MuteType.All, string reason = "")
else if (type == MuteType.Voice)
{
var usr = _client.GetGuild(guildId)?.GetUser(usrId);
if (type == MuteType.All)
{
StopTimer(guildId, usrId, TimerType.Mute);
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 toRemove = config.MutedUsers.FirstOrDefault(x => x.Equals(match));
if (toRemove != null)
{
uow.Remove(toRemove);
}
if (MutedUsers.TryGetValue(guildId, out ConcurrentHashSet<ulong> muted))
muted.TryRemove(usrId);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usrId);
await uow.SaveChangesAsync();
}
if (usr != null)
{
try { await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false); } catch { }
try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false); } catch { /*ignore*/ }
UserUnmuted(usr, mod, MuteType.All, reason);
}
}
else if (type == MuteType.Voice)
{
if (usr is null)
return;
try
{
await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false);
UserUnmuted(usr, mod, MuteType.Voice, reason);
}
catch { }
}
else if (type == MuteType.Chat)
{
if (usr is null)
return;
await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserUnmuted(usr, mod, MuteType.Chat, reason);
}
}
public async Task<IRole> GetMuteRole(IGuild guild)
{
if (guild is null)
throw new ArgumentNullException(nameof(guild));
const string defaultMuteRoleName = "nadeko-mute";
var muteRoleName = GuildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName);
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).ConfigureAwait(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).ConfigureAwait(false);
}
}
foreach (var toOverwrite in (await guild.GetTextChannelsAsync().ConfigureAwait(false)))
{
try
{
if (!toOverwrite.PermissionOverwrites.Any(x => x.TargetId == muteRole.Id
&& x.TargetType == PermissionTarget.Role))
{
await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite)
.ConfigureAwait(false);
await Task.Delay(200).ConfigureAwait(false);
}
}
catch
{
// ignored
}
}
return muteRole;
}
public async Task TimedMute(IGuildUser user, IUser mod, TimeSpan after, MuteType muteType = MuteType.All, string reason = "")
{
await MuteUser(user, mod, muteType, reason).ConfigureAwait(false); // mute the user. This will also remove any previous unmute timers
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(user.GuildId, set => set.Include(x => x.UnmuteTimers));
config.UnmuteTimers.Add(new UnmuteTimer()
{
UserId = user.Id,
UnmuteAt = DateTime.UtcNow + after,
}); // add teh unmute timer to the database
uow.SaveChanges();
}
StartUn_Timer(user.GuildId, user.Id, after, TimerType.Mute); // start the timer
}
public async Task TimedBan(IGuild guild, IUser user, TimeSpan after, string reason)
{
await guild.AddBanAsync(user.Id, 0, reason).ConfigureAwait(false);
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.UnbanTimer));
config.UnbanTimer.Add(new UnbanTimer()
{
UserId = user.Id,
UnbanAt = DateTime.UtcNow + after,
}); // add teh unmute timer to the database
uow.SaveChanges();
}
StartUn_Timer(guild.Id, user.Id, after, TimerType.Ban); // start the timer
}
public async Task TimedRole(IGuildUser user, TimeSpan after, string reason, IRole role)
{
await user.AddRoleAsync(role).ConfigureAwait(false);
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(user.GuildId, set => set.Include(x => x.UnroleTimer));
config.UnroleTimer.Add(new UnroleTimer()
{
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)
{
//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)
{
await guild.RemoveBanAsync(userId).ConfigureAwait(false);
}
}
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))
{
await user.RemoveRoleAsync(role).ConfigureAwait(false);
}
}
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").ConfigureAwait(false);
}
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;
});
}
public void StopTimer(ulong guildId, ulong userId, TimerType type)
{
if (!Un_Timers.TryGetValue(guildId, out ConcurrentDictionary<(ulong, TimerType), Timer> userTimer))
if (usr is null)
return;
if (userTimer.TryRemove((userId, type), out Timer removed))
try
{
removed.Change(Timeout.Infinite, Timeout.Infinite);
await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false);
UserUnmuted(usr, mod, MuteType.Voice, reason);
}
catch { }
}
else if (type == MuteType.Chat)
{
if (usr is null)
return;
await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserUnmuted(usr, mod, MuteType.Chat, reason);
}
}
public async Task<IRole> GetMuteRole(IGuild guild)
{
if (guild is null)
throw new ArgumentNullException(nameof(guild));
const string defaultMuteRoleName = "nadeko-mute";
var muteRoleName = GuildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName);
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).ConfigureAwait(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).ConfigureAwait(false);
}
}
private void RemoveTimerFromDb(ulong guildId, ulong userId, TimerType type)
foreach (var toOverwrite in (await guild.GetTextChannelsAsync().ConfigureAwait(false)))
{
using (var uow = _db.GetDbContext())
try
{
object toDelete;
if (type == TimerType.Mute)
if (!toOverwrite.PermissionOverwrites.Any(x => x.TargetId == muteRole.Id
&& x.TargetType == PermissionTarget.Role))
{
var config = uow.GuildConfigsForId(guildId, set => set.Include(x => x.UnmuteTimers));
toDelete = config.UnmuteTimers.FirstOrDefault(x => x.UserId == userId);
await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite)
.ConfigureAwait(false);
await Task.Delay(200).ConfigureAwait(false);
}
else
{
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);
}
uow.SaveChanges();
}
catch
{
// ignored
}
}
return muteRole;
}
public async Task TimedMute(IGuildUser user, IUser mod, TimeSpan after, MuteType muteType = MuteType.All, string reason = "")
{
await MuteUser(user, mod, muteType, reason).ConfigureAwait(false); // mute the user. This will also remove any previous unmute timers
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(user.GuildId, set => set.Include(x => x.UnmuteTimers));
config.UnmuteTimers.Add(new UnmuteTimer()
{
UserId = user.Id,
UnmuteAt = DateTime.UtcNow + after,
}); // add teh unmute timer to the database
uow.SaveChanges();
}
StartUn_Timer(user.GuildId, user.Id, after, TimerType.Mute); // start the timer
}
public async Task TimedBan(IGuild guild, IUser user, TimeSpan after, string reason)
{
await guild.AddBanAsync(user.Id, 0, reason).ConfigureAwait(false);
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.UnbanTimer));
config.UnbanTimer.Add(new UnbanTimer()
{
UserId = user.Id,
UnbanAt = DateTime.UtcNow + after,
}); // add teh unmute timer to the database
uow.SaveChanges();
}
StartUn_Timer(guild.Id, user.Id, after, TimerType.Ban); // start the timer
}
public async Task TimedRole(IGuildUser user, TimeSpan after, string reason, IRole role)
{
await user.AddRoleAsync(role).ConfigureAwait(false);
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(user.GuildId, set => set.Include(x => x.UnroleTimer));
config.UnroleTimer.Add(new UnroleTimer()
{
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)
{
//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)
{
await guild.RemoveBanAsync(userId).ConfigureAwait(false);
}
}
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))
{
await user.RemoveRoleAsync(role).ConfigureAwait(false);
}
}
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").ConfigureAwait(false);
}
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;
});
}
public void StopTimer(ulong guildId, ulong userId, TimerType type)
{
if (!Un_Timers.TryGetValue(guildId, out ConcurrentDictionary<(ulong, TimerType), Timer> userTimer))
return;
if (userTimer.TryRemove((userId, type), out Timer removed))
{
removed.Change(Timeout.Infinite, Timeout.Infinite);
}
}
private void RemoveTimerFromDb(ulong guildId, ulong userId, TimerType type)
{
using (var uow = _db.GetDbContext())
{
object toDelete;
if (type == TimerType.Mute)
{
var config = uow.GuildConfigsForId(guildId, set => set.Include(x => x.UnmuteTimers));
toDelete = config.UnmuteTimers.FirstOrDefault(x => x.UserId == userId);
}
else
{
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);
}
uow.SaveChanges();
}
}
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading;
using Discord.WebSocket;
using NadekoBot.Common.Replacements;
using NadekoBot.Services;
@@ -10,115 +7,113 @@ using Discord;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public sealed class PlayingRotateService : INService
{
public sealed class PlayingRotateService : INService
private readonly Timer _t;
private readonly BotConfigService _bss;
private readonly SelfService _selfService;
private readonly Replacer _rep;
private readonly DbService _db;
private readonly Bot _bot;
private class TimerState
{
private readonly Timer _t;
private readonly BotConfigService _bss;
private readonly SelfService _selfService;
private readonly Replacer _rep;
private readonly DbService _db;
private readonly Bot _bot;
public int Index { get; set; }
}
private class TimerState
public PlayingRotateService(DiscordSocketClient client, DbService db, Bot bot,
BotConfigService bss, IEnumerable<IPlaceholderProvider> phProviders, SelfService selfService)
{
_db = db;
_bot = bot;
_bss = bss;
_selfService = selfService;
if (client.ShardId == 0)
{
public int Index { get; set; }
}
_rep = new ReplacementBuilder()
.WithClient(client)
.WithProviders(phProviders)
.Build();
public PlayingRotateService(DiscordSocketClient client, DbService db, Bot bot,
BotConfigService bss, IEnumerable<IPlaceholderProvider> phProviders, SelfService selfService)
{
_db = db;
_bot = bot;
_bss = bss;
_selfService = selfService;
if (client.ShardId == 0)
{
_rep = new ReplacementBuilder()
.WithClient(client)
.WithProviders(phProviders)
.Build();
_t = new Timer(RotatingStatuses, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
}
private async void RotatingStatuses(object objState)
{
try
{
var state = (TimerState) objState;
if (!_bss.Data.RotateStatuses) return;
IReadOnlyList<RotatingPlayingStatus> rotatingStatuses;
using (var uow = _db.GetDbContext())
{
rotatingStatuses = uow.RotatingStatus
.AsNoTracking()
.OrderBy(x => x.Id)
.ToList();
}
if (rotatingStatuses.Count == 0)
return;
var playingStatus = state.Index >= rotatingStatuses.Count
? rotatingStatuses[state.Index = 0]
: rotatingStatuses[state.Index++];
var statusText = _rep.Replace(playingStatus.Status);
await _selfService.SetGameAsync(statusText, playingStatus.Type);
}
catch (Exception ex)
{
Log.Warning(ex, "Rotating playing status errored: {ErrorMessage}", ex.Message);
}
}
public async Task<string> RemovePlayingAsync(int index)
{
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
using var uow = _db.GetDbContext();
var toRemove = await uow.RotatingStatus
.AsQueryable()
.AsNoTracking()
.Skip(index)
.FirstOrDefaultAsync();
if (toRemove is null)
return null;
uow.Remove(toRemove);
await uow.SaveChangesAsync();
return toRemove.Status;
}
public async Task AddPlaying(ActivityType t, string status)
{
using var uow = _db.GetDbContext();
var toAdd = new RotatingPlayingStatus {Status = status, Type = t};
uow.Add(toAdd);
await uow.SaveChangesAsync();
}
public bool ToggleRotatePlaying()
{
var enabled = false;
_bss.ModifyConfig(bs => { enabled = bs.RotateStatuses = !bs.RotateStatuses; });
return enabled;
}
public IReadOnlyList<RotatingPlayingStatus> GetRotatingStatuses()
{
using var uow = _db.GetDbContext();
return uow.RotatingStatus.AsNoTracking().ToList();
_t = new Timer(RotatingStatuses, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
}
private async void RotatingStatuses(object objState)
{
try
{
var state = (TimerState) objState;
if (!_bss.Data.RotateStatuses) return;
IReadOnlyList<RotatingPlayingStatus> rotatingStatuses;
using (var uow = _db.GetDbContext())
{
rotatingStatuses = uow.RotatingStatus
.AsNoTracking()
.OrderBy(x => x.Id)
.ToList();
}
if (rotatingStatuses.Count == 0)
return;
var playingStatus = state.Index >= rotatingStatuses.Count
? rotatingStatuses[state.Index = 0]
: rotatingStatuses[state.Index++];
var statusText = _rep.Replace(playingStatus.Status);
await _selfService.SetGameAsync(statusText, playingStatus.Type);
}
catch (Exception ex)
{
Log.Warning(ex, "Rotating playing status errored: {ErrorMessage}", ex.Message);
}
}
public async Task<string> RemovePlayingAsync(int index)
{
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
using var uow = _db.GetDbContext();
var toRemove = await uow.RotatingStatus
.AsQueryable()
.AsNoTracking()
.Skip(index)
.FirstOrDefaultAsync();
if (toRemove is null)
return null;
uow.Remove(toRemove);
await uow.SaveChangesAsync();
return toRemove.Status;
}
public async Task AddPlaying(ActivityType t, string status)
{
using var uow = _db.GetDbContext();
var toAdd = new RotatingPlayingStatus {Status = status, Type = t};
uow.Add(toAdd);
await uow.SaveChangesAsync();
}
public bool ToggleRotatePlaying()
{
var enabled = false;
_bss.ModifyConfig(bs => { enabled = bs.RotateStatuses = !bs.RotateStatuses; });
return enabled;
}
public IReadOnlyList<RotatingPlayingStatus> GetRotatingStatuses()
{
using var uow = _db.GetDbContext();
return uow.RotatingStatus.AsNoTracking().ToList();
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Collections.Concurrent;
using System.Threading.Channels;
using System.Threading.Tasks;
using Discord;
@@ -10,479 +8,476 @@ using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Db;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class ProtectionService : INService
{
public class ProtectionService : INService
private readonly ConcurrentDictionary<ulong, AntiRaidStats> _antiRaidGuilds
= new ConcurrentDictionary<ulong, AntiRaidStats>();
private readonly ConcurrentDictionary<ulong, AntiSpamStats> _antiSpamGuilds
= new ConcurrentDictionary<ulong, AntiSpamStats>();
private readonly ConcurrentDictionary<ulong, AntiAltStats> _antiAltGuilds
= new ConcurrentDictionary<ulong, AntiAltStats>();
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 UnboundedChannelOptions()
{
SingleReader = true,
SingleWriter = false
});
public ProtectionService(DiscordSocketClient client, Bot bot,
MuteService mute, DbService db, UserPunishService punishService)
{
_client = client;
_mute = mute;
_db = db;
_punishService = punishService;
var ids = client.GetGuildIds();
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();
foreach (var gc in configs)
{
Initialize(gc);
}
}
_client.MessageReceived += HandleAntiSpam;
_client.UserJoined += HandleUserJoined;
bot.JoinedGuild += _bot_JoinedGuild;
_client.LeftGuild += _client_LeftGuild;
_ = Task.Run(RunQueue);
}
private async Task RunQueue()
{
private readonly ConcurrentDictionary<ulong, AntiRaidStats> _antiRaidGuilds
= new ConcurrentDictionary<ulong, AntiRaidStats>();
private readonly ConcurrentDictionary<ulong, AntiSpamStats> _antiSpamGuilds
= new ConcurrentDictionary<ulong, AntiSpamStats>();
private readonly ConcurrentDictionary<ulong, AntiAltStats> _antiAltGuilds
= new ConcurrentDictionary<ulong, AntiAltStats>();
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 UnboundedChannelOptions()
{
SingleReader = true,
SingleWriter = false
});
public ProtectionService(DiscordSocketClient client, Bot bot,
MuteService mute, DbService db, UserPunishService punishService)
{
_client = client;
_mute = mute;
_db = db;
_punishService = punishService;
var ids = client.GetGuildIds();
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();
foreach (var gc in configs)
{
Initialize(gc);
}
}
_client.MessageReceived += HandleAntiSpam;
_client.UserJoined += HandleUserJoined;
bot.JoinedGuild += _bot_JoinedGuild;
_client.LeftGuild += _client_LeftGuild;
_ = Task.Run(RunQueue);
}
private async Task RunQueue()
while (true)
{
while (true)
{
var item = await PunishUserQueue.Reader.ReadAsync();
var item = await PunishUserQueue.Reader.ReadAsync();
var muteTime = item.MuteTime;
var gu = item.User;
try
{
await _punishService.ApplyPunishment(gu.Guild, gu, _client.CurrentUser,
item.Action, muteTime, item.RoleId, $"{item.Type} Protection");
}
catch (Exception ex)
{
Log.Warning(ex, "Error in punish queue: {Message}", ex.Message);
}
finally
{
await Task.Delay(1000);
}
var muteTime = item.MuteTime;
var gu = item.User;
try
{
await _punishService.ApplyPunishment(gu.Guild, gu, _client.CurrentUser,
item.Action, muteTime, item.RoleId, $"{item.Type} Protection");
}
catch (Exception ex)
{
Log.Warning(ex, "Error in punish queue: {Message}", ex.Message);
}
finally
{
await Task.Delay(1000);
}
}
}
private Task _client_LeftGuild(SocketGuild guild)
private Task _client_LeftGuild(SocketGuild guild)
{
var _ = Task.Run(async () =>
{
var _ = Task.Run(async () =>
{
TryStopAntiRaid(guild.Id);
TryStopAntiSpam(guild.Id);
await TryStopAntiAlt(guild.Id);
});
TryStopAntiRaid(guild.Id);
TryStopAntiSpam(guild.Id);
await TryStopAntiAlt(guild.Id);
});
return Task.CompletedTask;
}
private Task _bot_JoinedGuild(GuildConfig gc)
{
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));
Initialize(gcWithData);
return Task.CompletedTask;
}
private void Initialize(GuildConfig gc)
{
var raid = gc.AntiRaidSetting;
var spam = gc.AntiSpamSetting;
if (raid != null)
{
var raidStats = new AntiRaidStats() { AntiRaidSettings = raid };
_antiRaidGuilds[gc.GuildId] = raidStats;
}
if (spam != null)
_antiSpamGuilds[gc.GuildId] = new AntiSpamStats() { AntiSpamSettings = spam };
var alt = gc.AntiAltSetting;
if (alt is not null)
_antiAltGuilds[gc.GuildId] = new AntiAltStats(alt);
}
private Task HandleUserJoined(SocketGuildUser user)
{
if (user.IsBot)
return Task.CompletedTask;
}
private Task _bot_JoinedGuild(GuildConfig gc)
{
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));
Initialize(gcWithData);
_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;
}
private void Initialize(GuildConfig gc)
_ = Task.Run(async () =>
{
var raid = gc.AntiRaidSetting;
var spam = gc.AntiSpamSetting;
if (raid != null)
if (maybeAlts is AntiAltStats alts)
{
var raidStats = new AntiRaidStats() { AntiRaidSettings = raid };
_antiRaidGuilds[gc.GuildId] = raidStats;
}
if (spam != null)
_antiSpamGuilds[gc.GuildId] = new AntiSpamStats() { AntiSpamSettings = spam };
var alt = gc.AntiAltSetting;
if (alt is not null)
_antiAltGuilds[gc.GuildId] = new AntiAltStats(alt);
}
private Task HandleUserJoined(SocketGuildUser user)
{
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 AntiAltStats alts)
if (user.CreatedAt != default)
{
if (user.CreatedAt != default)
var diff = DateTime.UtcNow - user.CreatedAt.UtcDateTime;
if (diff < alts.MinAge)
{
var diff = DateTime.UtcNow - user.CreatedAt.UtcDateTime;
if (diff < alts.MinAge)
{
alts.Increment();
alts.Increment();
await PunishUsers(
alts.Action,
ProtectionType.Alting,
alts.ActionDurationMinutes,
alts.RoleId,
user);
await PunishUsers(
alts.Action,
ProtectionType.Alting,
alts.ActionDurationMinutes,
alts.RoleId,
user);
return;
}
return;
}
}
}
try
{
if (!(maybeStats is AntiRaidStats stats) || !stats.RaidUsers.Add(user))
return;
try
{
if (!(maybeStats is AntiRaidStats stats) || !stats.RaidUsers.Add(user))
return;
++stats.UsersCount;
++stats.UsersCount;
if (stats.UsersCount >= stats.AntiRaidSettings.UserThreshold)
{
var users = stats.RaidUsers.ToArray();
stats.RaidUsers.Clear();
var settings = stats.AntiRaidSettings;
await PunishUsers(settings.Action, ProtectionType.Raiding,
settings.PunishDuration, null, users).ConfigureAwait(false);
}
await Task.Delay(1000 * stats.AntiRaidSettings.Seconds).ConfigureAwait(false);
stats.RaidUsers.TryRemove(user);
--stats.UsersCount;
}
catch
if (stats.UsersCount >= stats.AntiRaidSettings.UserThreshold)
{
// ignored
var users = stats.RaidUsers.ToArray();
stats.RaidUsers.Clear();
var settings = stats.AntiRaidSettings;
await PunishUsers(settings.Action, ProtectionType.Raiding,
settings.PunishDuration, null, users).ConfigureAwait(false);
}
});
return Task.CompletedTask;
}
await Task.Delay(1000 * stats.AntiRaidSettings.Seconds).ConfigureAwait(false);
private Task HandleAntiSpam(SocketMessage arg)
{
if (!(arg is SocketUserMessage msg) || msg.Author.IsBot)
return Task.CompletedTask;
stats.RaidUsers.TryRemove(user);
--stats.UsersCount;
if (!(msg.Channel is ITextChannel channel))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
if (!_antiSpamGuilds.TryGetValue(channel.Guild.Id, out var spamSettings) ||
spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new AntiSpamIgnore()
{
ChannelId = channel.Id
}))
return;
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, (id) => new UserSpamStats(msg),
(id, 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)
.ConfigureAwait(false);
}
}
}
catch
{
// ignored
}
});
return Task.CompletedTask;
}
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",
pt,
gus.Length,
action,
gus[0].Guild.Name);
foreach (var gu in gus)
{
await PunishUserQueue.Writer.WriteAsync(new PunishQueueItem()
{
Action = action,
Type = pt,
User = gu,
MuteTime = muteTime,
RoleId = roleId
});
}
catch
{
// ignored
}
});
return Task.CompletedTask;
}
_ = OnAntiProtectionTriggered(action, pt, gus);
private Task HandleAntiSpam(SocketMessage arg)
{
if (!(arg is SocketUserMessage msg) || msg.Author.IsBot)
return Task.CompletedTask;
if (!(msg.Channel is ITextChannel channel))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
if (!_antiSpamGuilds.TryGetValue(channel.Guild.Id, out var spamSettings) ||
spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new AntiSpamIgnore()
{
ChannelId = channel.Id
}))
return;
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, (id) => new UserSpamStats(msg),
(id, 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)
.ConfigureAwait(false);
}
}
}
catch
{
// ignored
}
});
return Task.CompletedTask;
}
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",
pt,
gus.Length,
action,
gus[0].Guild.Name);
foreach (var gu in gus)
{
await PunishUserQueue.Writer.WriteAsync(new PunishQueueItem()
{
Action = action,
Type = pt,
User = gu,
MuteTime = muteTime,
RoleId = roleId
});
}
public async Task<AntiRaidStats> StartAntiRaidAsync(ulong guildId, int userThreshold, int seconds,
PunishmentAction action, int minutesDuration)
{
var g = _client.GetGuild(guildId);
await _mute.GetMuteRole(g).ConfigureAwait(false);
_ = OnAntiProtectionTriggered(action, pt, gus);
}
if (action == PunishmentAction.AddRole)
return null;
public async Task<AntiRaidStats> StartAntiRaidAsync(ulong guildId, int userThreshold, int seconds,
PunishmentAction action, int minutesDuration)
{
var g = _client.GetGuild(guildId);
await _mute.GetMuteRole(g).ConfigureAwait(false);
if (action == PunishmentAction.AddRole)
return null;
if (!IsDurationAllowed(action))
minutesDuration = 0;
if (!IsDurationAllowed(action))
minutesDuration = 0;
var stats = new AntiRaidStats()
var stats = new AntiRaidStats()
{
AntiRaidSettings = new AntiRaidSetting()
{
AntiRaidSettings = new AntiRaidSetting()
{
Action = action,
Seconds = seconds,
UserThreshold = userThreshold,
PunishDuration = minutesDuration
}
};
Action = action,
Seconds = seconds,
UserThreshold = userThreshold,
PunishDuration = minutesDuration
}
};
_antiRaidGuilds.AddOrUpdate(guildId, stats, (key, old) => stats);
_antiRaidGuilds.AddOrUpdate(guildId, stats, (key, old) => stats);
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiRaidSetting));
gc.AntiRaidSetting = stats.AntiRaidSettings;
await uow.SaveChangesAsync();
}
return stats;
}
public bool TryStopAntiRaid(ulong guildId)
{
if (_antiRaidGuilds.TryRemove(guildId, out _))
{
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiRaidSetting));
gc.AntiRaidSetting = stats.AntiRaidSettings;
await uow.SaveChangesAsync();
gc.AntiRaidSetting = null;
uow.SaveChanges();
}
return stats;
}
public bool TryStopAntiRaid(ulong guildId)
{
if (_antiRaidGuilds.TryRemove(guildId, out _))
{
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiRaidSetting));
gc.AntiRaidSetting = null;
uow.SaveChanges();
}
return true;
}
return false;
}
public bool TryStopAntiSpam(ulong guildId)
{
if (_antiSpamGuilds.TryRemove(guildId, out var removed))
{
removed.UserStats.ForEach(x => x.Value.Dispose());
using (var uow = _db.GetDbContext())
{
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)
{
var g = _client.GetGuild(guildId);
await _mute.GetMuteRole(g).ConfigureAwait(false);
if (!IsDurationAllowed(action))
punishDurationMinutes = 0;
var stats = new AntiSpamStats
{
AntiSpamSettings = new AntiSpamSetting()
{
Action = action,
MessageThreshold = messageCount,
MuteTime = punishDurationMinutes,
RoleId = roleId,
}
};
stats = _antiSpamGuilds.AddOrUpdate(guildId, stats, (key, old) =>
{
stats.AntiSpamSettings.IgnoredChannels = old.AntiSpamSettings.IgnoredChannels;
return stats;
});
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting));
if (gc.AntiSpamSetting != null)
{
gc.AntiSpamSetting.Action = stats.AntiSpamSettings.Action;
gc.AntiSpamSetting.MessageThreshold = stats.AntiSpamSettings.MessageThreshold;
gc.AntiSpamSetting.MuteTime = stats.AntiSpamSettings.MuteTime;
gc.AntiSpamSetting.RoleId = stats.AntiSpamSettings.RoleId;
}
else
{
gc.AntiSpamSetting = stats.AntiSpamSettings;
}
await uow.SaveChangesAsync();
}
return stats;
}
public async Task<bool?> AntiSpamIgnoreAsync(ulong guildId, ulong channelId)
{
var obj = new AntiSpamIgnore()
{
ChannelId = channelId
};
bool added;
using (var uow = _db.GetDbContext())
{
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.IgnoredChannels.Add(obj)) // if adding to db is successful
{
if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
temp.AntiSpamSettings.IgnoredChannels.Add(obj); // add to local cache
added = true;
}
else
{
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;
}
await uow.SaveChangesAsync();
}
return added;
}
public (AntiSpamStats, AntiRaidStats, AntiAltStats) GetAntiStats(ulong guildId)
{
_antiRaidGuilds.TryGetValue(guildId, out var antiRaidStats);
_antiSpamGuilds.TryGetValue(guildId, out var antiSpamStats);
_antiAltGuilds.TryGetValue(guildId, out var antiAltStats);
return (antiSpamStats, antiRaidStats, antiAltStats);
}
public bool IsDurationAllowed(PunishmentAction action)
{
switch (action)
{
case PunishmentAction.Ban:
case PunishmentAction.Mute:
case PunishmentAction.ChatMute:
case PunishmentAction.VoiceMute:
case PunishmentAction.AddRole:
return true;
default:
return false;
}
}
public async Task StartAntiAltAsync(ulong guildId, int minAgeMinutes, PunishmentAction action,
int actionDurationMinutes = 0, ulong? roleId = null)
{
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiAltSetting));
gc.AntiAltSetting = new AntiAltSetting()
{
Action = action,
ActionDurationMinutes = actionDurationMinutes,
MinAge = TimeSpan.FromMinutes(minAgeMinutes),
RoleId = roleId,
};
await uow.SaveChangesAsync();
_antiAltGuilds[guildId] = new AntiAltStats(gc.AntiAltSetting);
}
public async Task<bool> TryStopAntiAlt(ulong guildId)
{
if (!_antiAltGuilds.TryRemove(guildId, out _))
return false;
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiAltSetting));
gc.AntiAltSetting = null;
await uow.SaveChangesAsync();
return true;
}
return false;
}
}
public bool TryStopAntiSpam(ulong guildId)
{
if (_antiSpamGuilds.TryRemove(guildId, out var removed))
{
removed.UserStats.ForEach(x => x.Value.Dispose());
using (var uow = _db.GetDbContext())
{
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)
{
var g = _client.GetGuild(guildId);
await _mute.GetMuteRole(g).ConfigureAwait(false);
if (!IsDurationAllowed(action))
punishDurationMinutes = 0;
var stats = new AntiSpamStats
{
AntiSpamSettings = new AntiSpamSetting()
{
Action = action,
MessageThreshold = messageCount,
MuteTime = punishDurationMinutes,
RoleId = roleId,
}
};
stats = _antiSpamGuilds.AddOrUpdate(guildId, stats, (key, old) =>
{
stats.AntiSpamSettings.IgnoredChannels = old.AntiSpamSettings.IgnoredChannels;
return stats;
});
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting));
if (gc.AntiSpamSetting != null)
{
gc.AntiSpamSetting.Action = stats.AntiSpamSettings.Action;
gc.AntiSpamSetting.MessageThreshold = stats.AntiSpamSettings.MessageThreshold;
gc.AntiSpamSetting.MuteTime = stats.AntiSpamSettings.MuteTime;
gc.AntiSpamSetting.RoleId = stats.AntiSpamSettings.RoleId;
}
else
{
gc.AntiSpamSetting = stats.AntiSpamSettings;
}
await uow.SaveChangesAsync();
}
return stats;
}
public async Task<bool?> AntiSpamIgnoreAsync(ulong guildId, ulong channelId)
{
var obj = new AntiSpamIgnore()
{
ChannelId = channelId
};
bool added;
using (var uow = _db.GetDbContext())
{
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.IgnoredChannels.Add(obj)) // if adding to db is successful
{
if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
temp.AntiSpamSettings.IgnoredChannels.Add(obj); // add to local cache
added = true;
}
else
{
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;
}
await uow.SaveChangesAsync();
}
return added;
}
public (AntiSpamStats, AntiRaidStats, AntiAltStats) GetAntiStats(ulong guildId)
{
_antiRaidGuilds.TryGetValue(guildId, out var antiRaidStats);
_antiSpamGuilds.TryGetValue(guildId, out var antiSpamStats);
_antiAltGuilds.TryGetValue(guildId, out var antiAltStats);
return (antiSpamStats, antiRaidStats, antiAltStats);
}
public bool IsDurationAllowed(PunishmentAction action)
{
switch (action)
{
case PunishmentAction.Ban:
case PunishmentAction.Mute:
case PunishmentAction.ChatMute:
case PunishmentAction.VoiceMute:
case PunishmentAction.AddRole:
return true;
default:
return false;
}
}
public async Task StartAntiAltAsync(ulong guildId, int minAgeMinutes, PunishmentAction action,
int actionDurationMinutes = 0, ulong? roleId = null)
{
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiAltSetting));
gc.AntiAltSetting = new AntiAltSetting()
{
Action = action,
ActionDurationMinutes = actionDurationMinutes,
MinAge = TimeSpan.FromMinutes(minAgeMinutes),
RoleId = roleId,
};
await uow.SaveChangesAsync();
_antiAltGuilds[guildId] = new AntiAltStats(gc.AntiAltSetting);
}
public async Task<bool> TryStopAntiAlt(ulong guildId)
{
if (!_antiAltGuilds.TryRemove(guildId, out _))
return false;
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiAltSetting));
gc.AntiAltSetting = null;
await uow.SaveChangesAsync();
return true;
}
}

View File

@@ -1,78 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Discord;
using NadekoBot.Common.Collections;
using NadekoBot.Extensions;
using NadekoBot.Services;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class PruneService : INService
{
public class PruneService : INService
//channelids where prunes are currently occuring
private ConcurrentHashSet<ulong> _pruningGuilds = new ConcurrentHashSet<ulong>();
private readonly TimeSpan twoWeeks = TimeSpan.FromDays(14);
private readonly ILogCommandService _logService;
public PruneService(ILogCommandService logService)
{
//channelids where prunes are currently occuring
private ConcurrentHashSet<ulong> _pruningGuilds = new ConcurrentHashSet<ulong>();
private readonly TimeSpan twoWeeks = TimeSpan.FromDays(14);
private readonly ILogCommandService _logService;
this._logService = logService;
}
public PruneService(ILogCommandService logService)
public async Task PruneWhere(ITextChannel channel, int amount, Func<IMessage, bool> predicate)
{
channel.ThrowIfNull(nameof(channel));
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount));
if (!_pruningGuilds.Add(channel.GuildId))
return;
try
{
this._logService = logService;
}
public async Task PruneWhere(ITextChannel channel, int amount, Func<IMessage, bool> predicate)
{
channel.ThrowIfNull(nameof(channel));
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount));
if (!_pruningGuilds.Add(channel.GuildId))
return;
try
IMessage[] msgs;
IMessage lastMessage = null;
msgs = (await channel.GetMessagesAsync(50).FlattenAsync().ConfigureAwait(false)).Where(predicate).Take(amount).ToArray();
while (amount > 0 && msgs.Any())
{
IMessage[] msgs;
IMessage lastMessage = null;
msgs = (await channel.GetMessagesAsync(50).FlattenAsync().ConfigureAwait(false)).Where(predicate).Take(amount).ToArray();
while (amount > 0 && msgs.Any())
lastMessage = msgs[msgs.Length - 1];
var bulkDeletable = new List<IMessage>();
var singleDeletable = new List<IMessage>();
foreach (var x in msgs)
{
lastMessage = msgs[msgs.Length - 1];
_logService.AddDeleteIgnore(x.Id);
var bulkDeletable = new List<IMessage>();
var singleDeletable = new List<IMessage>();
foreach (var x in msgs)
{
_logService.AddDeleteIgnore(x.Id);
if (DateTime.UtcNow - x.CreatedAt < twoWeeks)
bulkDeletable.Add(x);
else
singleDeletable.Add(x);
}
if (bulkDeletable.Count > 0)
await Task.WhenAll(Task.Delay(1000), channel.DeleteMessagesAsync(bulkDeletable)).ConfigureAwait(false);
var i = 0;
foreach (var group in singleDeletable.GroupBy(x => ++i / (singleDeletable.Count / 5)))
await Task.WhenAll(Task.Delay(1000), Task.WhenAll(group.Select(x => x.DeleteAsync()))).ConfigureAwait(false);
//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().ConfigureAwait(false)).Where(predicate).Take(amount).ToArray();
if (DateTime.UtcNow - x.CreatedAt < twoWeeks)
bulkDeletable.Add(x);
else
singleDeletable.Add(x);
}
if (bulkDeletable.Count > 0)
await Task.WhenAll(Task.Delay(1000), channel.DeleteMessagesAsync(bulkDeletable)).ConfigureAwait(false);
var i = 0;
foreach (var group in singleDeletable.GroupBy(x => ++i / (singleDeletable.Count / 5)))
await Task.WhenAll(Task.Delay(1000), Task.WhenAll(group.Select(x => x.DeleteAsync()))).ConfigureAwait(false);
//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().ConfigureAwait(false)).Where(predicate).Take(amount).ToArray();
}
catch
{
//ignore
}
finally
{
_pruningGuilds.TryRemove(channel.GuildId);
}
}
catch
{
//ignore
}
finally
{
_pruningGuilds.TryRemove(channel.GuildId);
}
}
}
}

View File

@@ -6,52 +6,114 @@ using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Db;
using Serilog;
using System.Threading;
using System;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class RoleCommandsService : INService
{
public class RoleCommandsService : INService
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly ConcurrentDictionary<ulong, IndexedCollection<ReactionRoleMessage>> _models;
/// <summary>
/// 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)
{
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly ConcurrentDictionary<ulong, IndexedCollection<ReactionRoleMessage>> _models;
/// <summary>
/// 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)
{
_db = db;
_client = client;
_db = db;
_client = client;
#if !GLOBAL_NADEKO
_models = bot.AllGuildConfigs.ToDictionary(x => x.GuildId,
_models = bot.AllGuildConfigs.ToDictionary(x => x.GuildId,
x => x.ReactionRoleMessages)
.ToConcurrent();
.ToConcurrent();
_client.ReactionAdded += _client_ReactionAdded;
_client.ReactionRemoved += _client_ReactionRemoved;
_client.ReactionAdded += _client_ReactionAdded;
_client.ReactionRemoved += _client_ReactionRemoved;
#endif
}
}
private Task _client_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
private Task _client_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
{
_ = Task.Run(async () =>
{
_ = Task.Run(async () =>
if (!reaction.User.IsSpecified ||
reaction.User.Value.IsBot ||
reaction.User.Value is not SocketGuildUser gusr ||
chan is not SocketGuildChannel gch ||
!_models.TryGetValue(gch.Guild.Id, out var confs))
return;
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
if (conf is null)
return;
// compare emote names for backwards compatibility :facepalm:
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
if (reactionRole != null)
{
if (!conf.Exclusive)
{
await AddReactionRoleAsync(gusr, reactionRole);
return;
}
// If same (message, user) are being processed in an exclusive rero, quit
if (!_reacting.Add((msg.Id, reaction.UserId)))
return;
try
{
var removeExclusiveTask = RemoveExclusiveReactionRoleAsync(msg, gusr, reaction, conf, reactionRole, CancellationToken.None);
var addRoleTask = AddReactionRoleAsync(gusr, reactionRole);
await Task.WhenAll(removeExclusiveTask, addRoleTask).ConfigureAwait(false);
}
finally
{
// Free (message/user) for another exclusive rero
_reacting.TryRemove((msg.Id, reaction.UserId));
}
}
else
{
var dl = await msg.GetOrDownloadAsync().ConfigureAwait(false);
await dl.RemoveReactionAsync(reaction.Emote, dl.Author,
new RequestOptions()
{
RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502
}).ConfigureAwait(false);
Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
}
});
return Task.CompletedTask;
}
private Task _client_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
{
_ = Task.Run(async () =>
{
try
{
if (!reaction.User.IsSpecified ||
reaction.User.Value.IsBot ||
reaction.User.Value is not SocketGuildUser gusr ||
chan is not SocketGuildChannel gch ||
!_models.TryGetValue(gch.Guild.Id, out var confs))
reaction.User.Value is not SocketGuildUser gusr)
return;
if (chan is not SocketGuildChannel gch)
return;
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
return;
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
@@ -59,195 +121,129 @@ namespace NadekoBot.Modules.Administration.Services
if (conf is null)
return;
// compare emote names for backwards compatibility :facepalm:
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
if (reactionRole != null)
{
if (!conf.Exclusive)
{
await AddReactionRoleAsync(gusr, reactionRole);
var role = gusr.Guild.GetRole(reactionRole.RoleId);
if (role is null)
return;
}
// If same (message, user) are being processed in an exclusive rero, quit
if (!_reacting.Add((msg.Id, reaction.UserId)))
return;
try
{
var removeExclusiveTask = RemoveExclusiveReactionRoleAsync(msg, gusr, reaction, conf, reactionRole, CancellationToken.None);
var addRoleTask = AddReactionRoleAsync(gusr, reactionRole);
await Task.WhenAll(removeExclusiveTask, addRoleTask).ConfigureAwait(false);
}
finally
{
// Free (message/user) for another exclusive rero
_reacting.TryRemove((msg.Id, reaction.UserId));
}
await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
}
else
{
var dl = await msg.GetOrDownloadAsync().ConfigureAwait(false);
await dl.RemoveReactionAsync(reaction.Emote, dl.Author,
new RequestOptions()
{
RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502
}).ConfigureAwait(false);
Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
}
});
}
catch { }
});
return Task.CompletedTask;
}
return Task.CompletedTask;
}
private Task _client_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
public bool Get(ulong id, out IndexedCollection<ReactionRoleMessage> rrs)
{
return _models.TryGetValue(id, out rrs);
}
public bool Add(ulong id, ReactionRoleMessage rrm)
{
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));
if (gc.ReactionRoleMessages.Count >= 10)
return false;
gc.ReactionRoleMessages.Add(rrm);
uow.SaveChanges();
_models.AddOrUpdate(id,
gc.ReactionRoleMessages,
delegate { return gc.ReactionRoleMessages; });
return true;
}
public void Remove(ulong id, int index)
{
using (var uow = _db.GetDbContext())
{
_ = Task.Run(async () =>
{
try
{
if (!reaction.User.IsSpecified ||
reaction.User.Value.IsBot ||
reaction.User.Value is not SocketGuildUser gusr)
return;
if (chan is not SocketGuildChannel gch)
return;
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
return;
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
if (conf is null)
return;
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
if (reactionRole != null)
{
var role = gusr.Guild.GetRole(reactionRole.RoleId);
if (role is null)
return;
await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
}
}
catch { }
});
return Task.CompletedTask;
}
public bool Get(ulong id, out IndexedCollection<ReactionRoleMessage> rrs)
{
return _models.TryGetValue(id, out rrs);
}
public bool Add(ulong id, ReactionRoleMessage rrm)
{
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));
if (gc.ReactionRoleMessages.Count >= 10)
return false;
gc.ReactionRoleMessages.Add(rrm);
uow.SaveChanges();
var gc = uow.GuildConfigsForId(id,
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; });
return true;
}
public void Remove(ulong id, int index)
{
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);
gc.ReactionRoleMessages.RemoveAt(index);
_models.AddOrUpdate(id,
gc.ReactionRoleMessages,
delegate { return gc.ReactionRoleMessages; });
uow.SaveChanges();
}
}
/// <summary>
/// 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>
private Task AddReactionRoleAsync(SocketGuildUser user, ReactionRole dbRero)
{
var toAdd = user.Guild.GetRole(dbRero.RoleId);
return (toAdd != null && !user.Roles.Contains(toAdd))
? user.AddRoleAsync(toAdd)
: Task.CompletedTask;
}
/// <summary>
/// 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>
/// <param name="reaction">The Discord reaction of the user.</param>
/// <param name="dbReroMsg">The database entry of the reaction role message.</param>
/// <param name="dbRero">The database settings of this reaction role.</param>
/// <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)
{
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);
var removeReactionsTask = RemoveOldReactionsAsync(reactionMessage, user, reaction, cToken);
var removeRolesTask = user.RemoveRolesAsync(roleIds);
return Task.WhenAll(removeReactionsTask, removeRolesTask);
}
/// <summary>
/// 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>
/// <param name="reaction">The Discord reaction of the user.</param>
/// <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)
{
cToken.ThrowIfCancellationRequested();
//if the role is exclusive,
// remove all other reactions user added to the message
var dl = await reactionMessage.GetOrDownloadAsync().ConfigureAwait(false);
foreach (var r in dl.Reactions)
{
if (r.Key.Name == reaction.Emote.Name)
continue;
try { await dl.RemoveReactionAsync(r.Key, user).ConfigureAwait(false); } catch { }
await Task.Delay(100, cToken).ConfigureAwait(false);
}
uow.SaveChanges();
}
}
}
/// <summary>
/// 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>
private Task AddReactionRoleAsync(SocketGuildUser user, ReactionRole dbRero)
{
var toAdd = user.Guild.GetRole(dbRero.RoleId);
return (toAdd != null && !user.Roles.Contains(toAdd))
? user.AddRoleAsync(toAdd)
: Task.CompletedTask;
}
/// <summary>
/// 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>
/// <param name="reaction">The Discord reaction of the user.</param>
/// <param name="dbReroMsg">The database entry of the reaction role message.</param>
/// <param name="dbRero">The database settings of this reaction role.</param>
/// <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)
{
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);
var removeReactionsTask = RemoveOldReactionsAsync(reactionMessage, user, reaction, cToken);
var removeRolesTask = user.RemoveRolesAsync(roleIds);
return Task.WhenAll(removeReactionsTask, removeRolesTask);
}
/// <summary>
/// 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>
/// <param name="reaction">The Discord reaction of the user.</param>
/// <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)
{
cToken.ThrowIfCancellationRequested();
//if the role is exclusive,
// remove all other reactions user added to the message
var dl = await reactionMessage.GetOrDownloadAsync().ConfigureAwait(false);
foreach (var r in dl.Reactions)
{
if (r.Key.Name == reaction.Emote.Name)
continue;
try { await dl.RemoveReactionAsync(r.Key, user).ConfigureAwait(false); } catch { }
await Task.Delay(100, cToken).ConfigureAwait(false);
}
}
}

View File

@@ -1,271 +1,267 @@
using Discord;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db;
using NadekoBot.Modules.Xp;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class SelfAssignedRolesService : INService
{
public class SelfAssignedRolesService : INService
private readonly DbService _db;
public enum RemoveResult
{
private readonly DbService _db;
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 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)
}
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)
}
public SelfAssignedRolesService(DbService db)
{
_db = db;
}
public SelfAssignedRolesService(DbService db)
public bool AddNew(ulong guildId, IRole role, int group)
{
using (var uow = _db.GetDbContext())
{
_db = db;
}
public bool AddNew(ulong guildId, IRole role, int group)
{
using (var uow = _db.GetDbContext())
var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
{
var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
return false;
}
uow.SelfAssignableRoles.Add(new SelfAssignedRole
{
Group = group,
RoleId = role.Id,
GuildId = role.Guild.Id
});
uow.SaveChanges();
}
return true;
}
public bool ToggleAdSarm(ulong guildId)
{
bool newval;
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guildId, set => set);
newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages;
uow.SaveChanges();
}
return newval;
}
public async Task<(AssignResult Result, bool AutoDelete, object extra)> Assign(IGuildUser guildUser, IRole role)
{
LevelStats userLevelData;
using (var uow = _db.GetDbContext())
{
var stats = uow.GetOrCreateUserXpStats(guildUser.Guild.Id, guildUser.Id);
userLevelData = new LevelStats(stats.Xp + stats.AwardedXp);
}
var (autoDelete, exclusive, roles) = GetAdAndRoles(guildUser.Guild.Id);
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)
{
return (AssignResult.Err_Lvl_Req, autoDelete, theRoleYouWant.LevelRequirement);
}
else 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();
if (exclusive)
{
var sameRoles = guildUser.RoleIds
.Where(r => roleIds.Contains(r));
foreach (var roleId in sameRoles)
{
var sameRole = guildUser.Guild.GetRole(roleId);
if (sameRole != null)
{
return false;
}
uow.SelfAssignableRoles.Add(new SelfAssignedRole
{
Group = group,
RoleId = role.Id,
GuildId = role.Guild.Id
});
uow.SaveChanges();
}
return true;
}
public bool ToggleAdSarm(ulong guildId)
{
bool newval;
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guildId, set => set);
newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages;
uow.SaveChanges();
}
return newval;
}
public async Task<(AssignResult Result, bool AutoDelete, object extra)> Assign(IGuildUser guildUser, IRole role)
{
LevelStats userLevelData;
using (var uow = _db.GetDbContext())
{
var stats = uow.GetOrCreateUserXpStats(guildUser.Guild.Id, guildUser.Id);
userLevelData = new LevelStats(stats.Xp + stats.AwardedXp);
}
var (autoDelete, exclusive, roles) = GetAdAndRoles(guildUser.Guild.Id);
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)
{
return (AssignResult.Err_Lvl_Req, autoDelete, theRoleYouWant.LevelRequirement);
}
else 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();
if (exclusive)
{
var sameRoles = guildUser.RoleIds
.Where(r => roleIds.Contains(r));
foreach (var roleId in sameRoles)
{
var sameRole = guildUser.Guild.GetRole(roleId);
if (sameRole != null)
try
{
try
{
await guildUser.RemoveRoleAsync(sameRole).ConfigureAwait(false);
await Task.Delay(300).ConfigureAwait(false);
}
catch
{
// ignored
}
await guildUser.RemoveRoleAsync(sameRole).ConfigureAwait(false);
await Task.Delay(300).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
try
{
await guildUser.AddRoleAsync(role).ConfigureAwait(false);
}
catch (Exception ex)
{
return (AssignResult.Err_Not_Perms, autoDelete, ex);
}
return (AssignResult.Assigned, autoDelete, null);
}
try
{
await guildUser.AddRoleAsync(role).ConfigureAwait(false);
}
catch (Exception ex)
{
return (AssignResult.Err_Not_Perms, autoDelete, ex);
}
public async Task<bool> SetNameAsync(ulong guildId, int group, string name)
{
bool set = false;
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);
return (AssignResult.Assigned, autoDelete, null);
}
if (string.IsNullOrWhiteSpace(name))
public async Task<bool> SetNameAsync(ulong guildId, int group, string name)
{
bool set = false;
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);
if (string.IsNullOrWhiteSpace(name))
{
if (toUpdate != null)
gc.SelfAssignableRoleGroupNames.Remove(toUpdate);
}
else if (toUpdate is null)
{
gc.SelfAssignableRoleGroupNames.Add(new GroupName
{
if (toUpdate != null)
gc.SelfAssignableRoleGroupNames.Remove(toUpdate);
}
else if (toUpdate is null)
{
gc.SelfAssignableRoleGroupNames.Add(new GroupName
{
Name = name,
Number = group,
});
set = true;
}
else
{
toUpdate.Name = name;
set = true;
}
await uow.SaveChangesAsync();
Name = name,
Number = group,
});
set = true;
}
else
{
toUpdate.Name = name;
set = true;
}
return set;
await uow.SaveChangesAsync();
}
public async Task<(RemoveResult Result, bool AutoDelete)> Remove(IGuildUser guildUser, IRole role)
return set;
}
public async Task<(RemoveResult Result, bool AutoDelete)> Remove(IGuildUser guildUser, IRole role)
{
var (autoDelete, _, roles) = GetAdAndRoles(guildUser.Guild.Id);
if (roles.FirstOrDefault(r => r.RoleId == role.Id) is null)
{
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);
}
try
{
await guildUser.RemoveRoleAsync(role).ConfigureAwait(false);
}
catch (Exception)
{
return (RemoveResult.Err_Not_Perms, autoDelete);
}
return (RemoveResult.Removed, autoDelete);
return (RemoveResult.Err_Not_Assignable, autoDelete);
}
if (!guildUser.RoleIds.Contains(role.Id))
{
return (RemoveResult.Err_Not_Have, autoDelete);
}
try
{
await guildUser.RemoveRoleAsync(role).ConfigureAwait(false);
}
catch (Exception)
{
return (RemoveResult.Err_Not_Perms, autoDelete);
}
public bool RemoveSar(ulong guildId, ulong roleId)
return (RemoveResult.Removed, autoDelete);
}
public bool RemoveSar(ulong guildId, ulong roleId)
{
bool success;
using (var uow = _db.GetDbContext())
{
bool success;
using (var uow = _db.GetDbContext())
{
success = uow.SelfAssignableRoles.DeleteByGuildAndRoleId(guildId, roleId);
uow.SaveChanges();
}
return success;
success = uow.SelfAssignableRoles.DeleteByGuildAndRoleId(guildId, roleId);
uow.SaveChanges();
}
return success;
}
public (bool AutoDelete, bool Exclusive, IEnumerable<SelfAssignedRole>) GetAdAndRoles(ulong guildId)
public (bool AutoDelete, bool Exclusive, IEnumerable<SelfAssignedRole>) GetAdAndRoles(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set);
var autoDelete = gc.AutoDeleteSelfAssignedRoleMessages;
var exclusive = gc.ExclusiveSelfAssignedRoles;
var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
var gc = uow.GuildConfigsForId(guildId, set => set);
var autoDelete = gc.AutoDeleteSelfAssignedRoleMessages;
var exclusive = gc.ExclusiveSelfAssignedRoles;
var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
return (autoDelete, exclusive, roles);
}
}
public bool SetLevelReq(ulong guildId, IRole role, int level)
{
using (var uow = _db.GetDbContext())
{
var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
var sar = roles.FirstOrDefault(x => x.RoleId == role.Id);
if (sar != null)
{
sar.LevelRequirement = level;
uow.SaveChanges();
}
else
{
return false;
}
}
return true;
}
public bool ToggleEsar(ulong guildId)
{
bool areExclusive;
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guildId, set => set);
areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles;
uow.SaveChanges();
}
return areExclusive;
}
public (bool Exclusive, IEnumerable<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary<int, string> GroupNames) GetRoles(IGuild guild)
{
var exclusive = false;
IEnumerable<(SelfAssignedRole Model, IRole Role)> roles;
IDictionary<int, string> groupNames;
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.SelfAssignableRoleGroupNames));
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)));
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);
return (autoDelete, exclusive, roles);
}
}
}
public bool SetLevelReq(ulong guildId, IRole role, int level)
{
using (var uow = _db.GetDbContext())
{
var roles = uow.SelfAssignableRoles.GetFromGuild(guildId);
var sar = roles.FirstOrDefault(x => x.RoleId == role.Id);
if (sar != null)
{
sar.LevelRequirement = level;
uow.SaveChanges();
}
else
{
return false;
}
}
return true;
}
public bool ToggleEsar(ulong guildId)
{
bool areExclusive;
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guildId, set => set);
areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles;
uow.SaveChanges();
}
return areExclusive;
}
public (bool Exclusive, IEnumerable<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary<int, string> GroupNames) GetRoles(IGuild guild)
{
var exclusive = false;
IEnumerable<(SelfAssignedRole Model, IRole Role)> roles;
IDictionary<int, string> groupNames;
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.SelfAssignableRoleGroupNames));
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)));
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,420 +1,415 @@
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions;
using NadekoBot.Services;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Services.Database.Models;
using System.Threading;
using System.Collections.Concurrent;
using System;
using System.Net.Http;
using NadekoBot.Common;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
{
public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
private readonly CommandHandler _cmdHandler;
private readonly DbService _db;
private readonly IBotStrings _strings;
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private ImmutableDictionary<ulong, IDMChannel> ownerChannels =
new Dictionary<ulong, IDMChannel>().ToImmutableDictionary();
private ConcurrentDictionary<ulong?, ConcurrentDictionary<int, Timer>> _autoCommands =
new ConcurrentDictionary<ulong?, ConcurrentDictionary<int, Timer>>();
private readonly IImageCache _imgs;
private readonly IHttpClientFactory _httpFactory;
private readonly BotConfigService _bss;
private readonly IPubSub _pubSub;
private readonly IEmbedBuilderService _eb;
//keys
private readonly TypedKey<ActivityPubData> _activitySetKey;
private readonly TypedKey<bool> _imagesReloadKey;
private readonly TypedKey<string> _guildLeaveKey;
public SelfService(
DiscordSocketClient client,
CommandHandler cmdHandler,
DbService db,
IBotStrings strings,
IBotCredentials creds,
IDataCache cache,
IHttpClientFactory factory,
BotConfigService bss,
IPubSub pubSub,
IEmbedBuilderService eb)
{
private readonly CommandHandler _cmdHandler;
private readonly DbService _db;
private readonly IBotStrings _strings;
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private ImmutableDictionary<ulong, IDMChannel> ownerChannels =
new Dictionary<ulong, IDMChannel>().ToImmutableDictionary();
private ConcurrentDictionary<ulong?, ConcurrentDictionary<int, Timer>> _autoCommands =
new ConcurrentDictionary<ulong?, ConcurrentDictionary<int, Timer>>();
private readonly IImageCache _imgs;
private readonly IHttpClientFactory _httpFactory;
private readonly BotConfigService _bss;
private readonly IPubSub _pubSub;
private readonly IEmbedBuilderService _eb;
//keys
private readonly TypedKey<ActivityPubData> _activitySetKey;
private readonly TypedKey<bool> _imagesReloadKey;
private readonly TypedKey<string> _guildLeaveKey;
public SelfService(
DiscordSocketClient client,
CommandHandler cmdHandler,
DbService db,
IBotStrings strings,
IBotCredentials creds,
IDataCache cache,
IHttpClientFactory factory,
BotConfigService bss,
IPubSub pubSub,
IEmbedBuilderService eb)
{
_cmdHandler = cmdHandler;
_db = db;
_strings = strings;
_client = client;
_creds = creds;
_imgs = cache.LocalImages;
_httpFactory = factory;
_bss = bss;
_pubSub = pubSub;
_eb = eb;
_activitySetKey = new("activity.set");
_imagesReloadKey = new("images.reload");
_guildLeaveKey = new("guild.leave");
_cmdHandler = cmdHandler;
_db = db;
_strings = strings;
_client = client;
_creds = creds;
_imgs = cache.LocalImages;
_httpFactory = factory;
_bss = bss;
_pubSub = pubSub;
_eb = eb;
_activitySetKey = new("activity.set");
_imagesReloadKey = new("images.reload");
_guildLeaveKey = new("guild.leave");
HandleStatusChanges();
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;
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().ConfigureAwait(false);
Log.Information($"Left server {server.Name} [{server.Id}]");
}
else
{
await server.DeleteAsync().ConfigureAwait(false);
Log.Information($"Deleted server {server.Name} [{server.Id}]");
}
});
}
public async Task OnReadyAsync()
if (_client.ShardId == 0)
{
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();
var startupCommands = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
foreach (var cmd in startupCommands)
{
try
{
await ExecuteCommand(cmd).ConfigureAwait(false);
}
catch
{
}
}
if (_client.ShardId == 0)
{
await LoadOwnerChannels().ConfigureAwait(false);
}
_pubSub.Sub(_imagesReloadKey, async _ => await _imgs.Reload());
}
private Timer TimerFromAutoCommand(AutoCommand x)
_pubSub.Sub(_guildLeaveKey, async input =>
{
return new Timer(async (obj) => await ExecuteCommand((AutoCommand) obj).ConfigureAwait(false),
x,
x.Interval * 1000,
x.Interval * 1000);
}
var guildStr = input.ToString().Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(guildStr))
return;
private async Task ExecuteCommand(AutoCommand cmd)
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().ConfigureAwait(false);
Log.Information($"Left server {server.Name} [{server.Id}]");
}
else
{
await server.DeleteAsync().ConfigureAwait(false);
Log.Information($"Deleted server {server.Name} [{server.Id}]");
}
});
}
public async Task OnReadyAsync()
{
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();
var startupCommands = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
foreach (var cmd in startupCommands)
{
try
{
if (cmd.GuildId is null)
return;
await ExecuteCommand(cmd).ConfigureAwait(false);
}
catch
{
}
}
var guildShard = (int) ((cmd.GuildId.Value >> 22) % (ulong) _creds.TotalShards);
if (guildShard != _client.ShardId)
return;
var prefix = _cmdHandler.GetPrefix(cmd.GuildId);
//if someone already has .die as their startup command, ignore it
if (cmd.CommandText.StartsWith(prefix + "die", StringComparison.InvariantCulture))
return;
await _cmdHandler.ExecuteExternal(cmd.GuildId, cmd.ChannelId, cmd.CommandText).ConfigureAwait(false);
if (_client.ShardId == 0)
{
await LoadOwnerChannels().ConfigureAwait(false);
}
}
private Timer TimerFromAutoCommand(AutoCommand x)
{
return new Timer(async (obj) => await ExecuteCommand((AutoCommand) obj).ConfigureAwait(false),
x,
x.Interval * 1000,
x.Interval * 1000);
}
private async Task ExecuteCommand(AutoCommand cmd)
{
try
{
if (cmd.GuildId is null)
return;
var guildShard = (int) ((cmd.GuildId.Value >> 22) % (ulong) _creds.TotalShards);
if (guildShard != _client.ShardId)
return;
var prefix = _cmdHandler.GetPrefix(cmd.GuildId);
//if someone already has .die as their startup command, ignore it
if (cmd.CommandText.StartsWith(prefix + "die", StringComparison.InvariantCulture))
return;
await _cmdHandler.ExecuteExternal(cmd.GuildId, cmd.ChannelId, cmd.CommandText).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex, "Error in SelfService ExecuteCommand");
}
}
public void AddNewAutoCommand(AutoCommand cmd)
{
using (var uow = _db.GetDbContext())
{
uow.AutoCommands.Add(cmd);
uow.SaveChanges();
}
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);
});
}
}
public IEnumerable<AutoCommand> GetStartupCommands()
{
using var uow = _db.GetDbContext();
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();
}
private async Task LoadOwnerChannels()
{
var channels = await Task.WhenAll(_creds.OwnerIds.Select(id =>
{
var user = _client.GetUser(id);
if (user is null)
return Task.FromResult<IDMChannel>(null);
return user.GetOrCreateDMChannelAsync();
})).ConfigureAwait(false);
ownerChannels = channels.Where(x => x != null)
.ToDictionary(x => x.Recipient.Id, x => x)
.ToImmutableDictionary();
if (!ownerChannels.Any())
Log.Warning(
"No owner channels created! Make sure you've specified the correct OwnerId in the creds.yml file and invited the bot to a Discord server.");
else
Log.Information($"Created {ownerChannels.Count} out of {_creds.OwnerIds.Count} owner message channels.");
}
public Task LeaveGuild(string guildStr)
=> _pubSub.Pub(_guildLeaveKey, guildStr);
// forwards dms
public async Task LateExecute(IGuild guild, IUserMessage msg)
{
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 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));
}
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).ConfigureAwait(false);
}
catch
{
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).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
}
}
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();
if (cmd != null)
{
uow.Remove(cmd);
uow.SaveChanges();
return true;
}
}
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();
if (cmd != null)
{
uow.Remove(cmd);
if (_autoCommands.TryGetValue(cmd.GuildId, out var autos))
if (autos.TryRemove(cmd.Id, out var timer))
timer.Change(Timeout.Infinite, Timeout.Infinite);
uow.SaveChanges();
return true;
}
}
return false;
}
public async Task<bool> SetAvatar(string img)
{
if (string.IsNullOrWhiteSpace(img))
return false;
if (!Uri.IsWellFormedUriString(img, UriKind.Absolute))
return false;
var uri = new Uri(img);
using (var http = _httpFactory.CreateClient())
using (var sr = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
{
if (!sr.IsImage())
return false;
// i can't just do ReadAsStreamAsync because dicord.net's image poops itself
var imgData = await sr.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
using (var imgStream = imgData.ToStream())
{
await _client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false);
}
}
return true;
}
public void ClearStartupCommands()
{
using (var uow = _db.GetDbContext())
{
var toRemove = uow
.AutoCommands
.AsNoTracking()
.Where(x => x.Interval == 0);
uow.AutoCommands.RemoveRange(toRemove);
uow.SaveChanges();
}
}
public Task ReloadImagesAsync()
=> _pubSub.Pub(_imagesReloadKey, true);
public bool ForwardMessages()
{
var isForwarding = false;
_bss.ModifyConfig(config => { isForwarding = config.ForwardMessages = !config.ForwardMessages; });
return isForwarding;
}
public bool ForwardToAll()
{
var isToAll = false;
_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 in SelfService ExecuteCommand");
Log.Warning(ex, "Error setting activity");
}
}
});
}
public void AddNewAutoCommand(AutoCommand cmd)
{
using (var uow = _db.GetDbContext())
{
uow.AutoCommands.Add(cmd);
uow.SaveChanges();
}
public Task SetGameAsync(string game, ActivityType type)
=> _pubSub.Pub(_activitySetKey, new() {Name = game, Link = null, Type = type});
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);
});
}
}
public Task SetStreamAsync(string name, string link)
=> _pubSub.Pub(_activitySetKey, new() { Name = name, Link = link, Type = ActivityType.Streaming });
public IEnumerable<AutoCommand> GetStartupCommands()
{
using var uow = _db.GetDbContext();
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();
}
private async Task LoadOwnerChannels()
{
var channels = await Task.WhenAll(_creds.OwnerIds.Select(id =>
{
var user = _client.GetUser(id);
if (user is null)
return Task.FromResult<IDMChannel>(null);
return user.GetOrCreateDMChannelAsync();
})).ConfigureAwait(false);
ownerChannels = channels.Where(x => x != null)
.ToDictionary(x => x.Recipient.Id, x => x)
.ToImmutableDictionary();
if (!ownerChannels.Any())
Log.Warning(
"No owner channels created! Make sure you've specified the correct OwnerId in the creds.yml file and invited the bot to a Discord server.");
else
Log.Information($"Created {ownerChannels.Count} out of {_creds.OwnerIds.Count} owner message channels.");
}
public Task LeaveGuild(string guildStr)
=> _pubSub.Pub(_guildLeaveKey, guildStr);
// forwards dms
public async Task LateExecute(IGuild guild, IUserMessage msg)
{
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 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));
}
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).ConfigureAwait(false);
}
catch
{
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).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
}
}
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();
if (cmd != null)
{
uow.Remove(cmd);
uow.SaveChanges();
return true;
}
}
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();
if (cmd != null)
{
uow.Remove(cmd);
if (_autoCommands.TryGetValue(cmd.GuildId, out var autos))
if (autos.TryRemove(cmd.Id, out var timer))
timer.Change(Timeout.Infinite, Timeout.Infinite);
uow.SaveChanges();
return true;
}
}
return false;
}
public async Task<bool> SetAvatar(string img)
{
if (string.IsNullOrWhiteSpace(img))
return false;
if (!Uri.IsWellFormedUriString(img, UriKind.Absolute))
return false;
var uri = new Uri(img);
using (var http = _httpFactory.CreateClient())
using (var sr = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
{
if (!sr.IsImage())
return false;
// i can't just do ReadAsStreamAsync because dicord.net's image poops itself
var imgData = await sr.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
using (var imgStream = imgData.ToStream())
{
await _client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false);
}
}
return true;
}
public void ClearStartupCommands()
{
using (var uow = _db.GetDbContext())
{
var toRemove = uow
.AutoCommands
.AsNoTracking()
.Where(x => x.Interval == 0);
uow.AutoCommands.RemoveRange(toRemove);
uow.SaveChanges();
}
}
public Task ReloadImagesAsync()
=> _pubSub.Pub(_imagesReloadKey, true);
public bool ForwardMessages()
{
var isForwarding = false;
_bss.ModifyConfig(config => { isForwarding = config.ForwardMessages = !config.ForwardMessages; });
return isForwarding;
}
public bool ForwardToAll()
{
var isToAll = false;
_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});
public Task SetStreamAsync(string name, string link)
=> _pubSub.Pub(_activitySetKey, new() { Name = name, Link = link, Type = ActivityType.Streaming });
private sealed class ActivityPubData
{
public string Name { get; init; }
public string Link { get; init; }
public ActivityType Type { get; init; }
}
private sealed class ActivityPubData
{
public string Name { get; init; }
public string Link { get; init; }
public ActivityType Type { get; init; }
}
}

View File

@@ -1,13 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common;
using NadekoBot.Common.Replacements;
using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Services;
@@ -16,511 +12,509 @@ using NadekoBot.Db;
using NadekoBot.Extensions;
using NadekoBot.Modules.Permissions.Services;
using Newtonsoft.Json;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class UserPunishService : INService
{
public class UserPunishService : INService
private readonly MuteService _mute;
private readonly DbService _db;
private readonly BlacklistService _blacklistService;
private readonly BotConfigService _bcs;
private readonly Timer _warnExpiryTimer;
public UserPunishService(MuteService mute, DbService db, BlacklistService blacklistService, BotConfigService bcs)
{
private readonly MuteService _mute;
private readonly DbService _db;
private readonly BlacklistService _blacklistService;
private readonly BotConfigService _bcs;
private readonly Timer _warnExpiryTimer;
_mute = mute;
_db = db;
_blacklistService = blacklistService;
_bcs = bcs;
public UserPunishService(MuteService mute, DbService db, BlacklistService blacklistService, BotConfigService bcs)
_warnExpiryTimer = new Timer(async _ =>
{
_mute = mute;
_db = db;
_blacklistService = blacklistService;
_bcs = bcs;
await CheckAllWarnExpiresAsync();
}, null, TimeSpan.FromSeconds(0), TimeSpan.FromHours(12));
}
_warnExpiryTimer = new Timer(async _ =>
{
await CheckAllWarnExpiresAsync();
}, null, TimeSpan.FromSeconds(0), TimeSpan.FromHours(12));
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))
reason = "-";
var guildId = guild.Id;
var warn = new Warning()
{
UserId = userId,
GuildId = guildId,
Forgiven = false,
Reason = reason,
Moderator = modName,
Weight = weight,
};
int warnings = 1;
List<WarningPunishment> ps;
using (var uow = _db.GetDbContext())
{
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);
uow.Warnings.Add(warn);
uow.SaveChanges();
}
public async Task<WarningPunishment> Warn(IGuild guild, ulong userId, IUser mod, int weight, string reason)
var p = ps.FirstOrDefault(x => x.Count == warnings);
if (p != null)
{
if (weight <= 0)
throw new ArgumentOutOfRangeException(nameof(weight));
var modName = mod.ToString();
var user = await guild.GetUserAsync(userId).ConfigureAwait(false);
if (user is null)
return null;
if (string.IsNullOrWhiteSpace(reason))
reason = "-";
var guildId = guild.Id;
var warn = new Warning()
{
UserId = userId,
GuildId = guildId,
Forgiven = false,
Reason = reason,
Moderator = modName,
Weight = weight,
};
int warnings = 1;
List<WarningPunishment> ps;
using (var uow = _db.GetDbContext())
{
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);
uow.Warnings.Add(warn);
uow.SaveChanges();
}
var p = ps.FirstOrDefault(x => x.Count == warnings);
if (p != null)
{
var user = await guild.GetUserAsync(userId).ConfigureAwait(false);
if (user is null)
return null;
await ApplyPunishment(guild, user, mod, p.Punishment, p.Time, p.RoleId, "Warned too many times.");
return p;
}
return null;
await ApplyPunishment(guild, user, mod, p.Punishment, p.Time, p.RoleId, "Warned too many times.");
return p;
}
public async Task ApplyPunishment(IGuild guild, IGuildUser user, IUser mod, PunishmentAction p, int minutes,
ulong? roleId, string reason)
{
return null;
}
if (!await CheckPermission(guild, p))
return;
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:
if (minutes == 0)
await _mute.MuteUser(user, mod, reason: reason).ConfigureAwait(false);
else
await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), reason: reason)
.ConfigureAwait(false);
break;
case PunishmentAction.VoiceMute:
if (minutes == 0)
await _mute.MuteUser(user, mod, MuteType.Voice, reason).ConfigureAwait(false);
else
await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), MuteType.Voice, reason)
.ConfigureAwait(false);
break;
case PunishmentAction.ChatMute:
if (minutes == 0)
await _mute.MuteUser(user, mod, MuteType.Chat, reason).ConfigureAwait(false);
else
await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), MuteType.Chat, reason)
.ConfigureAwait(false);
break;
case PunishmentAction.Kick:
await user.KickAsync(reason).ConfigureAwait(false);
break;
case PunishmentAction.Ban:
if (minutes == 0)
await guild.AddBanAsync(user, reason: reason, pruneDays: 7).ConfigureAwait(false);
else
await _mute.TimedBan(user.Guild, user, TimeSpan.FromMinutes(minutes), reason)
.ConfigureAwait(false);
break;
case PunishmentAction.Softban:
await guild.AddBanAsync(user, 7, reason: $"Softban | {reason}").ConfigureAwait(false);
try
{
await guild.RemoveBanAsync(user).ConfigureAwait(false);
}
catch
{
await guild.RemoveBanAsync(user).ConfigureAwait(false);
}
break;
case PunishmentAction.RemoveRoles:
await user.RemoveRolesAsync(user.GetRoles().Where(x => !x.IsManaged && x != x.Guild.EveryoneRole))
switch (p)
{
case PunishmentAction.Mute:
if (minutes == 0)
await _mute.MuteUser(user, mod, reason: reason).ConfigureAwait(false);
else
await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), reason: reason)
.ConfigureAwait(false);
break;
case PunishmentAction.AddRole:
if (roleId is null)
return;
var role = guild.GetRole(roleId.Value);
if (role is not null)
{
if (minutes == 0)
await user.AddRoleAsync(role).ConfigureAwait(false);
else
await _mute.TimedRole(user, TimeSpan.FromMinutes(minutes), reason, role)
.ConfigureAwait(false);
}
break;
case PunishmentAction.VoiceMute:
if (minutes == 0)
await _mute.MuteUser(user, mod, MuteType.Voice, reason).ConfigureAwait(false);
else
await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), MuteType.Voice, reason)
.ConfigureAwait(false);
break;
case PunishmentAction.ChatMute:
if (minutes == 0)
await _mute.MuteUser(user, mod, MuteType.Chat, reason).ConfigureAwait(false);
else
await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), MuteType.Chat, reason)
.ConfigureAwait(false);
break;
case PunishmentAction.Kick:
await user.KickAsync(reason).ConfigureAwait(false);
break;
case PunishmentAction.Ban:
if (minutes == 0)
await guild.AddBanAsync(user, reason: reason, pruneDays: 7).ConfigureAwait(false);
else
await _mute.TimedBan(user.Guild, user, TimeSpan.FromMinutes(minutes), reason)
.ConfigureAwait(false);
break;
case PunishmentAction.Softban:
await guild.AddBanAsync(user, 7, reason: $"Softban | {reason}").ConfigureAwait(false);
try
{
await guild.RemoveBanAsync(user).ConfigureAwait(false);
}
catch
{
await guild.RemoveBanAsync(user).ConfigureAwait(false);
}
break;
case PunishmentAction.RemoveRoles:
await user.RemoveRolesAsync(user.GetRoles().Where(x => !x.IsManaged && x != x.Guild.EveryoneRole))
.ConfigureAwait(false);
break;
case PunishmentAction.AddRole:
if (roleId is null)
return;
var role = guild.GetRole(roleId.Value);
if (role is not null)
{
if (minutes == 0)
await user.AddRoleAsync(role).ConfigureAwait(false);
else
{
Log.Warning($"Can't find role {roleId.Value} on server {guild.Id} to apply punishment.");
}
await _mute.TimedRole(user, TimeSpan.FromMinutes(minutes), reason, role)
.ConfigureAwait(false);
}
else
{
Log.Warning($"Can't find role {roleId.Value} on server {guild.Id} to apply punishment.");
}
break;
default:
break;
}
break;
default:
break;
}
}
/// <summary>
/// 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)
{
/// <summary>
/// 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)
{
case PunishmentAction.Mute:
return botUser.GuildPermissions.MuteMembers && botUser.GuildPermissions.ManageRoles;
case PunishmentAction.Kick:
return botUser.GuildPermissions.KickMembers;
case PunishmentAction.Ban:
return botUser.GuildPermissions.BanMembers;
case PunishmentAction.Softban:
return botUser.GuildPermissions.BanMembers; // ban + unban
case PunishmentAction.RemoveRoles:
return botUser.GuildPermissions.ManageRoles;
case PunishmentAction.ChatMute:
return botUser.GuildPermissions.ManageRoles; // adds nadeko-mute role
case PunishmentAction.VoiceMute:
return botUser.GuildPermissions.MuteMembers;
case PunishmentAction.AddRole:
return botUser.GuildPermissions.ManageRoles;
default:
return true;
}
}
public async Task CheckAllWarnExpiresAsync()
var botUser = await guild.GetCurrentUserAsync();
switch (punish)
{
using (var uow = _db.GetDbContext())
{
var cleared = await uow.Database.ExecuteSqlRawAsync($@"UPDATE Warnings
case PunishmentAction.Mute:
return botUser.GuildPermissions.MuteMembers && botUser.GuildPermissions.ManageRoles;
case PunishmentAction.Kick:
return botUser.GuildPermissions.KickMembers;
case PunishmentAction.Ban:
return botUser.GuildPermissions.BanMembers;
case PunishmentAction.Softban:
return botUser.GuildPermissions.BanMembers; // ban + unban
case PunishmentAction.RemoveRoles:
return botUser.GuildPermissions.ManageRoles;
case PunishmentAction.ChatMute:
return botUser.GuildPermissions.ManageRoles; // adds nadeko-mute role
case PunishmentAction.VoiceMute:
return botUser.GuildPermissions.MuteMembers;
case PunishmentAction.AddRole:
return botUser.GuildPermissions.ManageRoles;
default:
return true;
}
}
public async Task CheckAllWarnExpiresAsync()
{
using (var uow = _db.GetDbContext())
{
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)
{
Log.Information($"Cleared {cleared} warnings and deleted {deleted} warnings due to expiry.");
}
if(cleared > 0 || deleted > 0)
{
Log.Information($"Cleared {cleared} warnings and deleted {deleted} warnings due to expiry.");
}
}
}
public async Task CheckWarnExpiresAsync(ulong guildId)
public async Task CheckWarnExpiresAsync(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
using (var uow = _db.GetDbContext())
var config = uow.GuildConfigsForId(guildId, inc => inc);
if (config.WarnExpireHours == 0)
return;
var hours = $"{-config.WarnExpireHours} hours";
if (config.WarnExpireAction == WarnExpireAction.Clear)
{
var config = uow.GuildConfigsForId(guildId, inc => inc);
if (config.WarnExpireHours == 0)
return;
var hours = $"{-config.WarnExpireHours} hours";
if (config.WarnExpireAction == WarnExpireAction.Clear)
{
await uow.Database.ExecuteSqlInterpolatedAsync($@"UPDATE warnings
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
}
else if (config.WarnExpireAction == WarnExpireAction.Delete)
{
await uow.Database.ExecuteSqlInterpolatedAsync($@"DELETE FROM warnings
WHERE GuildId={guildId}
AND DateAdded < datetime('now', {hours})");
}
await uow.SaveChangesAsync();
}
}
public Task<int> GetWarnExpire(ulong guildId)
{
using var uow = _db.GetDbContext();
var config = uow.GuildConfigsForId(guildId, set => set);
return Task.FromResult(config.WarnExpireHours / 24);
}
public async Task WarnExpireAsync(ulong guildId, int days, bool delete)
{
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guildId, inc => inc);
config.WarnExpireHours = days * 24;
config.WarnExpireAction = delete ? WarnExpireAction.Delete : WarnExpireAction.Clear;
await uow.SaveChangesAsync();
// no need to check for warn expires
if (config.WarnExpireHours == 0)
return;
}
await CheckWarnExpiresAsync(guildId);
}
public IGrouping<ulong, Warning>[] WarnlogAll(ulong gid)
{
using (var uow = _db.GetDbContext())
{
return uow.Warnings.GetForGuild(gid).GroupBy(x => x.UserId).ToArray();
}
}
public Warning[] UserWarnings(ulong gid, ulong userId)
{
using (var uow = _db.GetDbContext())
{
return uow.Warnings.ForId(gid, userId);
}
}
public async Task<bool> WarnClearAsync(ulong guildId, ulong userId, int index, string moderator)
{
bool toReturn = true;
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)
{
// these 3 don't make sense with time
if ((punish == PunishmentAction.Softban || punish == PunishmentAction.Kick || punish == PunishmentAction.RemoveRoles) && time != null)
return false;
if (number <= 0 || (time != null && time.Time > TimeSpan.FromDays(49)))
return false;
using (var uow = _db.GetDbContext())
{
var ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
var toDelete = ps.Where(x => x.Count == number);
uow.RemoveRange(toDelete);
ps.Add(new WarningPunishment()
{
Count = number,
Punishment = punish,
Time = (int?)(time?.Time.TotalMinutes) ?? 0,
RoleId = punish == PunishmentAction.AddRole ? role.Id : default(ulong?),
});
uow.SaveChanges();
}
return true;
}
public bool WarnPunishRemove(ulong guildId, int number)
{
if (number <= 0)
return false;
using (var uow = _db.GetDbContext())
{
var ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
var p = ps.FirstOrDefault(x => x.Count == number);
if (p != null)
{
uow.Remove(p);
uow.SaveChanges();
}
}
return true;
}
public WarningPunishment[] WarnPunishList(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
return uow.GuildConfigsForId(guildId, gc => gc.Include(x => x.WarnPunishments))
.WarnPunishments
.OrderBy(x => x.Count)
.ToArray();
}
}
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(" ");
var reason = string.Join(" ", split.Skip(1));
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();
//if user is null, means that person couldn't be found
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();
_blacklistService.BlacklistUsers(found);
return (bans, missing);
}
public string GetBanTemplate(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
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);
if (text is null)
{
if (template is null)
return;
uow.Remove(template);
}
else if (template is null)
{
uow.BanTemplates.Add(new BanTemplate()
{
GuildId = guildId,
Text = text,
});
}
else
{
template.Text = text;
}
uow.SaveChanges();
}
}
public SmartText GetBanUserDmEmbed(ICommandContext context, IGuildUser target, string defaultMessage,
string banReason, TimeSpan? duration)
{
return 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)
{
var template = GetBanTemplate(guild.Id);
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();
// if template isn't set, use the old message style
if (string.IsNullOrWhiteSpace(template))
{
template = JsonConvert.SerializeObject(new
{
color = _bcs.Data.Color.Error,
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,
description = template
});
}
var output = SmartText.CreateFrom(template);
return replacer.Replace(output);
await uow.SaveChangesAsync();
}
}
}
public Task<int> GetWarnExpire(ulong guildId)
{
using var uow = _db.GetDbContext();
var config = uow.GuildConfigsForId(guildId, set => set);
return Task.FromResult(config.WarnExpireHours / 24);
}
public async Task WarnExpireAsync(ulong guildId, int days, bool delete)
{
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigsForId(guildId, inc => inc);
config.WarnExpireHours = days * 24;
config.WarnExpireAction = delete ? WarnExpireAction.Delete : WarnExpireAction.Clear;
await uow.SaveChangesAsync();
// no need to check for warn expires
if (config.WarnExpireHours == 0)
return;
}
await CheckWarnExpiresAsync(guildId);
}
public IGrouping<ulong, Warning>[] WarnlogAll(ulong gid)
{
using (var uow = _db.GetDbContext())
{
return uow.Warnings.GetForGuild(gid).GroupBy(x => x.UserId).ToArray();
}
}
public Warning[] UserWarnings(ulong gid, ulong userId)
{
using (var uow = _db.GetDbContext())
{
return uow.Warnings.ForId(gid, userId);
}
}
public async Task<bool> WarnClearAsync(ulong guildId, ulong userId, int index, string moderator)
{
bool toReturn = true;
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)
{
// these 3 don't make sense with time
if ((punish == PunishmentAction.Softban || punish == PunishmentAction.Kick || punish == PunishmentAction.RemoveRoles) && time != null)
return false;
if (number <= 0 || (time != null && time.Time > TimeSpan.FromDays(49)))
return false;
using (var uow = _db.GetDbContext())
{
var ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
var toDelete = ps.Where(x => x.Count == number);
uow.RemoveRange(toDelete);
ps.Add(new WarningPunishment()
{
Count = number,
Punishment = punish,
Time = (int?)(time?.Time.TotalMinutes) ?? 0,
RoleId = punish == PunishmentAction.AddRole ? role.Id : default(ulong?),
});
uow.SaveChanges();
}
return true;
}
public bool WarnPunishRemove(ulong guildId, int number)
{
if (number <= 0)
return false;
using (var uow = _db.GetDbContext())
{
var ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
var p = ps.FirstOrDefault(x => x.Count == number);
if (p != null)
{
uow.Remove(p);
uow.SaveChanges();
}
}
return true;
}
public WarningPunishment[] WarnPunishList(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
return uow.GuildConfigsForId(guildId, gc => gc.Include(x => x.WarnPunishments))
.WarnPunishments
.OrderBy(x => x.Count)
.ToArray();
}
}
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(" ");
var reason = string.Join(" ", split.Skip(1));
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();
//if user is null, means that person couldn't be found
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();
_blacklistService.BlacklistUsers(found);
return (bans, missing);
}
public string GetBanTemplate(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
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);
if (text is null)
{
if (template is null)
return;
uow.Remove(template);
}
else if (template is null)
{
uow.BanTemplates.Add(new BanTemplate()
{
GuildId = guildId,
Text = text,
});
}
else
{
template.Text = text;
}
uow.SaveChanges();
}
}
public SmartText GetBanUserDmEmbed(ICommandContext context, IGuildUser target, string defaultMessage,
string banReason, TimeSpan? duration)
{
return 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)
{
var template = GetBanTemplate(guild.Id);
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();
// if template isn't set, use the old message style
if (string.IsNullOrWhiteSpace(template))
{
template = JsonConvert.SerializeObject(new
{
color = _bcs.Data.Color.Error,
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,
description = template
});
}
var output = SmartText.CreateFrom(template);
return replacer.Replace(output);
}
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
@@ -9,223 +6,221 @@ using Microsoft.EntityFrameworkCore;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Db;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
namespace NadekoBot.Modules.Administration.Services;
public class VcRoleService : INService
{
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; }
public VcRoleService(DiscordSocketClient client, Bot bot, DbService db)
{
private readonly DbService _db;
private readonly DiscordSocketClient _client;
_db = db;
_client = client;
public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; }
public ConcurrentDictionary<ulong, ConcurrentQueue<(bool, IGuildUser, IRole)>> ToAssign { get; }
_client.UserVoiceStateUpdated += ClientOnUserVoiceStateUpdated;
VcRoles = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>>();
ToAssign = new ConcurrentDictionary<ulong, ConcurrentQueue<(bool, IGuildUser, IRole)>>();
var missingRoles = new ConcurrentBag<VcRoleInfo>();
public VcRoleService(DiscordSocketClient client, Bot bot, DbService db)
using (var uow = db.GetDbContext())
{
_db = db;
_client = client;
_client.UserVoiceStateUpdated += ClientOnUserVoiceStateUpdated;
VcRoles = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>>();
ToAssign = new ConcurrentDictionary<ulong, ConcurrentQueue<(bool, IGuildUser, IRole)>>();
var missingRoles = new ConcurrentBag<VcRoleInfo>();
using (var uow = db.GetDbContext())
{
var guildIds = client.Guilds.Select(x => x.Id).ToList();
var configs = uow.Set<GuildConfig>()
.AsQueryable()
.Include(x => x.VcRoleInfos)
.Where(x => guildIds.Contains(x.GuildId))
.ToList();
var guildIds = client.Guilds.Select(x => x.Id).ToList();
var configs = uow.Set<GuildConfig>()
.AsQueryable()
.Include(x => x.VcRoleInfos)
.Where(x => guildIds.Contains(x.GuildId))
.ToList();
Task.WhenAll(configs.Select(InitializeVcRole));
}
Task.WhenAll(configs.Select(InitializeVcRole));
}
Task.Run(async () =>
Task.Run(async () =>
{
while (true)
{
while (true)
var tasks = ToAssign.Values.Select(queue => Task.Run(async () =>
{
var tasks = ToAssign.Values.Select(queue => Task.Run(async () =>
while (queue.TryDequeue(out var item))
{
while (queue.TryDequeue(out var item))
var (add, user, role) = item;
if (add)
{
var (add, user, role) = item;
if (add)
if (!user.RoleIds.Contains(role.Id))
{
if (!user.RoleIds.Contains(role.Id))
{
try { await user.AddRoleAsync(role).ConfigureAwait(false); } catch { }
}
try { await user.AddRoleAsync(role).ConfigureAwait(false); } catch { }
}
else
{
if (user.RoleIds.Contains(role.Id))
{
try { await user.RemoveRoleAsync(role).ConfigureAwait(false); } catch { }
}
}
await Task.Delay(250).ConfigureAwait(false);
}
}));
await Task.WhenAll(tasks.Append(Task.Delay(1000))).ConfigureAwait(false);
}
});
_client.LeftGuild += _client_LeftGuild;
bot.JoinedGuild += Bot_JoinedGuild;
}
private Task Bot_JoinedGuild(GuildConfig arg)
{
// includeall no longer loads vcrole
// 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 _ = InitializeVcRole(configWithVcRole);
}
return Task.CompletedTask;
}
private Task _client_LeftGuild(SocketGuild arg)
{
VcRoles.TryRemove(arg.Id, out _);
ToAssign.TryRemove(arg.Id, out _);
return Task.CompletedTask;
}
private async Task InitializeVcRole(GuildConfig gconf)
{
await Task.Yield();
var g = _client.GetGuild(gconf.GuildId);
if (g is null)
return;
var infos = new ConcurrentDictionary<ulong, IRole>();
var missingRoles = new List<VcRoleInfo>();
VcRoles.AddOrUpdate(gconf.GuildId, infos, delegate { return infos; });
foreach (var ri in gconf.VcRoleInfos)
{
var role = g.GetRole(ri.RoleId);
if (role is null)
{
missingRoles.Add(ri);
continue;
}
infos.TryAdd(ri.VoiceChannelId, role);
}
if (missingRoles.Any())
{
using (var uow = _db.GetDbContext())
{
Log.Warning($"Removing {missingRoles.Count} missing roles from {nameof(VcRoleService)}");
uow.RemoveRange(missingRoles);
await uow.SaveChangesAsync();
}
}
}
public void AddVcRole(ulong guildId, IRole role, ulong vcId)
{
if (role is null)
throw new ArgumentNullException(nameof(role));
var guildVcRoles = VcRoles.GetOrAdd(guildId, new ConcurrentDictionary<ulong, IRole>());
guildVcRoles.AddOrUpdate(vcId, role, (key, old) => role);
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 VcRoleInfo()
{
VoiceChannelId = vcId,
RoleId = role.Id,
}); // add new one
uow.SaveChanges();
}
}
public bool RemoveVcRole(ulong guildId, ulong vcId)
{
if (!VcRoles.TryGetValue(guildId, out var guildVcRoles))
return false;
if (!guildVcRoles.TryRemove(vcId, out _))
return false;
using (var uow = _db.GetDbContext())
{
var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.VcRoleInfos));
var toRemove = conf.VcRoleInfos.Where(x => x.VoiceChannelId == vcId).ToList();
uow.RemoveRange(toRemove);
uow.SaveChanges();
}
return true;
}
private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
SocketVoiceState newState)
{
var gusr = usr as SocketGuildUser;
if (gusr is null)
return Task.CompletedTask;
var oldVc = oldState.VoiceChannel;
var newVc = newState.VoiceChannel;
var _ = Task.Run(() =>
{
try
{
if (oldVc != newVc)
{
ulong guildId;
guildId = newVc?.Guild.Id ?? oldVc.Guild.Id;
if (VcRoles.TryGetValue(guildId, out ConcurrentDictionary<ulong, IRole> guildVcRoles))
else
{
//remove old
if (oldVc != null && guildVcRoles.TryGetValue(oldVc.Id, out IRole role))
if (user.RoleIds.Contains(role.Id))
{
Assign(false, gusr, role);
try { await user.RemoveRoleAsync(role).ConfigureAwait(false); } catch { }
}
//add new
if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role))
{
Assign(true, gusr, role);
}
}
await Task.Delay(250).ConfigureAwait(false);
}
}
catch (Exception ex)
{
Log.Warning(ex, "Error in VcRoleService VoiceStateUpdate");
}
});
return Task.CompletedTask;
}));
await Task.WhenAll(tasks.Append(Task.Delay(1000))).ConfigureAwait(false);
}
});
_client.LeftGuild += _client_LeftGuild;
bot.JoinedGuild += Bot_JoinedGuild;
}
private Task Bot_JoinedGuild(GuildConfig arg)
{
// includeall no longer loads vcrole
// 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 _ = InitializeVcRole(configWithVcRole);
}
private void Assign(bool v, SocketGuildUser gusr, IRole role)
return Task.CompletedTask;
}
private Task _client_LeftGuild(SocketGuild arg)
{
VcRoles.TryRemove(arg.Id, out _);
ToAssign.TryRemove(arg.Id, out _);
return Task.CompletedTask;
}
private async Task InitializeVcRole(GuildConfig gconf)
{
await Task.Yield();
var g = _client.GetGuild(gconf.GuildId);
if (g is null)
return;
var infos = new ConcurrentDictionary<ulong, IRole>();
var missingRoles = new List<VcRoleInfo>();
VcRoles.AddOrUpdate(gconf.GuildId, infos, delegate { return infos; });
foreach (var ri in gconf.VcRoleInfos)
{
var queue = ToAssign.GetOrAdd(gusr.Guild.Id, new ConcurrentQueue<(bool, IGuildUser, IRole)>());
queue.Enqueue((v, gusr, role));
var role = g.GetRole(ri.RoleId);
if (role is null)
{
missingRoles.Add(ri);
continue;
}
infos.TryAdd(ri.VoiceChannelId, role);
}
if (missingRoles.Any())
{
using (var uow = _db.GetDbContext())
{
Log.Warning($"Removing {missingRoles.Count} missing roles from {nameof(VcRoleService)}");
uow.RemoveRange(missingRoles);
await uow.SaveChangesAsync();
}
}
}
}
public void AddVcRole(ulong guildId, IRole role, ulong vcId)
{
if (role is null)
throw new ArgumentNullException(nameof(role));
var guildVcRoles = VcRoles.GetOrAdd(guildId, new ConcurrentDictionary<ulong, IRole>());
guildVcRoles.AddOrUpdate(vcId, role, (key, old) => role);
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 VcRoleInfo()
{
VoiceChannelId = vcId,
RoleId = role.Id,
}); // add new one
uow.SaveChanges();
}
}
public bool RemoveVcRole(ulong guildId, ulong vcId)
{
if (!VcRoles.TryGetValue(guildId, out var guildVcRoles))
return false;
if (!guildVcRoles.TryRemove(vcId, out _))
return false;
using (var uow = _db.GetDbContext())
{
var conf = uow.GuildConfigsForId(guildId, set => set.Include(x => x.VcRoleInfos));
var toRemove = conf.VcRoleInfos.Where(x => x.VoiceChannelId == vcId).ToList();
uow.RemoveRange(toRemove);
uow.SaveChanges();
}
return true;
}
private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
SocketVoiceState newState)
{
var gusr = usr as SocketGuildUser;
if (gusr is null)
return Task.CompletedTask;
var oldVc = oldState.VoiceChannel;
var newVc = newState.VoiceChannel;
var _ = Task.Run(() =>
{
try
{
if (oldVc != newVc)
{
ulong guildId;
guildId = newVc?.Guild.Id ?? oldVc.Guild.Id;
if (VcRoles.TryGetValue(guildId, out ConcurrentDictionary<ulong, IRole> guildVcRoles))
{
//remove old
if (oldVc != null && guildVcRoles.TryGetValue(oldVc.Id, out IRole role))
{
Assign(false, gusr, role);
}
//add new
if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role))
{
Assign(true, gusr, role);
}
}
}
}
catch (Exception ex)
{
Log.Warning(ex, "Error in VcRoleService VoiceStateUpdate");
}
});
return Task.CompletedTask;
}
private void Assign(bool v, SocketGuildUser gusr, IRole role)
{
var queue = ToAssign.GetOrAdd(gusr.Guild.Id, new ConcurrentQueue<(bool, IGuildUser, IRole)>());
queue.Enqueue((v, gusr, role));
}
}

View File

@@ -1,90 +1,87 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using System;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Administration.Services;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class TimeZoneCommands : NadekoSubmodule<GuildTimezoneService>
{
[Group]
public class TimeZoneCommands : NadekoSubmodule<GuildTimezoneService>
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Timezones(int page = 1)
{
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Timezones(int page = 1)
{
page--;
page--;
if (page < 0 || page > 20)
return;
if (page < 0 || page > 20)
return;
var timezones = TimeZoneInfo.GetSystemTimeZones()
.OrderBy(x => x.BaseUtcOffset)
.ToArray();
var timezonesPerPage = 20;
var timezones = TimeZoneInfo.GetSystemTimeZones()
.OrderBy(x => x.BaseUtcOffset)
.ToArray();
var timezonesPerPage = 20;
var curTime = DateTimeOffset.UtcNow;
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}";
}
});
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).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Timezone()
{
await ReplyConfirmLocalizedAsync(strs.timezone_guild(_service.GetTimeZoneOrUtc(ctx.Guild.Id))).ConfigureAwait(false);
}
[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; }
if (tz is null)
var i = 0;
var timezoneStrings = timezones
.Select(x => (x, ++i % 2 == 0))
.Select(data =>
{
await ReplyErrorLocalizedAsync(strs.timezone_not_found).ConfigureAwait(false);
return;
}
_service.SetTimeZone(ctx.Guild.Id, tz);
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}";
}
});
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).ConfigureAwait(false);
}
await SendConfirmAsync(tz.ToString()).ConfigureAwait(false);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Timezone()
{
await ReplyConfirmLocalizedAsync(strs.timezone_guild(_service.GetTimeZoneOrUtc(ctx.Guild.Id))).ConfigureAwait(false);
}
[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; }
if (tz is null)
{
await ReplyErrorLocalizedAsync(strs.timezone_not_found).ConfigureAwait(false);
return;
}
_service.SetTimeZone(ctx.Guild.Id, tz);
await SendConfirmAsync(tz.ToString()).ConfigureAwait(false);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using System.Linq;
using Discord;
using Discord.Commands;
using System.Threading.Tasks;
@@ -8,86 +7,85 @@ using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
namespace NadekoBot.Modules.Administration
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
public partial class Administration
[Group]
public class VcRoleCommands : NadekoSubmodule<VcRoleService>
{
[Group]
public class VcRoleCommands : NadekoSubmodule<VcRoleService>
[NadekoCommand, Aliases]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireContext(ContextType.Guild)]
public async Task VcRoleRm(ulong vcId)
{
[NadekoCommand, Aliases]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireContext(ContextType.Guild)]
public async Task VcRoleRm(ulong vcId)
if (_service.RemoveVcRole(ctx.Guild.Id, vcId))
{
if (_service.RemoveVcRole(ctx.Guild.Id, vcId))
{
await ReplyConfirmLocalizedAsync(strs.vcrole_removed(Format.Bold(vcId.ToString()))).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync(strs.vcrole_not_found).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.vcrole_removed(Format.Bold(vcId.ToString()))).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync(strs.vcrole_not_found).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireContext(ContextType.Guild)]
public async Task VcRole([Leftover] IRole role = null)
{
var user = (IGuildUser)ctx.User;
var vc = user.VoiceChannel;
if (vc is null || vc.GuildId != user.GuildId)
{
await ReplyErrorLocalizedAsync(strs.must_be_in_voice).ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireContext(ContextType.Guild)]
public async Task VcRole([Leftover] IRole role = null)
if (role is null)
{
var user = (IGuildUser)ctx.User;
var vc = user.VoiceChannel;
if (vc is null || vc.GuildId != user.GuildId)
if (_service.RemoveVcRole(ctx.Guild.Id, vc.Id))
{
await ReplyErrorLocalizedAsync(strs.must_be_in_voice).ConfigureAwait(false);
return;
}
if (role is null)
{
if (_service.RemoveVcRole(ctx.Guild.Id, vc.Id))
{
await ReplyConfirmLocalizedAsync(strs.vcrole_removed(Format.Bold(vc.Name))).ConfigureAwait(false);
}
}
else
{
_service.AddVcRole(ctx.Guild.Id, role, vc.Id);
await ReplyConfirmLocalizedAsync(strs.vcrole_added(Format.Bold(vc.Name), Format.Bold(role.Name))).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.vcrole_removed(Format.Bold(vc.Name))).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task VcRoleList()
else
{
var guild = (SocketGuild)ctx.Guild;
string text;
if (_service.VcRoles.TryGetValue(ctx.Guild.Id, out ConcurrentDictionary<ulong, IRole> 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}"));
}
}
else
_service.AddVcRole(ctx.Guild.Id, role, vc.Id);
await ReplyConfirmLocalizedAsync(strs.vcrole_added(Format.Bold(vc.Name), Format.Bold(role.Name))).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task VcRoleList()
{
var guild = (SocketGuild)ctx.Guild;
string text;
if (_service.VcRoles.TryGetValue(ctx.Guild.Id, out ConcurrentDictionary<ulong, IRole> roles))
{
if (!roles.Any())
{
text = GetText(strs.no_vcroles);
}
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
.WithTitle(GetText(strs.vc_role_list))
.WithDescription(text))
.ConfigureAwait(false);
else
{
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))
.ConfigureAwait(false);
}
}
}
}

View File

@@ -1,30 +1,29 @@
using NadekoBot.Services.Database.Models;
using NadekoBot.Common;
namespace NadekoBot.Modules.CustomReactions
{
public class ExportedExpr
{
public string Res { get; set; }
public string Id { get; set; }
public bool Ad { get; set; }
public bool Dm { get; set; }
public bool At { get; set; }
public bool Ca { get; set; }
public string[] React;
namespace NadekoBot.Modules.CustomReactions;
public static ExportedExpr FromModel(CustomReaction cr)
=> new ExportedExpr()
{
Res = cr.Response,
Id = ((kwum)cr.Id).ToString(),
Ad = cr.AutoDeleteTrigger,
At = cr.AllowTarget,
Ca = cr.ContainsAnywhere,
Dm = cr.DmResponse,
React = string.IsNullOrWhiteSpace(cr.Reactions)
? null
: cr.GetReactions(),
};
}
public class ExportedExpr
{
public string Res { get; set; }
public string Id { get; set; }
public bool Ad { get; set; }
public bool Dm { get; set; }
public bool At { get; set; }
public bool Ca { get; set; }
public string[] React;
public static ExportedExpr FromModel(CustomReaction cr)
=> new ExportedExpr()
{
Res = cr.Response,
Id = ((kwum)cr.Id).ToString(),
Ad = cr.AutoDeleteTrigger,
At = cr.AllowTarget,
Ca = cr.ContainsAnywhere,
Dm = cr.DmResponse,
React = string.IsNullOrWhiteSpace(cr.Reactions)
? null
: cr.GetReactions(),
};
}

View File

@@ -1,362 +1,358 @@
using Discord;
using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Services;
using NadekoBot.Extensions;
using NadekoBot.Modules.CustomReactions.Services;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using NadekoBot.Common;
namespace NadekoBot.Modules.CustomReactions
namespace NadekoBot.Modules.CustomReactions;
public class CustomReactions : NadekoModule<CustomReactionsService>
{
public class CustomReactions : NadekoModule<CustomReactionsService>
private readonly IBotCredentials _creds;
private readonly IHttpClientFactory _clientFactory;
public CustomReactions(IBotCredentials creds, IHttpClientFactory clientFactory)
{
private readonly IBotCredentials _creds;
private readonly IHttpClientFactory _clientFactory;
_creds = creds;
_clientFactory = clientFactory;
}
public CustomReactions(IBotCredentials creds, IHttpClientFactory clientFactory)
private bool AdminInGuildOrOwnerInDm() => (ctx.Guild is null && _creds.IsOwner(ctx.User))
|| (ctx.Guild != null && ((IGuildUser)ctx.User).GuildPermissions.Administrator);
[NadekoCommand, Aliases]
public async Task AddCustReact(string key, [Leftover] string message)
{
var channel = ctx.Channel as ITextChannel;
if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(key))
return;
if (!AdminInGuildOrOwnerInDm())
{
_creds = creds;
_clientFactory = clientFactory;
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
private bool AdminInGuildOrOwnerInDm() => (ctx.Guild is null && _creds.IsOwner(ctx.User))
|| (ctx.Guild != null && ((IGuildUser)ctx.User).GuildPermissions.Administrator);
var cr = await _service.AddAsync(ctx.Guild?.Id, key, message);
[NadekoCommand, Aliases]
public async Task AddCustReact(string key, [Leftover] string message)
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
.WithTitle(GetText(strs.new_cust_react))
.WithDescription($"#{(kwum)cr.Id}")
.AddField(GetText(strs.trigger), key)
.AddField(GetText(strs.response), message.Length > 1024 ? GetText(strs.redacted_too_long) : message)
).ConfigureAwait(false);
}
[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))
{
var channel = ctx.Channel as ITextChannel;
if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(key))
return;
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
var cr = await _service.AddAsync(ctx.Guild?.Id, key, message);
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
var cr = await _service.EditAsync(ctx.Guild?.Id, (int)id, message).ConfigureAwait(false);
if (cr != null)
{
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
.WithTitle(GetText(strs.new_cust_react))
.WithDescription($"#{(kwum)cr.Id}")
.AddField(GetText(strs.trigger), key)
.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)
).ConfigureAwait(false);
).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
public async Task EditCustReact(kwum id, [Leftover] string message)
else
{
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))
{
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
var cr = await _service.EditAsync(ctx.Guild?.Id, (int)id, message).ConfigureAwait(false);
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)
).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync(strs.edit_fail).ConfigureAwait(false);
}
await ReplyErrorLocalizedAsync(strs.edit_fail).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[Priority(1)]
public async Task ListCustReact(int page = 1)
[NadekoCommand, Aliases]
[Priority(1)]
public async Task ListCustReact(int page = 1)
{
if (--page < 0 || page > 999)
return;
var customReactions = _service.GetCustomReactionsFor(ctx.Guild?.Id);
if (customReactions is null || !customReactions.Any())
{
if (--page < 0 || page > 999)
return;
var customReactions = _service.GetCustomReactionsFor(ctx.Guild?.Id);
if (customReactions is null || !customReactions.Any())
{
await ReplyErrorLocalizedAsync(strs.no_found).ConfigureAwait(false);
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())))
.JoinWith('\n');
return _eb.Create().WithOkColor()
.WithTitle(GetText(strs.custom_reactions))
.WithDescription(desc);
}, customReactions.Length, 20);
await ReplyErrorLocalizedAsync(strs.no_found).ConfigureAwait(false);
return;
}
public enum All
await ctx.SendPaginatedConfirmAsync(page, pageFunc: curPage =>
{
All
}
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())))
.JoinWith('\n');
[NadekoCommand, Aliases]
[Priority(0)]
public async Task ListCustReact(All _)
return _eb.Create().WithOkColor()
.WithTitle(GetText(strs.custom_reactions))
.WithDescription(desc);
}, customReactions.Length, 20);
}
public enum All
{
All
}
[NadekoCommand, Aliases]
[Priority(0)]
public async Task ListCustReact(All _)
{
await ReplyPendingLocalizedAsync(strs.obsolete_use(Format.Code($"{Prefix}crsexport")));
await CrsExport();
}
[NadekoCommand, Aliases]
public async Task ListCustReactG(int page = 1)
{
await ReplyPendingLocalizedAsync(strs.obsolete_use(Format.Code($"{Prefix}crsexport")));
await CrsExport();
}
[NadekoCommand, Aliases]
public async Task ShowCustReact(kwum id)
{
var found = _service.GetCustomReaction(ctx.Guild?.Id, (int)id);
if (found is null)
{
await ReplyPendingLocalizedAsync(strs.obsolete_use(Format.Code($"{Prefix}crsexport")));
await CrsExport();
await ReplyErrorLocalizedAsync(strs.no_found_id).ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
public async Task ListCustReactG(int page = 1)
else
{
await ReplyPendingLocalizedAsync(strs.obsolete_use(Format.Code($"{Prefix}crsexport")));
await CrsExport();
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("](", "]\\("))
).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
public async Task ShowCustReact(kwum id)
[NadekoCommand, Aliases]
public async Task DelCustReact(kwum id)
{
if (!AdminInGuildOrOwnerInDm())
{
var found = _service.GetCustomReaction(ctx.Guild?.Id, (int)id);
if (found is null)
{
await ReplyErrorLocalizedAsync(strs.no_found_id).ConfigureAwait(false);
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("](", "]\\("))
).ConfigureAwait(false);
}
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
public async Task DelCustReact(kwum id)
var cr = await _service.DeleteAsync(ctx.Guild?.Id, (int)id);
if (cr != null)
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
var cr = await _service.DeleteAsync(ctx.Guild?.Id, (int)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))).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync(strs.no_found_id).ConfigureAwait(false);
}
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))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
public async Task CrReact(kwum id, params string[] emojiStrs)
else
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
var cr = _service.GetCustomReaction(ctx.Guild?.Id, id);
if (cr is null)
{
await ReplyErrorLocalizedAsync(strs.no_found).ConfigureAwait(false);
return;
}
if (emojiStrs.Length == 0)
{
await _service.ResetCrReactions(ctx.Guild?.Id, id);
await ReplyConfirmLocalizedAsync(strs.crr_reset(Format.Bold(id.ToString()))).ConfigureAwait(false);
return;
}
List<string> 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
try
{
await ctx.Message.AddReactionAsync(emote).ConfigureAwait(false);
await Task.Delay(100).ConfigureAwait(false);
succ.Add(emojiStr);
if (succ.Count >= 3)
break;
}
catch { }
}
if(succ.Count == 0)
{
await ReplyErrorLocalizedAsync(strs.invalid_emojis).ConfigureAwait(false);
return;
}
await _service.SetCrReactions(ctx.Guild?.Id, id, succ);
await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()), string.Join(", ", succ.Select(x => x.ToString())))).ConfigureAwait(false);
await ReplyErrorLocalizedAsync(strs.no_found_id).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
public async Task CrReact(kwum id, params string[] emojiStrs)
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
public Task CrCa(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.ContainsAnywhere);
var cr = _service.GetCustomReaction(ctx.Guild?.Id, id);
if (cr is null)
{
await ReplyErrorLocalizedAsync(strs.no_found).ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
public Task CrDm(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.DmResponse);
if (emojiStrs.Length == 0)
{
await _service.ResetCrReactions(ctx.Guild?.Id, id);
await ReplyConfirmLocalizedAsync(strs.crr_reset(Format.Bold(id.ToString()))).ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
public Task CrAd(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.AutoDelete);
List<string> 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
try
{
await ctx.Message.AddReactionAsync(emote).ConfigureAwait(false);
await Task.Delay(100).ConfigureAwait(false);
succ.Add(emojiStr);
if (succ.Count >= 3)
break;
}
catch { }
}
if(succ.Count == 0)
{
await ReplyErrorLocalizedAsync(strs.invalid_emojis).ConfigureAwait(false);
return;
}
await _service.SetCrReactions(ctx.Guild?.Id, id, succ);
await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()), string.Join(", ", succ.Select(x => x.ToString())))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
public Task CrCa(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.ContainsAnywhere);
[NadekoCommand, Aliases]
public Task CrDm(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.DmResponse);
[NadekoCommand, Aliases]
public Task CrAd(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.AutoDelete);
[NadekoCommand, Aliases]
public Task CrAt(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.AllowTarget);
[NadekoCommand, Aliases]
public Task CrAt(kwum id)
=> InternalCrEdit(id, CustomReactionsService.CrField.AllowTarget);
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task CrsReload()
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task CrsReload()
{
await _service.TriggerReloadCustomReactions();
await ctx.OkAsync();
}
private async Task InternalCrEdit(kwum id, CustomReactionsService.CrField option)
{
var cr = _service.GetCustomReaction(ctx.Guild?.Id, id);
if (!AdminInGuildOrOwnerInDm())
{
await _service.TriggerReloadCustomReactions();
await ctx.OkAsync();
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
var (success, newVal) = await _service.ToggleCrOptionAsync(id, option).ConfigureAwait(false);
if (!success)
{
await ReplyErrorLocalizedAsync(strs.no_found_id).ConfigureAwait(false);
return;
}
private async Task InternalCrEdit(kwum id, CustomReactionsService.CrField option)
if (newVal)
{
var cr = _service.GetCustomReaction(ctx.Guild?.Id, id);
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
var (success, newVal) = await _service.ToggleCrOptionAsync(id, option).ConfigureAwait(false);
if (!success)
{
await ReplyErrorLocalizedAsync(strs.no_found_id).ConfigureAwait(false);
return;
}
if (newVal)
{
await ReplyConfirmLocalizedAsync(strs.option_enabled(Format.Code(option.ToString()), Format.Code(id.ToString()))).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.option_disabled(Format.Code(option.ToString()), Format.Code(id.ToString()))).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.option_enabled(Format.Code(option.ToString()), Format.Code(id.ToString()))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task CrClear()
else
{
if (await PromptUserConfirmAsync(_eb.Create()
await ReplyConfirmLocalizedAsync(strs.option_disabled(Format.Code(option.ToString()), Format.Code(id.ToString()))).ConfigureAwait(false);
}
}
[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.")).ConfigureAwait(false))
{
var count = _service.DeleteAllCustomReactions(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync(strs.cleared(count));
}
}
[NadekoCommand, Aliases]
public async Task CrsExport()
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
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);
var count = _service.DeleteAllCustomReactions(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync(strs.cleared(count));
}
}
[NadekoCommand, Aliases]
[NadekoCommand, Aliases]
public async Task CrsExport()
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
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);
}
[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())
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
input = input?.Trim();
_ = ctx.Channel.TriggerTypingAsync();
if (input is null)
{
var attachment = ctx.Message.Attachments.FirstOrDefault();
if (attachment is null)
{
await ReplyErrorLocalizedAsync(strs.expr_import_no_input);
return;
}
using var client = _clientFactory.CreateClient();
input = await client.GetStringAsync(attachment.Url);
if (string.IsNullOrWhiteSpace(input))
{
await ReplyErrorLocalizedAsync(strs.expr_import_no_input);
return;
}
}
var succ = await _service.ImportCrsAsync(ctx.Guild?.Id, input);
if (!succ)
{
await ReplyErrorLocalizedAsync(strs.expr_import_invalid_data);
return;
}
await ctx.OkAsync();
await ReplyErrorLocalizedAsync(strs.insuff_perms).ConfigureAwait(false);
return;
}
input = input?.Trim();
_ = ctx.Channel.TriggerTypingAsync();
if (input is null)
{
var attachment = ctx.Message.Attachments.FirstOrDefault();
if (attachment is null)
{
await ReplyErrorLocalizedAsync(strs.expr_import_no_input);
return;
}
using var client = _clientFactory.CreateClient();
input = await client.GetStringAsync(attachment.Url);
if (string.IsNullOrWhiteSpace(input))
{
await ReplyErrorLocalizedAsync(strs.expr_import_no_input);
return;
}
}
var succ = await _service.ImportCrsAsync(ctx.Guild?.Id, input);
if (!succ)
{
await ReplyErrorLocalizedAsync(strs.expr_import_invalid_data);
return;
}
await ctx.OkAsync();
}
}
}

View File

@@ -3,94 +3,92 @@ using Discord.WebSocket;
using NadekoBot.Common.Replacements;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace NadekoBot.Modules.CustomReactions.Extensions
namespace NadekoBot.Modules.CustomReactions.Extensions;
public static class CustomReactionExtensions
{
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)
{
private static string ResolveTriggerString(this string str, DiscordSocketClient client)
=> str.Replace("%bot.mention%", client.CurrentUser.Mention, StringComparison.Ordinal);
var channel = cr.DmResponse
? await ctx.Author.GetOrCreateDMChannelAsync().ConfigureAwait(false)
: ctx.Channel;
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage ctx,
DiscordSocketClient client, bool sanitize)
var trigger = cr.Trigger.ResolveTriggerString(client);
var substringIndex = trigger.Length;
if (cr.ContainsAnywhere)
{
var channel = cr.DmResponse
? await ctx.Author.GetOrCreateDMChannelAsync().ConfigureAwait(false)
: ctx.Channel;
var trigger = cr.Trigger.ResolveTriggerString(client);
var substringIndex = trigger.Length;
if (cr.ContainsAnywhere)
{
var pos = ctx.Content.AsSpan().GetWordPosition(trigger);
if (pos == WordPosition.Start)
substringIndex += 1;
else if (pos == WordPosition.End)
substringIndex = ctx.Content.Length;
else if (pos == WordPosition.Middle)
substringIndex += ctx.Content.IndexOf(trigger, StringComparison.InvariantCulture);
}
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.Substring(substringIndex).Trim()
: ctx.Content.Substring(substringIndex).Trim().SanitizeMentions(true))
.Build();
var text = SmartText.CreateFrom(cr.Response);
text = rep.Replace(text);
return await channel.SendAsync(text, sanitize);
var pos = ctx.Content.AsSpan().GetWordPosition(trigger);
if (pos == WordPosition.Start)
substringIndex += 1;
else if (pos == WordPosition.End)
substringIndex = ctx.Content.Length;
else if (pos == WordPosition.Middle)
substringIndex += ctx.Content.IndexOf(trigger, StringComparison.InvariantCulture);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static WordPosition GetWordPosition(this ReadOnlySpan<char> str, in ReadOnlySpan<char> word)
{
var wordIndex = str.IndexOf(word, StringComparison.OrdinalIgnoreCase);
if (wordIndex == -1)
return WordPosition.None;
var canMentionEveryone = (ctx.Author as IGuildUser)?.GuildPermissions.MentionEveryone ?? true;
if (wordIndex == 0)
{
if (word.Length < str.Length && str.isValidWordDivider(word.Length))
return WordPosition.Start;
}
else if ((wordIndex + word.Length) == str.Length)
{
if (str.isValidWordDivider(wordIndex - 1))
return WordPosition.End;
}
else if (str.isValidWordDivider(wordIndex - 1) && str.isValidWordDivider(wordIndex + word.Length))
return WordPosition.Middle;
var rep = new ReplacementBuilder()
.WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild as SocketGuild, client)
.WithOverride("%target%", () => canMentionEveryone
? ctx.Content.Substring(substringIndex).Trim()
: ctx.Content.Substring(substringIndex).Trim().SanitizeMentions(true))
.Build();
var text = SmartText.CreateFrom(cr.Response);
text = rep.Replace(text);
return await channel.SendAsync(text, sanitize);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static WordPosition GetWordPosition(this ReadOnlySpan<char> str, in ReadOnlySpan<char> word)
{
var wordIndex = str.IndexOf(word, StringComparison.OrdinalIgnoreCase);
if (wordIndex == -1)
return WordPosition.None;
}
private static bool isValidWordDivider(this in ReadOnlySpan<char> str, int index)
if (wordIndex == 0)
{
var ch = str[index];
if (ch >= 'a' && ch <= 'z')
return false;
if (ch >= 'A' && ch <= 'Z')
return false;
if (ch >= '1' && ch <= '9')
return false;
return true;
if (word.Length < str.Length && str.isValidWordDivider(word.Length))
return WordPosition.Start;
}
else if ((wordIndex + word.Length) == str.Length)
{
if (str.isValidWordDivider(wordIndex - 1))
return WordPosition.End;
}
else if (str.isValidWordDivider(wordIndex - 1) && str.isValidWordDivider(wordIndex + word.Length))
return WordPosition.Middle;
return WordPosition.None;
}
public enum WordPosition
private static bool isValidWordDivider(this in ReadOnlySpan<char> str, int index)
{
None,
Start,
Middle,
End,
var ch = str[index];
if (ch >= 'a' && ch <= 'z')
return false;
if (ch >= 'A' && ch <= 'Z')
return false;
if (ch >= '1' && ch <= '9')
return false;
return true;
}
}
public enum WordPosition
{
None,
Start,
Middle,
End,
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
@@ -14,172 +12,171 @@ using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
using NadekoBot.Modules.Games.Services;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
// wth is this, needs full rewrite
public partial class Gambling
{
// wth is this, needs full rewrite
public partial class Gambling
[Group]
public class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
{
[Group]
public class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
private readonly ICurrencyService _cs;
private readonly DiscordSocketClient _client;
private readonly GamesConfigService _gamesConf;
public AnimalRacingCommands(ICurrencyService cs, DiscordSocketClient client,
GamblingConfigService gamblingConf, GamesConfigService gamesConf) : base(gamblingConf)
{
private readonly ICurrencyService _cs;
private readonly DiscordSocketClient _client;
private readonly GamesConfigService _gamesConf;
_cs = cs;
_client = client;
_gamesConf = gamesConf;
}
public AnimalRacingCommands(ICurrencyService cs, DiscordSocketClient client,
GamblingConfigService gamblingConf, GamesConfigService gamesConf) : base(gamblingConf)
private IUserMessage raceMessage = null;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[NadekoOptionsAttribute(typeof(RaceOptions))]
public Task Race(params string[] args)
{
var (options, success) = OptionsParser.ParseFrom(new RaceOptions(), args);
var ar = new AnimalRace(options, _cs, _gamesConf.Data.RaceAnimals.Shuffle());
if (!_service.AnimalRaces.TryAdd(ctx.Guild.Id, ar))
return SendErrorAsync(GetText(strs.animal_race), GetText(strs.animal_race_already_started));
ar.Initialize();
var count = 0;
Task _client_MessageReceived(SocketMessage arg)
{
_cs = cs;
_client = client;
_gamesConf = gamesConf;
}
private IUserMessage raceMessage = null;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[NadekoOptionsAttribute(typeof(RaceOptions))]
public Task Race(params string[] args)
{
var (options, success) = OptionsParser.ParseFrom(new RaceOptions(), args);
var ar = new AnimalRace(options, _cs, _gamesConf.Data.RaceAnimals.Shuffle());
if (!_service.AnimalRaces.TryAdd(ctx.Guild.Id, ar))
return SendErrorAsync(GetText(strs.animal_race), GetText(strs.animal_race_already_started));
ar.Initialize();
var count = 0;
Task _client_MessageReceived(SocketMessage arg)
var _ = Task.Run(() =>
{
var _ = Task.Run(() =>
try
{
try
if (arg.Channel.Id == ctx.Channel.Id)
{
if (arg.Channel.Id == ctx.Channel.Id)
if (ar.CurrentPhase == AnimalRace.Phase.Running && ++count % 9 == 0)
{
if (ar.CurrentPhase == AnimalRace.Phase.Running && ++count % 9 == 0)
{
raceMessage = null;
}
raceMessage = null;
}
}
catch { }
});
return Task.CompletedTask;
}
Task Ar_OnEnded(AnimalRace race)
{
_client.MessageReceived -= _client_MessageReceived;
_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),
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)));
}
}
ar.OnStartingFailed += Ar_OnStartingFailed;
ar.OnStateUpdate += Ar_OnStateUpdate;
ar.OnEnded += Ar_OnEnded;
ar.OnStarted += Ar_OnStarted;
_client.MessageReceived += _client_MessageReceived;
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_starting(options.StartTime)),
footer: GetText(strs.animal_race_join_instr(Prefix)));
catch { }
});
return Task.CompletedTask;
}
private Task Ar_OnStarted(AnimalRace race)
Task Ar_OnEnded(AnimalRace race)
{
if (race.Users.Count == race.MaxUsers)
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full));
_client.MessageReceived -= _client_MessageReceived;
_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),
winner.Animal.Icon,
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)));
}
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_won(Format.Bold(winner.Username), winner.Animal.Icon)));
}
}
private async Task Ar_OnStateUpdate(AnimalRace race)
{
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
ar.OnStartingFailed += Ar_OnStartingFailed;
ar.OnStateUpdate += Ar_OnStateUpdate;
ar.OnEnded += Ar_OnEnded;
ar.OnStarted += Ar_OnStarted;
_client.MessageReceived += _client_MessageReceived;
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_starting(options.StartTime)),
footer: GetText(strs.animal_race_join_instr(Prefix)));
}
private Task Ar_OnStarted(AnimalRace race)
{
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)));
}
private async Task Ar_OnStateUpdate(AnimalRace race)
{
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
{String.Join("\n", race.Users.Select(p =>
{
var index = race.FinishedUsers.IndexOf(p);
var extra = (index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}");
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
}))}
{
var index = race.FinishedUsers.IndexOf(p);
var extra = (index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}");
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
}))}
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
var msg = raceMessage;
var msg = raceMessage;
if (msg is null)
raceMessage = await SendConfirmAsync(text)
.ConfigureAwait(false);
else
await msg.ModifyAsync(x => x.Embed = _eb.Create()
if (msg is null)
raceMessage = await SendConfirmAsync(text)
.ConfigureAwait(false);
else
await msg.ModifyAsync(x => x.Embed = _eb.Create()
.WithTitle(GetText(strs.animal_race))
.WithDescription(text)
.WithOkColor()
.Build())
.ConfigureAwait(false);
}
.ConfigureAwait(false);
}
private Task Ar_OnStartingFailed(AnimalRace race)
private Task Ar_OnStartingFailed(AnimalRace race)
{
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
return ReplyErrorLocalizedAsync(strs.animal_race_failed);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task JoinRace(ShmartNumber amount = default)
{
if (!await CheckBetOptional(amount).ConfigureAwait(false))
return;
if (!_service.AnimalRaces.TryGetValue(ctx.Guild.Id, out var ar))
{
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
return ReplyErrorLocalizedAsync(strs.animal_race_failed);
await ReplyErrorLocalizedAsync(strs.race_not_exist).ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task JoinRace(ShmartNumber amount = default)
try
{
if (!await CheckBetOptional(amount).ConfigureAwait(false))
return;
if (!_service.AnimalRaces.TryGetValue(ctx.Guild.Id, out var ar))
{
await ReplyErrorLocalizedAsync(strs.race_not_exist).ConfigureAwait(false);
return;
}
try
{
var user = await ar.JoinRace(ctx.User.Id, ctx.User.ToString(), amount)
.ConfigureAwait(false);
if (amount > 0)
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)));
}
catch (ArgumentOutOfRangeException)
{
//ignore if user inputed an invalid amount
}
catch (AlreadyJoinedException)
{
// just ignore this
}
catch (AlreadyStartedException)
{
//ignore
}
catch (AnimalRaceFullException)
{
await SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full))
.ConfigureAwait(false);
}
catch (NotEnoughFundsException)
{
await SendErrorAsync(GetText(strs.not_enough(CurrencySign)));
}
var user = await ar.JoinRace(ctx.User.Id, ctx.User.ToString(), amount)
.ConfigureAwait(false);
if (amount > 0)
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)));
}
catch (ArgumentOutOfRangeException)
{
//ignore if user inputed an invalid amount
}
catch (AlreadyJoinedException)
{
// just ignore this
}
catch (AlreadyStartedException)
{
//ignore
}
catch (AnimalRaceFullException)
{
await SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full))
.ConfigureAwait(false);
}
catch (NotEnoughFundsException)
{
await SendErrorAsync(GetText(strs.not_enough(CurrencySign)));
}
}
}

View File

@@ -6,188 +6,185 @@ using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Common.Blackjack;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Extensions;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Services;
using Serilog;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
public class BlackJackCommands : GamblingSubmodule<BlackJackService>
{
public class BlackJackCommands : GamblingSubmodule<BlackJackService>
private readonly ICurrencyService _cs;
private readonly DbService _db;
private IUserMessage _msg;
public enum BjAction
{
private readonly ICurrencyService _cs;
private readonly DbService _db;
private IUserMessage _msg;
Hit = int.MinValue,
Stand,
Double,
}
public enum BjAction
public BlackJackCommands(ICurrencyService cs, DbService db,
GamblingConfigService gamblingConf) : base(gamblingConf)
{
_cs = cs;
_db = db;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task BlackJack(ShmartNumber amount)
{
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
return;
var newBj = new Blackjack(_cs, _db);
Blackjack bj;
if (newBj == (bj = _service.Games.GetOrAdd(ctx.Channel.Id, newBj)))
{
Hit = int.MinValue,
Stand,
Double,
}
public BlackJackCommands(ICurrencyService cs, DbService db,
GamblingConfigService gamblingConf) : base(gamblingConf)
{
_cs = cs;
_db = db;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task BlackJack(ShmartNumber amount)
{
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
return;
var newBj = new Blackjack(_cs, _db);
Blackjack bj;
if (newBj == (bj = _service.Games.GetOrAdd(ctx.Channel.Id, newBj)))
if (!await bj.Join(ctx.User, amount).ConfigureAwait(false))
{
if (!await bj.Join(ctx.User, amount).ConfigureAwait(false))
{
_service.Games.TryRemove(ctx.Channel.Id, out _);
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
bj.StateUpdated += Bj_StateUpdated;
bj.GameEnded += Bj_GameEnded;
bj.Start();
await ReplyConfirmLocalizedAsync(strs.bj_created).ConfigureAwait(false);
_service.Games.TryRemove(ctx.Channel.Id, out _);
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
bj.StateUpdated += Bj_StateUpdated;
bj.GameEnded += Bj_GameEnded;
bj.Start();
await ReplyConfirmLocalizedAsync(strs.bj_created).ConfigureAwait(false);
}
else
{
if (await bj.Join(ctx.User, amount).ConfigureAwait(false))
await ReplyConfirmLocalizedAsync(strs.bj_joined).ConfigureAwait(false);
else
{
if (await bj.Join(ctx.User, amount).ConfigureAwait(false))
await ReplyConfirmLocalizedAsync(strs.bj_joined).ConfigureAwait(false);
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.ToString() + " state already.");
}
}
await ctx.Message.DeleteAsync().ConfigureAwait(false);
}
private Task Bj_GameEnded(Blackjack arg)
{
_service.Games.TryRemove(ctx.Channel.Id, out _);
return Task.CompletedTask;
}
private async Task Bj_StateUpdated(Blackjack bj)
{
try
{
if (_msg != null)
{
var _ = _msg.DeleteAsync();
}
await ctx.Message.DeleteAsync().ConfigureAwait(false);
}
private Task Bj_GameEnded(Blackjack arg)
{
_service.Games.TryRemove(ctx.Channel.Id, out _);
return Task.CompletedTask;
}
private async Task Bj_StateUpdated(Blackjack bj)
{
try
var c = bj.Dealer.Cards.Select(x => x.GetEmojiString());
var dealerIcon = "❔ ";
if (bj.State == Blackjack.GameState.Ended)
{
if (_msg != null)
{
var _ = _msg.DeleteAsync();
}
if (bj.Dealer.GetHandValue() == 21)
dealerIcon = "💰 ";
else if (bj.Dealer.GetHandValue() > 21)
dealerIcon = "💥 ";
else
dealerIcon = "🏁 ";
}
var c = bj.Dealer.Cards.Select(x => x.GetEmojiString());
var dealerIcon = " ";
var cStr = string.Concat(c.Select(x => x.Substring(0, x.Length - 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);
if (bj.CurrentUser != null)
{
embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser.ToString()}");
}
foreach (var p in bj.Players)
{
c = p.Cards.Select(x => x.GetEmojiString());
cStr = "-\t" + string.Concat(c.Select(x => x.Substring(0, x.Length - 1) + " "));
cStr += "\n-\t" + string.Concat(c.Select(x => x.Last() + " "));
var full = $"{p.DiscordUser.ToString().TrimTo(20)} | Bet: {p.Bet} | Value: {p.GetHandValue()}";
if (bj.State == Blackjack.GameState.Ended)
{
if (bj.Dealer.GetHandValue() == 21)
dealerIcon = "💰 ";
else if (bj.Dealer.GetHandValue() > 21)
dealerIcon = "💥 ";
else
dealerIcon = "🏁 ";
}
var cStr = string.Concat(c.Select(x => x.Substring(0, x.Length - 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);
if (bj.CurrentUser != null)
{
embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser.ToString()}");
}
foreach (var p in bj.Players)
{
c = p.Cards.Select(x => x.GetEmojiString());
cStr = "-\t" + string.Concat(c.Select(x => x.Substring(0, x.Length - 1) + " "));
cStr += "\n-\t" + string.Concat(c.Select(x => x.Last() + " "));
var full = $"{p.DiscordUser.ToString().TrimTo(20)} | Bet: {p.Bet} | Value: {p.GetHandValue()}";
if (bj.State == Blackjack.GameState.Ended)
if (p.State == User.UserState.Lost)
{
if (p.State == User.UserState.Lost)
{
full = "❌ " + full;
}
else
{
full = "✅ " + full;
}
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).ConfigureAwait(false);
}
catch
{
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).ConfigureAwait(false);
}
private string UserToString(User x)
catch
{
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() + ""))}";
return $"{playerName} | Bet: {x.Bet}\n";
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public Task Hit() => InternalBlackJack(BjAction.Hit);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public Task Stand() => InternalBlackJack(BjAction.Stand);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public Task Double() => InternalBlackJack(BjAction.Double);
public async Task InternalBlackJack(BjAction a)
{
if (!_service.Games.TryGetValue(ctx.Channel.Id, out var bj))
return;
if (a == BjAction.Hit)
await bj.Hit(ctx.User).ConfigureAwait(false);
else if (a == BjAction.Stand)
await bj.Stand(ctx.User).ConfigureAwait(false);
else if (a == BjAction.Double)
{
if (!await bj.Double(ctx.User).ConfigureAwait(false))
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
}
await ctx.Message.DeleteAsync().ConfigureAwait(false);
}
}
private string UserToString(User x)
{
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() + ""))}";
return $"{playerName} | Bet: {x.Bet}\n";
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public Task Hit() => InternalBlackJack(BjAction.Hit);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public Task Stand() => InternalBlackJack(BjAction.Stand);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public Task Double() => InternalBlackJack(BjAction.Double);
public async Task InternalBlackJack(BjAction a)
{
if (!_service.Games.TryGetValue(ctx.Channel.Id, out var bj))
return;
if (a == BjAction.Hit)
await bj.Hit(ctx.User).ConfigureAwait(false);
else if (a == BjAction.Stand)
await bj.Stand(ctx.User).ConfigureAwait(false);
else if (a == BjAction.Double)
{
if (!await bj.Double(ctx.User).ConfigureAwait(false))
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
}
await ctx.Message.DeleteAsync().ConfigureAwait(false);
}
}
}
}

View File

@@ -1,163 +1,156 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using NadekoBot.Common;
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
using NadekoBot.Modules.Games.Common;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
public sealed class AnimalRace : IDisposable
{
public sealed class AnimalRace : IDisposable
public enum Phase
{
public enum Phase
WaitingForPlayers,
Running,
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 List<AnimalRacingUser> FinishedUsers { get; } = new List<AnimalRacingUser>();
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
private readonly HashSet<AnimalRacingUser> _users = new HashSet<AnimalRacingUser>();
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 Queue<RaceAnimal>(availableAnimals);
this.MaxUsers = _animalsQueue.Count;
if (this._animalsQueue.Count == 0)
CurrentPhase = Phase.Ended;
}
public void Initialize() //lame name
{
var _t = Task.Run(async () =>
{
WaitingForPlayers,
Running,
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 List<AnimalRacingUser> FinishedUsers { get; } = new List<AnimalRacingUser>();
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
private readonly HashSet<AnimalRacingUser> _users = new HashSet<AnimalRacingUser>();
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 Queue<RaceAnimal>(availableAnimals);
this.MaxUsers = _animalsQueue.Count;
if (this._animalsQueue.Count == 0)
CurrentPhase = Phase.Ended;
}
public void Initialize() //lame name
{
var _t = Task.Run(async () =>
{
await Task.Delay(_options.StartTime * 1000).ConfigureAwait(false);
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (CurrentPhase != Phase.WaitingForPlayers)
return;
await Start().ConfigureAwait(false);
}
finally { _locker.Release(); }
});
}
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
{
if (bet < 0)
throw new ArgumentOutOfRangeException(nameof(bet));
var user = new AnimalRacingUser(userName, userId, bet);
await Task.Delay(_options.StartTime * 1000).ConfigureAwait(false);
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (_users.Count == MaxUsers)
throw new AnimalRaceFullException();
if (CurrentPhase != Phase.WaitingForPlayers)
throw new AlreadyStartedException();
return;
if (!await _currency.RemoveAsync(userId, "BetRace", bet).ConfigureAwait(false))
throw new NotEnoughFundsException();
if (_users.Contains(user))
throw new AlreadyJoinedException();
var animal = _animalsQueue.Dequeue();
user.Animal = animal;
_users.Add(user);
if (_animalsQueue.Count == 0) //start if no more spots left
await Start().ConfigureAwait(false);
return user;
await Start().ConfigureAwait(false);
}
finally { _locker.Release(); }
});
}
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
{
if (bet < 0)
throw new ArgumentOutOfRangeException(nameof(bet));
var user = new AnimalRacingUser(userName, userId, bet);
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (_users.Count == MaxUsers)
throw new AnimalRaceFullException();
if (CurrentPhase != Phase.WaitingForPlayers)
throw new AlreadyStartedException();
if (!await _currency.RemoveAsync(userId, "BetRace", bet).ConfigureAwait(false))
throw new NotEnoughFundsException();
if (_users.Contains(user))
throw new AlreadyJoinedException();
var animal = _animalsQueue.Dequeue();
user.Animal = animal;
_users.Add(user);
if (_animalsQueue.Count == 0) //start if no more spots left
await Start().ConfigureAwait(false);
return user;
}
finally { _locker.Release(); }
}
private async Task Start()
{
CurrentPhase = Phase.Running;
if (_users.Count <= 1)
{
foreach (var user in _users)
{
if (user.Bet > 0)
await _currency.AddAsync(user.UserId, "Race refund", user.Bet).ConfigureAwait(false);
}
var _sf = OnStartingFailed?.Invoke(this);
CurrentPhase = Phase.Ended;
return;
}
private async Task Start()
var _ = OnStarted?.Invoke(this);
var _t = Task.Run(async () =>
{
CurrentPhase = Phase.Running;
if (_users.Count <= 1)
var rng = new NadekoRandom();
while (!_users.All(x => x.Progress >= 60))
{
foreach (var user in _users)
{
if (user.Bet > 0)
await _currency.AddAsync(user.UserId, "Race refund", user.Bet).ConfigureAwait(false);
user.Progress += rng.Next(1, 11);
if (user.Progress >= 60)
user.Progress = 60;
}
var _sf = OnStartingFailed?.Invoke(this);
CurrentPhase = Phase.Ended;
return;
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x))
.Shuffle();
FinishedUsers.AddRange(finished);
var _ignore = OnStateUpdate?.Invoke(this);
await Task.Delay(2500).ConfigureAwait(false);
}
var _ = OnStarted?.Invoke(this);
var _t = Task.Run(async () =>
{
var rng = new NadekoRandom();
while (!_users.All(x => x.Progress >= 60))
{
foreach (var user in _users)
{
user.Progress += rng.Next(1, 11);
if (user.Progress >= 60)
user.Progress = 60;
}
if (FinishedUsers[0].Bet > 0)
await _currency.AddAsync(FinishedUsers[0].UserId, "Won a Race", FinishedUsers[0].Bet * (_users.Count - 1))
.ConfigureAwait(false);
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x))
.Shuffle();
var _ended = OnEnded?.Invoke(this);
});
}
FinishedUsers.AddRange(finished);
var _ignore = OnStateUpdate?.Invoke(this);
await Task.Delay(2500).ConfigureAwait(false);
}
if (FinishedUsers[0].Bet > 0)
await _currency.AddAsync(FinishedUsers[0].UserId, "Won a Race", FinishedUsers[0].Bet * (_users.Count - 1))
.ConfigureAwait(false);
var _ended = OnEnded?.Invoke(this);
});
}
public void Dispose()
{
CurrentPhase = Phase.Ended;
OnStarted = null;
OnEnded = null;
OnStartingFailed = null;
OnStateUpdate = null;
_locker.Dispose();
_users.Clear();
}
public void Dispose()
{
CurrentPhase = Phase.Ended;
OnStarted = null;
OnEnded = null;
OnStartingFailed = null;
OnStateUpdate = null;
_locker.Dispose();
_users.Clear();
}
}

View File

@@ -1,32 +1,31 @@
using NadekoBot.Modules.Games.Common;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
public class AnimalRacingUser
{
public class AnimalRacingUser
public long Bet { get; }
public string Username { get; }
public ulong UserId { get; }
public RaceAnimal Animal { get; set; }
public int Progress { get; set; }
public AnimalRacingUser(string username, ulong userId, long bet)
{
public long Bet { get; }
public string Username { get; }
public ulong UserId { get; }
public RaceAnimal Animal { get; set; }
public int Progress { get; set; }
public AnimalRacingUser(string username, ulong userId, long bet)
{
this.Bet = bet;
this.Username = username;
this.UserId = userId;
}
public override bool Equals(object obj)
{
return obj is AnimalRacingUser x
? x.UserId == this.UserId
: false;
}
public override int GetHashCode()
{
return this.UserId.GetHashCode();
}
this.Bet = bet;
this.Username = username;
this.UserId = userId;
}
}
public override bool Equals(object obj)
{
return obj is AnimalRacingUser x
? x.UserId == this.UserId
: false;
}
public override int GetHashCode()
{
return this.UserId.GetHashCode();
}
}

View File

@@ -1,20 +1,17 @@
using System;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
public class AlreadyJoinedException : Exception
{
public class AlreadyJoinedException : Exception
public AlreadyJoinedException()
{
public AlreadyJoinedException()
{
}
public AlreadyJoinedException(string message) : base(message)
{
}
public AlreadyJoinedException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
public AlreadyJoinedException(string message) : base(message)
{
}
public AlreadyJoinedException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@@ -1,19 +1,16 @@
using System;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
public class AlreadyStartedException : Exception
{
public class AlreadyStartedException : Exception
public AlreadyStartedException()
{
public AlreadyStartedException()
{
}
public AlreadyStartedException(string message) : base(message)
{
}
public AlreadyStartedException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
public AlreadyStartedException(string message) : base(message)
{
}
public AlreadyStartedException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@@ -1,19 +1,16 @@
using System;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
public class AnimalRaceFullException : Exception
{
public class AnimalRaceFullException : Exception
public AnimalRaceFullException()
{
public AnimalRaceFullException()
{
}
public AnimalRaceFullException(string message) : base(message)
{
}
public AnimalRaceFullException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
public AnimalRaceFullException(string message) : base(message)
{
}
public AnimalRaceFullException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@@ -1,19 +1,16 @@
using System;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions
public class NotEnoughFundsException : Exception
{
public class NotEnoughFundsException : Exception
public NotEnoughFundsException()
{
public NotEnoughFundsException()
{
}
public NotEnoughFundsException(string message) : base(message)
{
}
public NotEnoughFundsException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
public NotEnoughFundsException(string message) : base(message)
{
}
public NotEnoughFundsException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@@ -1,17 +1,16 @@
using CommandLine;
using NadekoBot.Common;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing
{
public class RaceOptions : INadekoCommandOptions
{
[Option('s', "start-time", Default = 20, Required = false)]
public int StartTime { get; set; } = 20;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
public void NormalizeOptions()
{
if (this.StartTime < 10 || this.StartTime > 120)
this.StartTime = 20;
}
public class RaceOptions : INadekoCommandOptions
{
[Option('s', "start-time", Default = 20, Required = false)]
public int StartTime { get; set; } = 20;
public void NormalizeOptions()
{
if (this.StartTime < 10 || this.StartTime > 120)
this.StartTime = 20;
}
}

View File

@@ -1,47 +1,43 @@
using System;
using System.Linq;
namespace NadekoBot.Modules.Gambling.Common;
namespace NadekoBot.Modules.Gambling.Common
public class Betroll
{
public class Betroll
public class Result
{
public class Result
{
public int Roll { get; set; }
public float Multiplier { get; set; }
public int Threshold { get; set; }
}
public int Roll { get; set; }
public float Multiplier { get; set; }
public int Threshold { get; set; }
}
private readonly IOrderedEnumerable<BetRollPair> _thresholdPairs;
private readonly Random _rng;
private readonly IOrderedEnumerable<BetRollPair> _thresholdPairs;
private readonly Random _rng;
public Betroll(BetRollConfig settings)
public Betroll(BetRollConfig settings)
{
_thresholdPairs = settings.Pairs.OrderByDescending(x => x.WhenAbove);
_rng = new Random();
}
public Result Roll()
{
var roll = _rng.Next(0, 101);
var pair = _thresholdPairs.FirstOrDefault(x => x.WhenAbove < roll);
if (pair is null)
{
_thresholdPairs = settings.Pairs.OrderByDescending(x => x.WhenAbove);
_rng = new Random();
}
public Result Roll()
{
var roll = _rng.Next(0, 101);
var pair = _thresholdPairs.FirstOrDefault(x => x.WhenAbove < roll);
if (pair is null)
{
return new Result
{
Multiplier = 0,
Roll = roll,
};
}
return new Result
{
Multiplier = pair.MultiplyBy,
Multiplier = 0,
Roll = roll,
Threshold = pair.WhenAbove,
};
}
return new Result
{
Multiplier = pair.MultiplyBy,
Roll = roll,
Threshold = pair.WhenAbove,
};
}
}

View File

@@ -1,353 +1,347 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using Discord;
using NadekoBot.Services;
using NadekoBot.Modules.Gambling.Common;
using Serilog;
namespace NadekoBot.Modules.Gambling.Common.Blackjack
namespace NadekoBot.Modules.Gambling.Common.Blackjack;
public class Blackjack
{
public class Blackjack
public enum GameState
{
public enum GameState
{
Starting,
Playing,
Ended
}
Starting,
Playing,
Ended
}
private Deck Deck { get; set; } = new QuadDeck();
public Dealer Dealer { get; set; }
private Deck Deck { get; set; } = new QuadDeck();
public Dealer Dealer { get; set; }
public List<User> Players { get; set; } = new List<User>();
public GameState State { get; set; } = GameState.Starting;
public User CurrentUser { get; private set; }
public List<User> Players { get; set; } = new List<User>();
public GameState State { get; set; } = GameState.Starting;
public User CurrentUser { get; private set; }
private TaskCompletionSource<bool> _currentUserMove;
private readonly ICurrencyService _cs;
private readonly DbService _db;
private TaskCompletionSource<bool> _currentUserMove;
private readonly ICurrencyService _cs;
private readonly DbService _db;
public event Func<Blackjack, Task> StateUpdated;
public event Func<Blackjack, Task> GameEnded;
public event Func<Blackjack, Task> StateUpdated;
public event Func<Blackjack, Task> GameEnded;
private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1);
public Blackjack(ICurrencyService cs, DbService db)
{
_cs = cs;
_db = db;
Dealer = new Dealer();
}
public void Start()
{
var _ = GameLoop();
}
public async Task GameLoop()
public Blackjack(ICurrencyService cs, DbService db)
{
_cs = cs;
_db = db;
Dealer = new Dealer();
}
public void Start()
{
var _ = GameLoop();
}
public async Task GameLoop()
{
try
{
//wait for players to join
await Task.Delay(20000).ConfigureAwait(false);
await locker.WaitAsync().ConfigureAwait(false);
try
{
//wait for players to join
await Task.Delay(20000).ConfigureAwait(false);
await locker.WaitAsync().ConfigureAwait(false);
try
{
State = GameState.Playing;
}
finally
{
locker.Release();
}
await PrintState().ConfigureAwait(false);
//if no users joined the game, end it
if (!Players.Any())
{
State = GameState.Ended;
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)
{
usr.Cards.Add(Deck.Draw());
usr.Cards.Add(Deck.Draw());
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).ConfigureAwait(false);
}
}
await PrintState().ConfigureAwait(false);
State = GameState.Ended;
await Task.Delay(2500).ConfigureAwait(false);
Log.Information("Dealer moves");
await DealerMoves().ConfigureAwait(false);
await PrintState().ConfigureAwait(false);
var _ = GameEnded?.Invoke(this);
State = GameState.Playing;
}
catch (Exception ex)
finally
{
Log.Error(ex, "REPORT THE MESSAGE BELOW IN #NadekoLog SERVER PLEASE");
State = GameState.Ended;
var _ = GameEnded?.Invoke(this);
locker.Release();
}
}
private async Task PromptUserMove(User usr)
{
var pause = Task.Delay(20000); //10 seconds to decide
CurrentUser = usr;
_currentUserMove = new TaskCompletionSource<bool>();
await PrintState().ConfigureAwait(false);
// either wait for the user to make an action and
// if he doesn't - stand
var finished = await Task.WhenAny(pause, _currentUserMove.Task).ConfigureAwait(false);
if (finished == pause)
//if no users joined the game, end it
if (!Players.Any())
{
await Stand(usr).ConfigureAwait(false);
State = GameState.Ended;
var end = GameEnded?.Invoke(this);
return;
}
CurrentUser = null;
_currentUserMove = null;
}
public async Task<bool> Join(IUser user, long bet)
{
await locker.WaitAsync().ConfigureAwait(false);
try
//give 1 card to the dealer and 2 to each player
Dealer.Cards.Add(Deck.Draw());
foreach (var usr in Players)
{
if (State != GameState.Starting)
return false;
usr.Cards.Add(Deck.Draw());
usr.Cards.Add(Deck.Draw());
if (Players.Count >= 5)
return false;
if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true).ConfigureAwait(false))
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)
{
return false;
Log.Information($"Waiting for {usr.DiscordUser}'s move");
await PromptUserMove(usr).ConfigureAwait(false);
}
Players.Add(new User(user, bet));
var _ = PrintState();
return true;
}
finally
{
locker.Release();
}
await PrintState().ConfigureAwait(false);
State = GameState.Ended;
await Task.Delay(2500).ConfigureAwait(false);
Log.Information("Dealer moves");
await DealerMoves().ConfigureAwait(false);
await PrintState().ConfigureAwait(false);
var _ = GameEnded?.Invoke(this);
}
public async Task<bool> Stand(IUser u)
catch (Exception ex)
{
var cu = CurrentUser;
Log.Error(ex, "REPORT THE MESSAGE BELOW IN #NadekoLog SERVER PLEASE");
State = GameState.Ended;
var _ = GameEnded?.Invoke(this);
}
}
if (cu != null && cu.DiscordUser == u)
return await Stand(cu).ConfigureAwait(false);
private async Task PromptUserMove(User usr)
{
var pause = Task.Delay(20000); //10 seconds to decide
CurrentUser = usr;
_currentUserMove = new TaskCompletionSource<bool>();
await PrintState().ConfigureAwait(false);
// either wait for the user to make an action and
// if he doesn't - stand
var finished = await Task.WhenAny(pause, _currentUserMove.Task).ConfigureAwait(false);
if (finished == pause)
{
await Stand(usr).ConfigureAwait(false);
}
CurrentUser = null;
_currentUserMove = null;
}
public async Task<bool> Join(IUser user, long bet)
{
await locker.WaitAsync().ConfigureAwait(false);
try
{
if (State != GameState.Starting)
return false;
if (Players.Count >= 5)
return false;
if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true).ConfigureAwait(false))
{
return false;
}
Players.Add(new User(user, bet));
var _ = PrintState();
return true;
}
finally
{
locker.Release();
}
}
public async Task<bool> Stand(IUser u)
{
var cu = CurrentUser;
if (cu != null && cu.DiscordUser == u)
return await Stand(cu).ConfigureAwait(false);
return false;
return false;
}
public async Task<bool> Stand(User u)
{
await locker.WaitAsync().ConfigureAwait(false);
try
{
if (State != GameState.Playing)
return false;
if (CurrentUser != u)
return false;
u.State = User.UserState.Stand;
_currentUserMove.TrySetResult(true);
return true;
}
finally
{
locker.Release();
}
}
private async Task DealerMoves()
{
var hw = Dealer.GetHandValue();
while (hw < 17
|| (hw == 17 && Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10))// hit on soft 17
{
/* Dealer has
A 6
That's 17, soft
hw == 17 => true
number of aces = 1
1 > 17-17 /10 => true
AA 5
That's 17, again soft, since one ace is worth 11, even though another one is 1
hw == 17 => true
number of aces = 2
2 > 27 - 17 / 10 => true
AA Q 5
That's 17, but not soft, since both aces are worth 1
hw == 17 => true
number of aces = 2
2 > 37 - 17 / 10 => false
* */
Dealer.Cards.Add(Deck.Draw());
hw = Dealer.GetHandValue();
}
public async Task<bool> Stand(User u)
if (hw > 21)
{
await locker.WaitAsync().ConfigureAwait(false);
try
foreach (var usr in Players)
{
if (State != GameState.Playing)
return false;
if (CurrentUser != u)
return false;
u.State = User.UserState.Stand;
_currentUserMove.TrySetResult(true);
return true;
if (usr.State == User.UserState.Stand || usr.State == User.UserState.Blackjack)
usr.State = User.UserState.Won;
else
usr.State = User.UserState.Lost;
}
finally
}
else
{
foreach (var usr in Players)
{
locker.Release();
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;
else
usr.State = User.UserState.Lost;
}
}
private async Task DealerMoves()
foreach (var usr in Players)
{
var hw = Dealer.GetHandValue();
while (hw < 17
|| (hw == 17 && Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10))// hit on soft 17
if (usr.State == User.UserState.Won || usr.State == User.UserState.Blackjack)
{
/* Dealer has
A 6
That's 17, soft
hw == 17 => true
number of aces = 1
1 > 17-17 /10 => true
AA 5
That's 17, again soft, since one ace is worth 11, even though another one is 1
hw == 17 => true
number of aces = 2
2 > 27 - 17 / 10 => true
AA Q 5
That's 17, but not soft, since both aces are worth 1
hw == 17 => true
number of aces = 2
2 > 37 - 17 / 10 => false
* */
Dealer.Cards.Add(Deck.Draw());
hw = Dealer.GetHandValue();
await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, gamble: true).ConfigureAwait(false);
}
}
}
if (hw > 21)
public async Task<bool> Double(IUser u)
{
var cu = CurrentUser;
if (cu != null && cu.DiscordUser == u)
return await Double(cu).ConfigureAwait(false);
return false;
}
public async Task<bool> Double(User u)
{
await locker.WaitAsync().ConfigureAwait(false);
try
{
if (State != GameState.Playing)
return false;
if (CurrentUser != u)
return false;
if (!await _cs.RemoveAsync(u.DiscordUser.Id, "Blackjack-double", u.Bet).ConfigureAwait(false))
return false;
u.Bet *= 2;
u.Cards.Add(Deck.Draw());
if (u.GetHandValue() == 21)
{
foreach (var usr in Players)
{
if (usr.State == User.UserState.Stand || usr.State == User.UserState.Blackjack)
usr.State = User.UserState.Won;
else
usr.State = User.UserState.Lost;
}
//blackjack
u.State = User.UserState.Blackjack;
}
else if (u.GetHandValue() > 21)
{
// user busted
u.State = User.UserState.Bust;
}
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;
else
usr.State = User.UserState.Lost;
}
//with double you just get one card, and then you're done
u.State = User.UserState.Stand;
}
_currentUserMove.TrySetResult(true);
foreach (var usr in Players)
{
if (usr.State == User.UserState.Won || usr.State == User.UserState.Blackjack)
{
await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, gamble: true).ConfigureAwait(false);
}
}
return true;
}
public async Task<bool> Double(IUser u)
finally
{
var cu = CurrentUser;
if (cu != null && cu.DiscordUser == u)
return await Double(cu).ConfigureAwait(false);
return false;
}
public async Task<bool> Double(User u)
{
await locker.WaitAsync().ConfigureAwait(false);
try
{
if (State != GameState.Playing)
return false;
if (CurrentUser != u)
return false;
if (!await _cs.RemoveAsync(u.DiscordUser.Id, "Blackjack-double", u.Bet).ConfigureAwait(false))
return false;
u.Bet *= 2;
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;
}
finally
{
locker.Release();
}
}
public async Task<bool> Hit(IUser u)
{
var cu = CurrentUser;
if (cu != null && cu.DiscordUser == u)
return await Hit(cu).ConfigureAwait(false);
return false;
}
public async Task<bool> Hit(User u)
{
await locker.WaitAsync().ConfigureAwait(false);
try
{
if (State != GameState.Playing)
return false;
if (CurrentUser != u)
return false;
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;
}
finally
{
locker.Release();
}
}
public Task PrintState()
{
if (StateUpdated is null)
return Task.CompletedTask;
return StateUpdated.Invoke(this);
locker.Release();
}
}
}
public async Task<bool> Hit(IUser u)
{
var cu = CurrentUser;
if (cu != null && cu.DiscordUser == u)
return await Hit(cu).ConfigureAwait(false);
return false;
}
public async Task<bool> Hit(User u)
{
await locker.WaitAsync().ConfigureAwait(false);
try
{
if (State != GameState.Playing)
return false;
if (CurrentUser != u)
return false;
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;
}
finally
{
locker.Release();
}
}
public Task PrintState()
{
if (StateUpdated is null)
return Task.CompletedTask;
return StateUpdated.Invoke(this);
}
}

View File

@@ -1,65 +1,60 @@
using Discord;
using NadekoBot.Modules.Gambling.Common;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Modules.Gambling.Common.Blackjack
namespace NadekoBot.Modules.Gambling.Common.Blackjack;
public abstract class Player
{
public abstract class Player
public List<Deck.Card> Cards { get; } = new List<Deck.Card>();
public int GetHandValue()
{
public List<Deck.Card> Cards { get; } = new List<Deck.Card>();
var val = GetRawHandValue();
public int GetHandValue()
// while the hand value is greater than 21, for each ace you have in the deck
// 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)
{
var val = GetRawHandValue();
// while the hand value is greater than 21, for each ace you have in the deck
// 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;
}
return val;
}
public int GetRawHandValue()
{
return Cards.Sum(x => x.Number == 1 ? 11 : x.Number >= 10 ? 10 : x.Number);
val -= 10;
}
return val;
}
public class Dealer : Player
public int GetRawHandValue()
{
}
public class User : Player
{
public enum UserState
{
Waiting,
Stand,
Bust,
Blackjack,
Won,
Lost
}
public User(IUser user, long bet)
{
if (bet <= 0)
throw new ArgumentOutOfRangeException(nameof(bet));
this.Bet = bet;
this.DiscordUser = user;
}
public UserState State { get; set; } = UserState.Waiting;
public long Bet { get; set; }
public IUser DiscordUser { get; }
public bool Done => State != UserState.Waiting;
return Cards.Sum(x => x.Number == 1 ? 11 : x.Number >= 10 ? 10 : x.Number);
}
}
public class Dealer : Player
{
}
public class User : Player
{
public enum UserState
{
Waiting,
Stand,
Bust,
Blackjack,
Won,
Lost
}
public User(IUser user, long bet)
{
if (bet <= 0)
throw new ArgumentOutOfRangeException(nameof(bet));
this.Bet = bet;
this.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

@@ -1,81 +1,78 @@
using Discord;
using NadekoBot.Common;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Modules.Gambling.Common
namespace NadekoBot.Modules.Gambling.Common;
public class CurrencyRaffleGame
{
public class CurrencyRaffleGame
public enum Type {
Mixed,
Normal
}
public class User
{
public enum Type {
Mixed,
Normal
public IUser DiscordUser { get; set; }
public long Amount { get; set; }
public override int GetHashCode()
{
return DiscordUser.GetHashCode();
}
public class User
public override bool Equals(object obj)
{
public IUser DiscordUser { get; set; }
public long Amount { get; set; }
public override int GetHashCode()
{
return DiscordUser.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is User u
? u.DiscordUser == DiscordUser
: false;
}
return obj is User u
? u.DiscordUser == DiscordUser
: false;
}
}
private readonly HashSet<User> _users = new HashSet<User>();
public IEnumerable<User> Users => _users;
public Type GameType { get; }
private readonly HashSet<User> _users = new HashSet<User>();
public IEnumerable<User> Users => _users;
public Type GameType { get; }
public CurrencyRaffleGame(Type type)
{
GameType = type;
}
public CurrencyRaffleGame(Type type)
{
GameType = type;
}
public bool AddUser(IUser usr, long amount)
{
// 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)
return false;
public bool AddUser(IUser usr, long amount)
{
// 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)
return false;
if (!_users.Add(new User
if (!_users.Add(new User
{
DiscordUser = usr,
Amount = amount,
}))
{
return false;
}
return true;
}
public User GetWinner()
{
var rng = new NadekoRandom();
if (GameType == Type.Mixed)
{
var num = rng.NextLong(0L, Users.Sum(x => x.Amount));
var sum = 0L;
foreach (var u in Users)
{
sum += u.Amount;
if (sum > num)
return u;
}
}
var usrs = _users.ToArray();
return usrs[rng.Next(0, usrs.Length)];
return false;
}
return true;
}
}
public User GetWinner()
{
var rng = new NadekoRandom();
if (GameType == Type.Mixed)
{
var num = rng.NextLong(0L, Users.Sum(x => x.Amount));
var sum = 0L;
foreach (var u in Users)
{
sum += u.Amount;
if (sum > num)
return u;
}
}
var usrs = _users.ToArray();
return usrs[rng.Next(0, usrs.Length)];
}
}

View File

@@ -1,312 +1,308 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NadekoBot.Common;
using NadekoBot.Common;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Gambling.Common
namespace NadekoBot.Modules.Gambling.Common;
public class QuadDeck : Deck
{
public class QuadDeck : Deck
protected override void RefillPool()
{
protected override void RefillPool()
CardPool = new List<Card>(52 * 4);
for (var j = 1; j < 14; j++)
{
CardPool = new List<Card>(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 Card((CardSuit)i, j));
CardPool.Add(new Card((CardSuit)i, j));
CardPool.Add(new Card((CardSuit)i, j));
CardPool.Add(new Card((CardSuit)i, j));
}
CardPool.Add(new Card((CardSuit)i, j));
CardPool.Add(new Card((CardSuit)i, j));
CardPool.Add(new Card((CardSuit)i, j));
CardPool.Add(new Card((CardSuit)i, j));
}
}
}
}
public class Deck
public class Deck
{
private static readonly Dictionary<int, string> cardNames = new Dictionary<int, string>() {
{ 1, "Ace" },
{ 2, "Two" },
{ 3, "Three" },
{ 4, "Four" },
{ 5, "Five" },
{ 6, "Six" },
{ 7, "Seven" },
{ 8, "Eight" },
{ 9, "Nine" },
{ 10, "Ten" },
{ 11, "Jack" },
{ 12, "Queen" },
{ 13, "King" }
};
private static Dictionary<string, Func<List<Card>, bool>> handValues;
public enum CardSuit
{
private static readonly Dictionary<int, string> cardNames = new Dictionary<int, string>() {
{ 1, "Ace" },
{ 2, "Two" },
{ 3, "Three" },
{ 4, "Four" },
{ 5, "Five" },
{ 6, "Six" },
{ 7, "Seven" },
{ 8, "Eight" },
{ 9, "Nine" },
{ 10, "Ten" },
{ 11, "Jack" },
{ 12, "Queen" },
{ 13, "King" }
};
private static Dictionary<string, Func<List<Card>, bool>> handValues;
Spades = 1,
Hearts = 2,
Diamonds = 3,
Clubs = 4
}
public class Card : IComparable
{
public CardSuit Suit { get; }
public int Number { get; }
public enum CardSuit
public string FullName
{
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 = "";
if (Number <= 10 && Number > 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 Card)) return 0;
var c = (Card)obj;
return this.Number - c.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 Card(s, n);
}
public string GetEmojiString()
get
{
var str = "";
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 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 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();
}
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.
/// </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 List<Card>(52);
//foreach suit
for (var j = 1; j < 14; j++)
{
// and number
for (var i = 1; i < 5; i++)
if (Number <= 10 && Number > 1)
{
//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 Card((CardSuit)i, j));
str += "_" + Number;
}
else
{
str += GetValueText().ToLowerInvariant();
}
return str + "_of_" + Suit.ToString().ToLowerInvariant();
}
}
private 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.
/// </summary>
/// <returns>A card from the pool</returns>
public Card Draw()
public Card(CardSuit s, int cardNum)
{
if (CardPool.Count == 0)
Restart();
//you can either do this if your deck is not shuffled
var num = r.Next(0, CardPool.Count);
var c = CardPool[num];
CardPool.RemoveAt(num);
return c;
// if you want to shuffle when you fill, then take the first one
/*
Card c = cardPool[0];
cardPool.RemoveAt(0);
return c;
*/
this.Suit = s;
this.Number = cardNum;
}
/// <summary>
/// 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()
public string GetValueText() => cardNames[Number];
public override string ToString() => cardNames[Number] + " Of " + Suit;
public int CompareTo(object obj)
{
if (CardPool.Count <= 1) return;
var orderedPool = CardPool.Shuffle();
CardPool = CardPool as List<Card> ?? orderedPool.ToList();
if (!(obj is Card)) return 0;
var c = (Card)obj;
return this.Number - c.Number;
}
public override string ToString() => string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine;
private static void InitHandValues()
public static Card Parse(string input)
{
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);
if (string.IsNullOrWhiteSpace(input))
throw new ArgumentNullException(nameof(input));
bool isTwoPair(List<Card> cards) => cards.GroupBy(card => card.Number)
.Count(group => group.Count() == 2) == 2;
bool isStraight(List<Card> cards)
if (input.Length != 2
|| !_numberCharToNumber.TryGetValue(input[0], out var n)
|| !_suitCharToSuit.TryGetValue(input[1].ToString(), out var s))
{
if (cards.GroupBy(card => card.Number).Count() != cards.Count())
return false;
var toReturn = (cards.Max(card => (int)card.Number)
- cards.Min(card => (int)card.Number) == 4);
if (toReturn || cards.All(c => c.Number != 1)) return toReturn;
var newCards = cards.Select(c => c.Number == 1 ? new Card(c.Suit, 14) : c);
return (newCards.Max(card => (int)card.Number)
- newCards.Min(card => (int)card.Number) == 4);
throw new ArgumentException("Invalid input", nameof(input));
}
bool hasThreeOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
.Any(group => group.Count() == 3);
bool isThreeOfKind(List<Card> cards) => hasThreeOfKind(cards) && !hasPair(cards);
bool isFlush(List<Card> cards) => cards.GroupBy(card => card.Suit).Count() == 1;
bool isFourOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
.Any(group => group.Count() == 4);
bool isFullHouse(List<Card> cards) => hasPair(cards) && hasThreeOfKind(cards);
bool hasStraightFlush(List<Card> cards) => isFlush(cards) && isStraight(cards);
bool isRoyalFlush(List<Card> cards) => cards.Min(card => card.Number) == 1 &&
cards.Max(card => card.Number) == 13
&& hasStraightFlush(cards);
bool isStraightFlush(List<Card> cards) => hasStraightFlush(cards) && !isRoyalFlush(cards);
handValues = new Dictionary<string, Func<List<Card>, bool>>
{
{ "Royal Flush", isRoyalFlush },
{ "Straight Flush", isStraightFlush },
{ "Four Of A Kind", isFourOfKind },
{ "Full House", isFullHouse },
{ "Flush", isFlush },
{ "Straight", isStraight },
{ "Three Of A Kind", isThreeOfKind },
{ "Two Pairs", isTwoPair },
{ "A Pair", isPair }
};
return new Card(s, n);
}
public static string GetHandValue(List<Card> cards)
public string GetEmojiString()
{
if (handValues is null)
InitHandValues();
foreach (var kvp in handValues.Where(x => x.Value(cards)))
var str = "";
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 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 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();
}
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.
/// </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 List<Card>(52);
//foreach suit
for (var j = 1; j < 14; j++)
{
// and number
for (var i = 1; i < 5; i++)
{
return kvp.Key;
//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 Card((CardSuit)i, j));
}
return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetValueText() ?? cards.Max().GetValueText());
}
}
private 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.
/// </summary>
/// <returns>A card from the pool</returns>
public Card Draw()
{
if (CardPool.Count == 0)
Restart();
//you can either do this if your deck is not shuffled
var num = r.Next(0, CardPool.Count);
var c = CardPool[num];
CardPool.RemoveAt(num);
return c;
// if you want to shuffle when you fill, then take the first one
/*
Card c = cardPool[0];
cardPool.RemoveAt(0);
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.
/// </summary>
private void Shuffle()
{
if (CardPool.Count <= 1) return;
var orderedPool = CardPool.Shuffle();
CardPool = CardPool as List<Card> ?? orderedPool.ToList();
}
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 isTwoPair(List<Card> cards) => 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 => (int)card.Number)
- cards.Min(card => (int)card.Number) == 4);
if (toReturn || cards.All(c => c.Number != 1)) return toReturn;
var newCards = cards.Select(c => c.Number == 1 ? new Card(c.Suit, 14) : c);
return (newCards.Max(card => (int)card.Number)
- newCards.Min(card => (int)card.Number) == 4);
}
bool hasThreeOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
.Any(group => group.Count() == 3);
bool isThreeOfKind(List<Card> cards) => hasThreeOfKind(cards) && !hasPair(cards);
bool isFlush(List<Card> cards) => cards.GroupBy(card => card.Suit).Count() == 1;
bool isFourOfKind(List<Card> cards) => cards.GroupBy(card => card.Number)
.Any(group => group.Count() == 4);
bool isFullHouse(List<Card> cards) => hasPair(cards) && hasThreeOfKind(cards);
bool hasStraightFlush(List<Card> cards) => isFlush(cards) && isStraight(cards);
bool isRoyalFlush(List<Card> cards) => cards.Min(card => card.Number) == 1 &&
cards.Max(card => card.Number) == 13
&& hasStraightFlush(cards);
bool isStraightFlush(List<Card> cards) => hasStraightFlush(cards) && !isRoyalFlush(cards);
handValues = new Dictionary<string, Func<List<Card>, bool>>
{
{ "Royal Flush", isRoyalFlush },
{ "Straight Flush", isStraightFlush },
{ "Four Of A Kind", isFourOfKind },
{ "Full House", isFullHouse },
{ "Flush", isFlush },
{ "Straight", isStraight },
{ "Three Of A Kind", isThreeOfKind },
{ "Two Pairs", isTwoPair },
{ "A Pair", isPair }
};
}
public static string GetHandValue(List<Card> cards)
{
if (handValues is null)
InitHandValues();
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());
}
}

View File

@@ -1,30 +1,29 @@
using CommandLine;
using NadekoBot.Common;
namespace NadekoBot.Modules.Gambling.Common.Events
namespace NadekoBot.Modules.Gambling.Common.Events;
public class EventOptions : INadekoCommandOptions
{
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('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.")]
public int Hours { get; set; } = 24;
public void NormalizeOptions()
{
[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('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.")]
public int Hours { get; set; } = 24;
public void NormalizeOptions()
{
if (Amount < 0)
Amount = 100;
if (PotSize < 0)
PotSize = 0;
if (Hours <= 0)
Hours = 24;
if (PotSize != 0 && PotSize < Amount)
PotSize = 0;
}
if (Amount < 0)
Amount = 100;
if (PotSize < 0)
PotSize = 0;
if (Hours <= 0)
Hours = 24;
if (PotSize != 0 && PotSize < Amount)
PotSize = 0;
}
}
}

View File

@@ -4,207 +4,201 @@ using NadekoBot.Common.Collections;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Common;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
namespace NadekoBot.Modules.Gambling.Common.Events
namespace NadekoBot.Modules.Gambling.Common.Events;
public class GameStatusEvent : ICurrencyEvent
{
public class GameStatusEvent : ICurrencyEvent
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 ConcurrentHashSet<ulong>();
private readonly ConcurrentQueue<ulong> _toAward = new ConcurrentQueue<ulong>();
private readonly Timer _t;
private readonly Timer _timeout = null;
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();
public GameStatusEvent(DiscordSocketClient client, ICurrencyService cs,SocketGuild g, ITextChannel ch,
EventOptions opt, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
{
private readonly DiscordSocketClient _client;
private readonly IGuild _guild;
private IUserMessage _msg;
private readonly ICurrencyService _cs;
private readonly long _amount;
_client = client;
_guild = g;
_cs = cs;
_amount = opt.Amount;
PotSize = opt.PotSize;
_embedFunc = embedFunc;
_isPotLimited = PotSize > 0;
_channel = ch;
_opts = opt;
// generate code
_code = new string(_sneakyGameStatusChars.Shuffle().Take(5).ToArray());
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 ConcurrentHashSet<ulong>();
private readonly ConcurrentQueue<ulong> _toAward = new ConcurrentQueue<ulong>();
private readonly Timer _t;
private readonly Timer _timeout = null;
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();
public GameStatusEvent(DiscordSocketClient client, ICurrencyService cs,SocketGuild g, ITextChannel ch,
EventOptions opt, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
_t = new Timer(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
if (_opts.Hours > 0)
{
_client = client;
_guild = g;
_cs = cs;
_amount = opt.Amount;
PotSize = opt.PotSize;
_embedFunc = embedFunc;
_isPotLimited = PotSize > 0;
_channel = ch;
_opts = opt;
// generate code
_code = new string(_sneakyGameStatusChars.Shuffle().Take(5).ToArray());
_timeout = new Timer(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
}
}
_t = new Timer(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
if (_opts.Hours > 0)
{
_timeout = new Timer(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
}
private void EventTimeout(object state)
{
var _ = StopEvent();
}
private async void OnTimerTick(object state)
{
var potEmpty = PotEmptied;
List<ulong> toAward = new List<ulong>();
while (_toAward.TryDequeue(out var x))
{
toAward.Add(x);
}
private void EventTimeout(object state)
{
var _ = StopEvent();
}
if (!toAward.Any())
return;
private async void OnTimerTick(object state)
try
{
var potEmpty = PotEmptied;
List<ulong> toAward = new List<ulong>();
while (_toAward.TryDequeue(out var x))
await _cs.AddBulkAsync(toAward,
toAward.Select(x => "GameStatus Event"),
toAward.Select(x => _amount),
gamble: true).ConfigureAwait(false);
if (_isPotLimited)
{
toAward.Add(x);
await _msg.ModifyAsync(m =>
{
m.Embed = GetEmbed(PotSize).Build();
}, new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
}
if (!toAward.Any())
Log.Information("Awarded {0} users {1} currency.{2}",
toAward.Count,
_amount,
_isPotLimited ? $" {PotSize} left." : "");
if (potEmpty)
{
var _ = StopEvent();
}
}
catch (Exception ex)
{
Log.Warning(ex, "Error in OnTimerTick in gamestatusevent");
}
}
public async Task StartEvent()
{
_msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize)).ConfigureAwait(false);
await _client.SetGameAsync(_code).ConfigureAwait(false);
_client.MessageDeleted += OnMessageDeleted;
_client.MessageReceived += HandleMessage;
_t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
}
private IEmbedBuilder GetEmbed(long pot)
{
return _embedFunc(CurrencyEvent.Type.GameStatus, _opts, pot);
}
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, ISocketMessageChannel _)
{
if (msg.Id == _msg.Id)
{
await StopEvent().ConfigureAwait(false);
}
}
private readonly object stopLock = new object();
public async Task StopEvent()
{
await Task.Yield();
lock (stopLock)
{
if (Stopped)
return;
Stopped = true;
_client.MessageDeleted -= OnMessageDeleted;
_client.MessageReceived -= HandleMessage;
_client.SetGameAsync(null);
_t.Change(Timeout.Infinite, Timeout.Infinite);
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
try { var _ = _msg.DeleteAsync(); } catch { }
var os = OnEnded(_guild.Id);
}
}
private Task HandleMessage(SocketMessage msg)
{
var _ = Task.Run(async () =>
{
if (!(msg.Author is IGuildUser gu) // no unknown users, as they could be bots, or alts
|| 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())
{
_toAward.Enqueue(msg.Author.Id);
if (_isPotLimited && PotSize < _amount)
PotEmptied = true;
}
try
{
await _cs.AddBulkAsync(toAward,
toAward.Select(x => "GameStatus Event"),
toAward.Select(x => _amount),
gamble: true).ConfigureAwait(false);
if (_isPotLimited)
await msg.DeleteAsync(new RequestOptions()
{
await _msg.ModifyAsync(m =>
{
m.Embed = GetEmbed(PotSize).Build();
}, new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
}
Log.Information("Awarded {0} users {1} currency.{2}",
toAward.Count,
_amount,
_isPotLimited ? $" {PotSize} left." : "");
if (potEmpty)
{
var _ = StopEvent();
}
RetryMode = RetryMode.AlwaysFail
});
}
catch (Exception ex)
{
Log.Warning(ex, "Error in OnTimerTick in gamestatusevent");
}
}
public async Task StartEvent()
{
_msg = await _channel.EmbedAsync(GetEmbed(_opts.PotSize)).ConfigureAwait(false);
await _client.SetGameAsync(_code).ConfigureAwait(false);
_client.MessageDeleted += OnMessageDeleted;
_client.MessageReceived += HandleMessage;
_t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
}
private IEmbedBuilder GetEmbed(long pot)
{
return _embedFunc(CurrencyEvent.Type.GameStatus, _opts, pot);
}
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, ISocketMessageChannel _)
{
if (msg.Id == _msg.Id)
{
await StopEvent().ConfigureAwait(false);
}
}
private readonly object stopLock = new object();
public async Task StopEvent()
{
await Task.Yield();
lock (stopLock)
{
if (Stopped)
return;
Stopped = true;
_client.MessageDeleted -= OnMessageDeleted;
_client.MessageReceived -= HandleMessage;
_client.SetGameAsync(null);
_t.Change(Timeout.Infinite, Timeout.Infinite);
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
try { var _ = _msg.DeleteAsync(); } catch { }
var os = OnEnded(_guild.Id);
}
}
private Task HandleMessage(SocketMessage msg)
{
var _ = Task.Run(async () =>
{
if (!(msg.Author is IGuildUser gu) // no unknown users, as they could be bots, or alts
|| 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())
{
_toAward.Enqueue(msg.Author.Id);
if (_isPotLimited && PotSize < _amount)
PotEmptied = true;
}
try
{
await msg.DeleteAsync(new RequestOptions()
{
RetryMode = RetryMode.AlwaysFail
});
}
catch { }
});
return Task.CompletedTask;
}
private readonly object potLock = new object();
private bool TryTakeFromPot()
{
if (_isPotLimited)
{
lock (potLock)
{
if (PotSize < _amount)
return false;
PotSize -= _amount;
return true;
}
}
return true;
}
catch { }
});
return Task.CompletedTask;
}
}
private readonly object potLock = new object();
private bool TryTakeFromPot()
{
if (_isPotLimited)
{
lock (potLock)
{
if (PotSize < _amount)
return false;
PotSize -= _amount;
return true;
}
}
return true;
}
}

View File

@@ -1,12 +1,10 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common
namespace NadekoBot.Modules.Gambling.Common;
public interface ICurrencyEvent
{
public interface ICurrencyEvent
{
event Func<ulong, Task> OnEnded;
Task StopEvent();
Task StartEvent();
}
}
event Func<ulong, Task> OnEnded;
Task StopEvent();
Task StartEvent();
}

View File

@@ -4,207 +4,200 @@ using NadekoBot.Common.Collections;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Common;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NadekoBot.Modules.Gambling.Services;
using Serilog;
namespace NadekoBot.Modules.Gambling.Common.Events
namespace NadekoBot.Modules.Gambling.Common.Events;
public class ReactionEvent : ICurrencyEvent
{
public class ReactionEvent : ICurrencyEvent
private readonly DiscordSocketClient _client;
private readonly IGuild _guild;
private IUserMessage _msg;
private IEmote _emote;
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 ConcurrentHashSet<ulong>();
private readonly ConcurrentQueue<ulong> _toAward = new ConcurrentQueue<ulong>();
private readonly Timer _t;
private readonly Timer _timeout = null;
private readonly bool _noRecentlyJoinedServer;
private readonly EventOptions _opts;
private readonly GamblingConfig _config;
public event Func<ulong, Task> OnEnded;
public ReactionEvent(DiscordSocketClient client, ICurrencyService cs,
SocketGuild g, ITextChannel ch, EventOptions opt, GamblingConfig config,
Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
{
private readonly DiscordSocketClient _client;
private readonly IGuild _guild;
private IUserMessage _msg;
private IEmote _emote;
private readonly ICurrencyService _cs;
private readonly long _amount;
_client = client;
_guild = g;
_cs = cs;
_amount = opt.Amount;
PotSize = opt.PotSize;
_embedFunc = embedFunc;
_isPotLimited = PotSize > 0;
_channel = ch;
_noRecentlyJoinedServer = false;
_opts = opt;
_config = config;
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 ConcurrentHashSet<ulong>();
private readonly ConcurrentQueue<ulong> _toAward = new ConcurrentQueue<ulong>();
private readonly Timer _t;
private readonly Timer _timeout = null;
private readonly bool _noRecentlyJoinedServer;
private readonly EventOptions _opts;
private readonly GamblingConfig _config;
public event Func<ulong, Task> OnEnded;
public ReactionEvent(DiscordSocketClient client, ICurrencyService cs,
SocketGuild g, ITextChannel ch, EventOptions opt, GamblingConfig config,
Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embedFunc)
_t = new Timer(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
if (_opts.Hours > 0)
{
_client = client;
_guild = g;
_cs = cs;
_amount = opt.Amount;
PotSize = opt.PotSize;
_embedFunc = embedFunc;
_isPotLimited = PotSize > 0;
_channel = ch;
_noRecentlyJoinedServer = false;
_opts = opt;
_config = config;
_t = new Timer(OnTimerTick, null, Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(2));
if (_opts.Hours > 0)
{
_timeout = new Timer(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
}
}
private void EventTimeout(object state)
{
var _ = StopEvent();
}
private async void OnTimerTick(object state)
{
var potEmpty = PotEmptied;
List<ulong> toAward = new List<ulong>();
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).ConfigureAwait(false);
if (_isPotLimited)
{
await _msg.ModifyAsync(m =>
{
m.Embed = GetEmbed(PotSize).Build();
}, new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
}
Log.Information("Awarded {0} users {1} currency.{2}",
toAward.Count,
_amount,
_isPotLimited ? $" {PotSize} left." : "");
if (potEmpty)
{
var _ = StopEvent();
}
}
catch (Exception ex)
{
Log.Warning(ex, "Error adding bulk currency to users");
}
}
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)).ConfigureAwait(false);
await _msg.AddReactionAsync(_emote).ConfigureAwait(false);
_client.MessageDeleted += OnMessageDeleted;
_client.ReactionAdded += HandleReaction;
_t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
}
private IEmbedBuilder GetEmbed(long pot)
{
return _embedFunc(CurrencyEvent.Type.Reaction, _opts, pot);
}
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, ISocketMessageChannel _)
{
if (msg.Id == _msg.Id)
{
await StopEvent().ConfigureAwait(false);
}
}
private readonly object stopLock = new object();
public async Task StopEvent()
{
await Task.Yield();
lock (stopLock)
{
if (Stopped)
return;
Stopped = true;
_client.MessageDeleted -= OnMessageDeleted;
_client.ReactionAdded -= HandleReaction;
_t.Change(Timeout.Infinite, Timeout.Infinite);
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
try { var _ = _msg.DeleteAsync(); } catch { }
var os = OnEnded(_guild.Id);
}
}
private Task HandleReaction(Cacheable<IUserMessage, ulong> msg,
ISocketMessageChannel ch, SocketReaction r)
{
var _ = Task.Run(() =>
{
if (_emote.Name != r.Emote.Name)
return;
var gu = (r.User.IsSpecified ? r.User.Value : null) as IGuildUser;
if (gu is null // 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
{
return;
}
// there has to be money left in the pot
// and the user wasn't rewarded
if (_awardedUsers.Add(r.UserId) && TryTakeFromPot())
{
_toAward.Enqueue(r.UserId);
if (_isPotLimited && PotSize < _amount)
PotEmptied = true;
}
});
return Task.CompletedTask;
}
private readonly object potLock = new object();
private bool TryTakeFromPot()
{
if (_isPotLimited)
{
lock (potLock)
{
if (PotSize < _amount)
return false;
PotSize -= _amount;
return true;
}
}
return true;
_timeout = new Timer(EventTimeout, null, TimeSpan.FromHours(_opts.Hours), Timeout.InfiniteTimeSpan);
}
}
}
private void EventTimeout(object state)
{
var _ = StopEvent();
}
private async void OnTimerTick(object state)
{
var potEmpty = PotEmptied;
List<ulong> toAward = new List<ulong>();
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).ConfigureAwait(false);
if (_isPotLimited)
{
await _msg.ModifyAsync(m =>
{
m.Embed = GetEmbed(PotSize).Build();
}, new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
}
Log.Information("Awarded {0} users {1} currency.{2}",
toAward.Count,
_amount,
_isPotLimited ? $" {PotSize} left." : "");
if (potEmpty)
{
var _ = StopEvent();
}
}
catch (Exception ex)
{
Log.Warning(ex, "Error adding bulk currency to users");
}
}
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)).ConfigureAwait(false);
await _msg.AddReactionAsync(_emote).ConfigureAwait(false);
_client.MessageDeleted += OnMessageDeleted;
_client.ReactionAdded += HandleReaction;
_t.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
}
private IEmbedBuilder GetEmbed(long pot)
{
return _embedFunc(CurrencyEvent.Type.Reaction, _opts, pot);
}
private async Task OnMessageDeleted(Cacheable<IMessage, ulong> msg, ISocketMessageChannel _)
{
if (msg.Id == _msg.Id)
{
await StopEvent().ConfigureAwait(false);
}
}
private readonly object stopLock = new object();
public async Task StopEvent()
{
await Task.Yield();
lock (stopLock)
{
if (Stopped)
return;
Stopped = true;
_client.MessageDeleted -= OnMessageDeleted;
_client.ReactionAdded -= HandleReaction;
_t.Change(Timeout.Infinite, Timeout.Infinite);
_timeout?.Change(Timeout.Infinite, Timeout.Infinite);
try { var _ = _msg.DeleteAsync(); } catch { }
var os = OnEnded(_guild.Id);
}
}
private Task HandleReaction(Cacheable<IUserMessage, ulong> msg,
ISocketMessageChannel ch, SocketReaction r)
{
var _ = Task.Run(() =>
{
if (_emote.Name != r.Emote.Name)
return;
var gu = (r.User.IsSpecified ? r.User.Value : null) as IGuildUser;
if (gu is null // 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
{
return;
}
// there has to be money left in the pot
// and the user wasn't rewarded
if (_awardedUsers.Add(r.UserId) && TryTakeFromPot())
{
_toAward.Enqueue(r.UserId);
if (_isPotLimited && PotSize < _amount)
PotEmptied = true;
}
});
return Task.CompletedTask;
}
private readonly object potLock = new object();
private bool TryTakeFromPot()
{
if (_isPotLimited)
{
lock (potLock)
{
if (PotSize < _amount)
return false;
PotSize -= _amount;
return true;
}
}
return true;
}
}

View File

@@ -1,321 +1,318 @@
using System;
using System.Collections.Generic;
using Cloneable;
using Cloneable;
using NadekoBot.Common;
using NadekoBot.Common.Yml;
using SixLabors.ImageSharp.PixelFormats;
using YamlDotNet.Serialization;
namespace NadekoBot.Modules.Gambling.Common
namespace NadekoBot.Modules.Gambling.Common;
[Cloneable]
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
{
[Cloneable]
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
public GamblingConfig()
{
public GamblingConfig()
{
BetRoll = new BetRollConfig();
WheelOfFortune = new WheelOfFortuneSettings();
Waifu = new WaifuConfig();
Currency = new CurrencyConfig();
BetFlip = new BetFlipConfig();
Generation = new GenerationConfig();
Timely = new TimelyConfig();
Decay = new DecayConfig();
Slots = new SlotsConfig();
}
BetRoll = new BetRollConfig();
WheelOfFortune = new WheelOfFortuneSettings();
Waifu = new WaifuConfig();
Currency = new CurrencyConfig();
BetFlip = new BetFlipConfig();
Generation = new GenerationConfig();
Timely = new TimelyConfig();
Decay = new DecayConfig();
Slots = new SlotsConfig();
}
[Comment(@"DO NOT CHANGE")]
public int Version { get; set; } = 2;
[Comment(@"DO NOT CHANGE")]
public int Version { get; set; } = 2;
[Comment(@"Currency settings")]
public CurrencyConfig Currency { get; set; }
[Comment(@"Currency settings")]
public CurrencyConfig Currency { get; set; }
[Comment(@"Minimum amount users can bet (>=0)")]
public int MinBet { get; set; } = 0;
[Comment(@"Minimum amount users can bet (>=0)")]
public int MinBet { get; set; } = 0;
[Comment(@"Maximum amount users can bet
[Comment(@"Maximum amount users can bet
Set 0 for unlimited")]
public int MaxBet { get; set; } = 0;
public int MaxBet { get; set; } = 0;
[Comment(@"Settings for betflip command")]
public BetFlipConfig BetFlip { get; set; }
[Comment(@"Settings for betflip command")]
public BetFlipConfig BetFlip { get; set; }
[Comment(@"Settings for betroll command")]
public BetRollConfig BetRoll { get; set; }
[Comment(@"Settings for betroll command")]
public BetRollConfig BetRoll { get; set; }
[Comment(@"Automatic currency generation settings.")]
public GenerationConfig Generation { get; set; }
[Comment(@"Automatic currency generation settings.")]
public GenerationConfig Generation { get; set; }
[Comment(@"Settings for timely command
[Comment(@"Settings for timely command
(letting people claim X amount of currency every Y hours)")]
public TimelyConfig Timely { get; set; }
public TimelyConfig Timely { get; set; }
[Comment(@"How much will each user's owned currency decay over time.")]
public DecayConfig Decay { get; set; }
[Comment(@"How much will each user's owned currency decay over time.")]
public DecayConfig Decay { get; set; }
[Comment(@"Settings for Wheel Of Fortune command.")]
public WheelOfFortuneSettings WheelOfFortune { get; set; }
[Comment(@"Settings for Wheel Of Fortune command.")]
public WheelOfFortuneSettings WheelOfFortune { get; set; }
[Comment(@"Settings related to waifus")]
public WaifuConfig Waifu { get; set; }
[Comment(@"Settings related to waifus")]
public WaifuConfig Waifu { get; set; }
[Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
[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;
public decimal PatreonCurrencyPerCent { get; set; } = 1;
[Comment(@"Currency reward per vote.
[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;
public long VoteReward { get; set; } = 100;
[Comment(@"Slot config")]
public SlotsConfig Slots { get; set; }
}
[Comment(@"Slot config")]
public SlotsConfig Slots { get; set; }
}
public class CurrencyConfig
{
[Comment(@"What is the emoji/character which represents the currency")]
public string Sign { get; set; } = "🌸";
public class CurrencyConfig
{
[Comment(@"What is the emoji/character which represents the currency")]
public string Sign { get; set; } = "🌸";
[Comment(@"What is the name of the currency")]
public string Name { get; set; } = "Nadeko Flower";
}
[Comment(@"What is the name of the currency")]
public string Name { get; set; } = "Nadeko Flower";
}
[Cloneable]
public partial class TimelyConfig
{
[Comment(@"How much currency will the users get every time they run .timely command
[Cloneable]
public partial class TimelyConfig
{
[Comment(@"How much currency will the users get every time they run .timely command
setting to 0 or less will disable this feature")]
public int Amount { get; set; } = 0;
public int Amount { get; set; } = 0;
[Comment(@"How often (in hours) can users claim currency with .timely command
[Comment(@"How often (in hours) can users claim currency with .timely command
setting to 0 or less will disable this feature")]
public int Cooldown { get; set; } = 24;
}
public int Cooldown { get; set; } = 24;
}
[Cloneable]
public partial class BetFlipConfig
{
[Comment(@"Bet multiplier if user guesses correctly")]
public decimal Multiplier { get; set; } = 1.95M;
}
[Cloneable]
public partial class BetFlipConfig
{
[Comment(@"Bet multiplier if user guesses correctly")]
public decimal Multiplier { get; set; } = 1.95M;
}
[Cloneable]
public partial class BetRollConfig
{
[Comment(@"When betroll is played, user will roll a number 0-100.
[Cloneable]
public partial class BetRollConfig
{
[Comment(@"When betroll is played, user will roll a number 0-100.
This setting will describe which multiplier is used for when the roll is higher than the given number.
Doesn't have to be ordered.")]
public BetRollPair[] Pairs { get; set; } = Array.Empty<BetRollPair>();
public BetRollPair[] Pairs { get; set; } = Array.Empty<BetRollPair>();
public BetRollConfig()
{
Pairs = new BetRollPair[]
{
new BetRollPair { WhenAbove = 99, MultiplyBy = 10 },
new BetRollPair { WhenAbove = 90, MultiplyBy = 4 },
new BetRollPair { WhenAbove = 66, MultiplyBy = 2 }
};
}
}
[Cloneable]
public partial class GenerationConfig
public BetRollConfig()
{
[Comment(@"when currency is generated, should it also have a random password
Pairs = new BetRollPair[]
{
new BetRollPair { WhenAbove = 99, MultiplyBy = 10 },
new BetRollPair { WhenAbove = 90, MultiplyBy = 4 },
new BetRollPair { WhenAbove = 66, MultiplyBy = 2 }
};
}
}
[Cloneable]
public partial class GenerationConfig
{
[Comment(@"when currency is generated, should it also have a random password
associated with it which users have to type after the .pick command
in order to get it")]
public bool HasPassword { get; set; } = true;
public bool HasPassword { get; set; } = true;
[Comment(@"Every message sent has a certain % chance to generate the currency
[Comment(@"Every message sent has a certain % chance to generate the currency
specify the percentage here (1 being 100%, 0 being 0% - for example
default is 0.02, which is 2%")]
public decimal Chance { get; set; } = 0.02M;
public decimal Chance { get; set; } = 0.02M;
[Comment(@"How many seconds have to pass for the next message to have a chance to spawn currency")]
public int GenCooldown { get; set; } = 10;
[Comment(@"How many seconds have to pass for the next message to have a chance to spawn currency")]
public int GenCooldown { get; set; } = 10;
[Comment(@"Minimum amount of currency that can spawn")]
public int MinAmount { get; set; } = 1;
[Comment(@"Minimum amount of currency that can spawn")]
public int MinAmount { get; set; } = 1;
[Comment(@"Maximum amount of currency that can spawn.
[Comment(@"Maximum amount of currency that can spawn.
Set to the same value as MinAmount to always spawn the same amount")]
public int MaxAmount { get; set; } = 1;
}
public int MaxAmount { get; set; } = 1;
}
[Cloneable]
public partial class DecayConfig
{
[Comment(@"Percentage of user's current currency which will be deducted every 24h.
[Cloneable]
public partial class DecayConfig
{
[Comment(@"Percentage of user's current currency which will be deducted every 24h.
0 - 1 (1 is 100%, 0.5 50%, 0 disabled)")]
public decimal Percent { get; set; } = 0;
public decimal Percent { get; set; } = 0;
[Comment(@"Maximum amount of user's currency that can decay at each interval. 0 for unlimited.")]
public int MaxDecay { get; set; } = 0;
[Comment(@"Maximum amount of user's currency that can decay at each interval. 0 for unlimited.")]
public int MaxDecay { get; set; } = 0;
[Comment(@"Only users who have more than this amount will have their currency decay.")]
public int MinThreshold { get; set; } = 99;
[Comment(@"Only users who have more than this amount will have their currency decay.")]
public int MinThreshold { get; set; } = 99;
[Comment(@"How often, in hours, does the decay run. Default is 24 hours")]
public int HourInterval { get; set; } = 24;
}
[Comment(@"How often, in hours, does the decay run. Default is 24 hours")]
public int HourInterval { get; set; } = 24;
}
[Cloneable]
public partial class WheelOfFortuneSettings
[Cloneable]
public partial class WheelOfFortuneSettings
{
[Comment(@"Self-Explanatory. Has to have 8 values, otherwise the command won't work.")]
public decimal[] Multipliers { get; set; }
public WheelOfFortuneSettings()
{
[Comment(@"Self-Explanatory. Has to have 8 values, otherwise the command won't work.")]
public decimal[] Multipliers { get; set; }
public WheelOfFortuneSettings()
Multipliers = new decimal[]
{
Multipliers = new decimal[]
{
1.7M,
1.5M,
0.2M,
0.1M,
0.3M,
0.5M,
1.2M,
2.4M,
};
}
1.7M,
1.5M,
0.2M,
0.1M,
0.3M,
0.5M,
1.2M,
2.4M,
};
}
}
[Cloneable]
public sealed partial class WaifuConfig
{
[Comment(@"Minimum price a waifu can have")]
public int MinPrice { get; set; } = 50;
[Cloneable]
public sealed partial class WaifuConfig
{
[Comment(@"Minimum price a waifu can have")]
public int MinPrice { get; set; } = 50;
public MultipliersData Multipliers { get; set; } = new MultipliersData();
public MultipliersData Multipliers { get; set; } = new MultipliersData();
[Comment(@"List of items available for gifting.
[Comment(@"List of items available for gifting.
If negative is true, gift will instead reduce waifu value.")]
public List<WaifuItemModel> Items { get; set; } = new List<WaifuItemModel>();
public List<WaifuItemModel> Items { get; set; } = new List<WaifuItemModel>();
public WaifuConfig()
{
Items = new()
{
new("🥔", 5, "Potato"),
new("🍪", 10, "Cookie"),
new("🥖", 20, "Bread"),
new("🍭", 30, "Lollipop"),
new("🌹", 50, "Rose"),
new("🍺", 70, "Beer"),
new("🌮", 85, "Taco"),
new("💌", 100, "LoveLetter"),
new("🥛", 125, "Milk"),
new("🍕", 150, "Pizza"),
new("🍫", 200, "Chocolate"),
new("🍦", 250, "Icecream"),
new("🍣", 300, "Sushi"),
new("🍚", 400, "Rice"),
new("🍉", 500, "Watermelon"),
new("🍱", 600, "Bento"),
new("🎟", 800, "MovieTicket"),
new("🍰", 1000, "Cake"),
new("📔", 1500, "Book"),
new("🐱", 2000, "Cat"),
new("🐶", 2001, "Dog"),
new("🐼", 2500, "Panda"),
new("💄", 3000, "Lipstick"),
new("👛", 3500, "Purse"),
new("📱", 4000, "iPhone"),
new("👗", 4500, "Dress"),
new("💻", 5000, "Laptop"),
new("🎻", 7500, "Violin"),
new("🎹", 8000, "Piano"),
new("🚗", 9000, "Car"),
new("💍", 10000, "Ring"),
new("🛳", 12000, "Ship"),
new("🏠", 15000, "House"),
new("🚁", 20000, "Helicopter"),
new("🚀", 30000, "Spaceship"),
new("🌕", 50000, "Moon")
};
}
}
[Cloneable]
public sealed partial class MultipliersData
public WaifuConfig()
{
[Comment(@"Multiplier for waifureset. Default 150.
Items = new()
{
new("🥔", 5, "Potato"),
new("🍪", 10, "Cookie"),
new("🥖", 20, "Bread"),
new("🍭", 30, "Lollipop"),
new("🌹", 50, "Rose"),
new("🍺", 70, "Beer"),
new("🌮", 85, "Taco"),
new("💌", 100, "LoveLetter"),
new("🥛", 125, "Milk"),
new("🍕", 150, "Pizza"),
new("🍫", 200, "Chocolate"),
new("🍦", 250, "Icecream"),
new("🍣", 300, "Sushi"),
new("🍚", 400, "Rice"),
new("🍉", 500, "Watermelon"),
new("🍱", 600, "Bento"),
new("🎟", 800, "MovieTicket"),
new("🍰", 1000, "Cake"),
new("📔", 1500, "Book"),
new("🐱", 2000, "Cat"),
new("🐶", 2001, "Dog"),
new("🐼", 2500, "Panda"),
new("💄", 3000, "Lipstick"),
new("👛", 3500, "Purse"),
new("📱", 4000, "iPhone"),
new("👗", 4500, "Dress"),
new("💻", 5000, "Laptop"),
new("🎻", 7500, "Violin"),
new("🎹", 8000, "Piano"),
new("🚗", 9000, "Car"),
new("💍", 10000, "Ring"),
new("🛳", 12000, "Ship"),
new("🏠", 15000, "House"),
new("🚁", 20000, "Helicopter"),
new("🚀", 30000, "Spaceship"),
new("🌕", 50000, "Moon")
};
}
}
[Cloneable]
public sealed partial class MultipliersData
{
[Comment(@"Multiplier for waifureset. Default 150.
Formula (at the time of writing this):
price = (waifu_price * 1.25f) + ((number_of_divorces + changes_of_heart + 2) * WaifuReset) rounded up")]
public int WaifuReset { get; set; } = 150;
public int WaifuReset { get; set; } = 150;
[Comment(@"The minimum amount of currency that you have to pay
[Comment(@"The minimum amount of currency that you have to pay
in order to buy a waifu who doesn't have a crush on you.
Default is 1.1
Example: If a waifu is worth 100, you will have to pay at least 100 * NormalClaim currency to claim her.
(100 * 1.1 = 110)")]
public decimal NormalClaim { get; set; } = 1.1m;
public decimal NormalClaim { get; set; } = 1.1m;
[Comment(@"The minimum amount of currency that you have to pay
[Comment(@"The minimum amount of currency that you have to pay
in order to buy a waifu that has a crush on you.
Default is 0.88
Example: If a waifu is worth 100, you will have to pay at least 100 * CrushClaim currency to claim her.
(100 * 0.88 = 88)")]
public decimal CrushClaim { get; set; } = 0.88M;
public decimal CrushClaim { get; set; } = 0.88M;
[Comment(@"When divorcing a waifu, her new value will be her current value multiplied by this number.
[Comment(@"When divorcing a waifu, her new value will be her current value multiplied by this number.
Default 0.75 (meaning will lose 25% of her value)")]
public decimal DivorceNewValue { get; set; } = 0.75M;
public decimal DivorceNewValue { get; set; } = 0.75M;
[Comment(@"All gift prices will be multiplied by this number.
[Comment(@"All gift prices will be multiplied by this number.
Default 1 (meaning no effect)")]
public decimal AllGiftPrices { get; set; } = 1.0M;
public decimal AllGiftPrices { get; set; } = 1.0M;
[Comment(@"What percentage of the value of the gift will a waifu gain when she's gifted.
[Comment(@"What percentage of the value of the gift will a waifu gain when she's gifted.
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;
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'.
[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 decimal NegativeGiftEffect { get; set; } = 0.50M;
}
public sealed partial 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 sealed partial 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;
}
[Cloneable]
public sealed partial class WaifuItemModel
{
public string ItemEmoji { get; set; }
public int Price { get; set; }
public string Name { get; set; }
[Cloneable]
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; }
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)]
public bool Negative { get; set; }
public WaifuItemModel()
{
public WaifuItemModel()
{
}
}
public WaifuItemModel(string itemEmoji, int price, string name, bool negative = false)
{
ItemEmoji = itemEmoji;
Price = price;
Name = name;
Negative = negative;
}
public override string ToString() => Name;
}
[Cloneable]
public sealed partial class BetRollPair
public WaifuItemModel(string itemEmoji, int price, string name, bool negative = false)
{
public int WhenAbove { get; set; }
public float MultiplyBy { get; set; }
ItemEmoji = itemEmoji;
Price = price;
Name = name;
Negative = negative;
}
public override string ToString() => Name;
}
[Cloneable]
public sealed partial class BetRollPair
{
public int WhenAbove { get; set; }
public float MultiplyBy { get; set; }
}

View File

@@ -1,8 +1,7 @@
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public enum GamblingError
{
public enum GamblingError
{
None,
NotEnough
}
None,
NotEnough
}

View File

@@ -1,68 +1,64 @@
using System;
using Discord;
using NadekoBot.Services;
using NadekoBot.Modules;
using Discord;
using System.Threading.Tasks;
using NadekoBot.Modules.Gambling.Services;
namespace NadekoBot.Modules.Gambling.Common
namespace NadekoBot.Modules.Gambling.Common;
public abstract class GamblingModule<TService> : NadekoModule<TService>
{
public abstract class GamblingModule<TService> : NadekoModule<TService>
{
private readonly Lazy<GamblingConfig> _lazyConfig;
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 Lazy<GamblingConfig>(() => gambService.Data);
}
private async Task<bool> InternalCheckBet(long amount)
{
if (amount < 1)
{
return false;
}
if (amount < _config.MinBet)
{
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));
return false;
}
return true;
}
protected Task<bool> CheckBetMandatory(long amount)
{
if (amount < 1)
{
return Task.FromResult(false);
}
return InternalCheckBet(amount);
}
protected Task<bool> CheckBetOptional(long amount)
{
if (amount == 0)
{
return Task.FromResult(true);
}
return InternalCheckBet(amount);
}
protected GamblingModule(GamblingConfigService gambService)
{
_lazyConfig = new Lazy<GamblingConfig>(() => gambService.Data);
}
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
private async Task<bool> InternalCheckBet(long amount)
{
protected GamblingSubmodule(GamblingConfigService gamblingConfService) : base(gamblingConfService)
if (amount < 1)
{
return false;
}
if (amount < _config.MinBet)
{
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));
return false;
}
return true;
}
protected Task<bool> CheckBetMandatory(long amount)
{
if (amount < 1)
{
return Task.FromResult(false);
}
return InternalCheckBet(amount);
}
protected Task<bool> CheckBetOptional(long amount)
{
if (amount == 0)
{
return Task.FromResult(true);
}
return InternalCheckBet(amount);
}
}
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
{
protected GamblingSubmodule(GamblingConfigService gamblingConfService) : base(gamblingConfService)
{
}
}

View File

@@ -1,8 +1,7 @@
namespace NadekoBot.Modules.Gambling.Common
namespace NadekoBot.Modules.Gambling.Common;
public class Payout
{
public class Payout
{
public string User { get; set; }
public int Amount { get; set; }
}
}
public string User { get; set; }
public int Amount { get; set; }
}

View File

@@ -1,140 +1,137 @@
using NadekoBot.Common;
using NadekoBot.Services;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common
namespace NadekoBot.Modules.Gambling.Common;
public class RollDuelGame
{
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 State
{
public ulong P1 { get; }
public ulong P2 { get; }
Waiting,
Running,
Ended,
}
private readonly ulong _botId;
public long Amount { get; }
private readonly ICurrencyService _cs;
public enum State
{
Waiting,
Running,
Ended,
}
public enum Reason
{
Normal,
NoFunds,
Timeout,
}
public enum Reason
{
Normal,
NoFunds,
Timeout,
}
private readonly Timer _timeoutTimer;
private readonly NadekoRandom _rng = new NadekoRandom();
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
private readonly Timer _timeoutTimer;
private readonly NadekoRandom _rng = new NadekoRandom();
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
public event Func<RollDuelGame, Task> OnGameTick;
public event Func<RollDuelGame, Reason, Task> OnEnded;
public event Func<RollDuelGame, Task> OnGameTick;
public event Func<RollDuelGame, Reason, Task> OnEnded;
public List<(int, int)> Rolls { get; } = new List<(int, int)>();
public State CurrentState { get; private set; }
public ulong Winner { get; private set; }
public List<(int, int)> Rolls { get; } = new List<(int, int)>();
public State CurrentState { get; private set; }
public ulong Winner { get; private set; }
public RollDuelGame(ICurrencyService cs, ulong botId, ulong p1, ulong p2, long amount)
{
this.P1 = p1;
this.P2 = p2;
this._botId = botId;
this.Amount = amount;
_cs = cs;
public RollDuelGame(ICurrencyService cs, ulong botId, ulong p1, ulong p2, long amount)
{
this.P1 = p1;
this.P2 = p2;
this._botId = botId;
this.Amount = amount;
_cs = cs;
_timeoutTimer = new Timer(async delegate
{
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (CurrentState != State.Waiting)
return;
CurrentState = State.Ended;
await (OnEnded?.Invoke(this, Reason.Timeout)).ConfigureAwait(false);
}
catch { }
finally
{
_locker.Release();
}
}, null, TimeSpan.FromSeconds(15), TimeSpan.FromMilliseconds(-1));
}
public async Task StartGame()
_timeoutTimer = new Timer(async delegate
{
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (CurrentState != State.Waiting)
return;
_timeoutTimer.Change(Timeout.Infinite, Timeout.Infinite);
CurrentState = State.Running;
CurrentState = State.Ended;
await (OnEnded?.Invoke(this, Reason.Timeout)).ConfigureAwait(false);
}
catch { }
finally
{
_locker.Release();
}
if(!await _cs.RemoveAsync(P1, "Roll Duel", Amount).ConfigureAwait(false))
{
await (OnEnded?.Invoke(this, Reason.NoFunds)).ConfigureAwait(false);
CurrentState = State.Ended;
return;
}
if(!await _cs.RemoveAsync(P2, "Roll Duel", Amount).ConfigureAwait(false))
{
await _cs.AddAsync(P1, "Roll Duel - refund", Amount).ConfigureAwait(false);
await (OnEnded?.Invoke(this, Reason.NoFunds)).ConfigureAwait(false);
CurrentState = State.Ended;
return;
}
int n1, n2;
do
{
n1 = _rng.Next(0, 5);
n2 = _rng.Next(0, 5);
Rolls.Add((n1, n2));
if (n1 != n2)
{
if (n1 > n2)
{
Winner = P1;
}
else
{
Winner = P2;
}
var won = (long)(Amount * 2 * 0.98f);
await _cs.AddAsync(Winner, "Roll Duel win", won)
.ConfigureAwait(false);
await _cs.AddAsync(_botId, "Roll Duel fee", Amount * 2 - won)
.ConfigureAwait(false);
}
try { await (OnGameTick?.Invoke(this)).ConfigureAwait(false); } catch { }
await Task.Delay(2500).ConfigureAwait(false);
if (n1 != n2)
break;
}
while (true);
CurrentState = State.Ended;
await (OnEnded?.Invoke(this, Reason.Normal)).ConfigureAwait(false);
}
}, null, TimeSpan.FromSeconds(15), TimeSpan.FromMilliseconds(-1));
}
public struct RollDuelChallenge
public async Task StartGame()
{
public ulong Player1 { get; set; }
public ulong Player2 { get; set; }
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (CurrentState != State.Waiting)
return;
_timeoutTimer.Change(Timeout.Infinite, Timeout.Infinite);
CurrentState = State.Running;
}
finally
{
_locker.Release();
}
if(!await _cs.RemoveAsync(P1, "Roll Duel", Amount).ConfigureAwait(false))
{
await (OnEnded?.Invoke(this, Reason.NoFunds)).ConfigureAwait(false);
CurrentState = State.Ended;
return;
}
if(!await _cs.RemoveAsync(P2, "Roll Duel", Amount).ConfigureAwait(false))
{
await _cs.AddAsync(P1, "Roll Duel - refund", Amount).ConfigureAwait(false);
await (OnEnded?.Invoke(this, Reason.NoFunds)).ConfigureAwait(false);
CurrentState = State.Ended;
return;
}
int n1, n2;
do
{
n1 = _rng.Next(0, 5);
n2 = _rng.Next(0, 5);
Rolls.Add((n1, n2));
if (n1 != n2)
{
if (n1 > n2)
{
Winner = P1;
}
else
{
Winner = P2;
}
var won = (long)(Amount * 2 * 0.98f);
await _cs.AddAsync(Winner, "Roll Duel win", won)
.ConfigureAwait(false);
await _cs.AddAsync(_botId, "Roll Duel fee", Amount * 2 - won)
.ConfigureAwait(false);
}
try { await (OnGameTick?.Invoke(this)).ConfigureAwait(false); } catch { }
await Task.Delay(2500).ConfigureAwait(false);
if (n1 != n2)
break;
}
while (true);
CurrentState = State.Ended;
await (OnEnded?.Invoke(this, Reason.Normal)).ConfigureAwait(false);
}
}
public struct RollDuelChallenge
{
public ulong Player1 { get; set; }
public ulong Player2 { get; set; }
}

View File

@@ -1,44 +1,41 @@
using System;
using System.Linq;
using NadekoBot.Common;
using NadekoBot.Common;
namespace NadekoBot.Modules.Gambling.Common.Slot
namespace NadekoBot.Modules.Gambling.Common.Slot;
public class SlotGame
{
public class SlotGame
public class Result
{
public class Result
public float Multiplier { get; }
public int[] Rolls { get; }
public Result(float multiplier, int[] rolls)
{
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 multi = 0;
if (rolls.All(x => x == 5))
multi = 30;
else if (rolls.All(x => x == rolls[0]))
multi = 10;
else if (rolls.Count(x => x == 5) == 2)
multi = 4;
else if (rolls.Any(x => x == 5))
multi = 1;
return new Result(multi, 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 multi = 0;
if (rolls.All(x => x == 5))
multi = 30;
else if (rolls.All(x => x == rolls[0]))
multi = 10;
else if (rolls.Count(x => x == 5) == 2)
multi = 4;
else if (rolls.Any(x => x == 5))
multi = 1;
return new Result(multi, rolls);
}
}

View File

@@ -1,12 +1,9 @@
using System.Collections.Generic;
namespace NadekoBot.Modules.Gambling;
namespace NadekoBot.Modules.Gambling
public class SlotResponse
{
public class SlotResponse
{
public float Multiplier { get; set; }
public long Won { get; set; }
public List<int> Rolls { get; set; } = new List<int>();
public GamblingError Error { get; set; }
}
public float Multiplier { get; set; }
public long Won { get; set; }
public List<int> Rolls { get; set; } = new List<int>();
public GamblingError Error { get; set; }
}

View File

@@ -1,16 +1,15 @@
namespace NadekoBot.Modules.Gambling.Common.Waifu
namespace NadekoBot.Modules.Gambling.Common.Waifu;
public enum AffinityTitle
{
public enum AffinityTitle
{
Pure,
Faithful,
Playful,
Cheater,
Tainted,
Corrupted,
Lewd,
Sloot,
Depraved,
Harlot
}
}
Pure,
Faithful,
Playful,
Cheater,
Tainted,
Corrupted,
Lewd,
Sloot,
Depraved,
Harlot
}

View File

@@ -1,18 +1,17 @@
namespace NadekoBot.Modules.Gambling.Common.Waifu
namespace NadekoBot.Modules.Gambling.Common.Waifu;
public enum ClaimTitle
{
public enum ClaimTitle
{
Lonely,
Devoted,
Rookie,
Schemer,
Dilettante,
Intermediate,
Seducer,
Expert,
Veteran,
Incubis,
Harem_King,
Harem_God,
}
}
Lonely,
Devoted,
Rookie,
Schemer,
Dilettante,
Intermediate,
Seducer,
Expert,
Veteran,
Incubis,
Harem_King,
Harem_God,
}

View File

@@ -1,10 +1,9 @@
namespace NadekoBot.Modules.Gambling.Common.Waifu
namespace NadekoBot.Modules.Gambling.Common.Waifu;
public enum DivorceResult
{
public enum DivorceResult
{
Success,
SucessWithPenalty,
NotYourWife,
Cooldown
}
}
Success,
SucessWithPenalty,
NotYourWife,
Cooldown
}

View File

@@ -1,9 +1,8 @@
namespace NadekoBot.Modules.Gambling.Common.Waifu
namespace NadekoBot.Modules.Gambling.Common.Waifu;
public enum WaifuClaimResult
{
public enum WaifuClaimResult
{
Success,
NotEnoughFunds,
InsufficientAmount
}
}
Success,
NotEnoughFunds,
InsufficientAmount
}

View File

@@ -1,47 +1,45 @@
using System.Threading.Tasks;
using NadekoBot.Common;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Services;
namespace NadekoBot.Modules.Gambling.Common.WheelOfFortune
namespace NadekoBot.Modules.Gambling.Common.WheelOfFortune;
public class WheelOfFortuneGame
{
public class WheelOfFortuneGame
public class Result
{
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)
{
_rng = new NadekoRandom();
_cs = cs;
_bet = bet;
_config = config;
_userId = userId;
}
public async Task<Result> SpinAsync()
{
var result = _rng.Next(0, _config.WheelOfFortune.Multipliers.Length);
var amount = (long)(_bet * _config.WheelOfFortune.Multipliers[result]);
if (amount > 0)
await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, gamble: true).ConfigureAwait(false);
return new Result
{
Index = result,
Amount = amount,
};
}
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)
{
_rng = new NadekoRandom();
_cs = cs;
_bet = bet;
_config = config;
_userId = userId;
}
public async Task<Result> SpinAsync()
{
var result = _rng.Next(0, _config.WheelOfFortune.Multipliers.Length);
var amount = (long)(_bet * _config.WheelOfFortune.Multipliers[result]);
if (amount > 0)
await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, gamble: true).ConfigureAwait(false);
return new Result
{
Index = result,
Amount = amount,
};
}
}

View File

@@ -1,400 +1,397 @@
using CommandLine;
using NadekoBot.Common;
using NadekoBot.Services;
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Common.Connect4
namespace NadekoBot.Modules.Gambling.Common.Connect4;
public sealed class Connect4Game : IDisposable
{
public sealed class Connect4Game : IDisposable
public enum Phase
{
public enum Phase
Joining, // waiting for second player to join
P1Move,
P2Move,
Ended,
}
public enum Field //temporary most likely
{
Empty,
P1,
P2,
}
public enum Result
{
Draw,
CurrentPlayerWon,
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;
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
private readonly Options _options;
private readonly ICurrencyService _cs;
private readonly NadekoRandom _rng;
private Timer _playerTimeoutTimer;
/* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
*/
public Connect4Game(ulong userId, string userName, Options options, ICurrencyService cs)
{
_players[0] = (userId, userName);
_options = options;
_cs = cs;
_rng = new NadekoRandom();
for (int i = 0; i < NumberOfColumns * NumberOfRows; i++)
{
Joining, // waiting for second player to join
P1Move,
P2Move,
Ended,
_gameState[i] = Field.Empty;
}
}
public enum Field //temporary most likely
public void Initialize()
{
if (CurrentPhase != Phase.Joining)
return;
var _ = Task.Run(async () =>
{
Empty,
P1,
P2,
}
public enum Result
{
Draw,
CurrentPlayerWon,
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;
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
private readonly Options _options;
private readonly ICurrencyService _cs;
private readonly NadekoRandom _rng;
private Timer _playerTimeoutTimer;
/* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
* [ ][ ][ ][ ][ ][ ]
*/
public Connect4Game(ulong userId, string userName, Options options, ICurrencyService cs)
{
_players[0] = (userId, userName);
_options = options;
_cs = cs;
_rng = new NadekoRandom();
for (int i = 0; i < NumberOfColumns * NumberOfRows; i++)
await Task.Delay(15000).ConfigureAwait(false);
await _locker.WaitAsync().ConfigureAwait(false);
try
{
_gameState[i] = Field.Empty;
if (_players[1] is null)
{
var __ = OnGameFailedToStart?.Invoke(this);
CurrentPhase = Phase.Ended;
await _cs.AddAsync(_players[0].Value.UserId, "Connect4-refund", _options.Bet, true).ConfigureAwait(false);
return;
}
}
}
finally { _locker.Release(); }
});
}
public void Initialize()
public async Task<bool> Join(ulong userId, string userName, int bet)
{
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (CurrentPhase != Phase.Joining)
return;
var _ = Task.Run(async () =>
if (CurrentPhase != Phase.Joining) //can't join if its not a joining phase
return false;
if (_players[0].Value.UserId == userId) // same user can't join own game
return false;
if (bet != _options.Bet) // can't join if bet amount is not the same
return false;
if (!await _cs.RemoveAsync(userId, "Connect4-bet", bet, true).ConfigureAwait(false)) // user doesn't have enough money to gamble
return false;
if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player
{
_players[1] = _players[0];
_players[0] = (userId, userName);
}
else //else join as a second player
_players[1] = (userId, userName);
CurrentPhase = Phase.P1Move; //start the game
_playerTimeoutTimer = new Timer(async state =>
{
await Task.Delay(15000).ConfigureAwait(false);
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (_players[1] is null)
{
var __ = OnGameFailedToStart?.Invoke(this);
CurrentPhase = Phase.Ended;
await _cs.AddAsync(_players[0].Value.UserId, "Connect4-refund", _options.Bet, true).ConfigureAwait(false);
return;
}
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
}
finally { _locker.Release(); }
});
}, null, TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
var __ = OnGameStateUpdated?.Invoke(this);
return true;
}
finally { _locker.Release(); }
}
public async Task<bool> Join(ulong userId, string userName, int bet)
public async Task<bool> Input(ulong userId, int inputCol)
{
await _locker.WaitAsync().ConfigureAwait(false);
try
{
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (CurrentPhase != Phase.Joining) //can't join if its not a joining phase
return false;
inputCol -= 1;
if (CurrentPhase == Phase.Ended || CurrentPhase == Phase.Joining)
return false;
if (_players[0].Value.UserId == userId) // same user can't join own game
return false;
if (!((_players[0].Value.UserId == userId && CurrentPhase == Phase.P1Move)
|| (_players[1].Value.UserId == userId && CurrentPhase == Phase.P2Move)))
return false;
if (bet != _options.Bet) // can't join if bet amount is not the same
return false;
if (inputCol < 0 || inputCol > NumberOfColumns) //invalid input
return false;
if (!await _cs.RemoveAsync(userId, "Connect4-bet", bet, true).ConfigureAwait(false)) // user doesn't have enough money to gamble
return false;
if (IsColumnFull(inputCol)) //can't play there event?
return false;
if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player
{
_players[1] = _players[0];
_players[0] = (userId, userName);
}
else //else join as a second player
_players[1] = (userId, userName);
CurrentPhase = Phase.P1Move; //start the game
_playerTimeoutTimer = new Timer(async state =>
{
await _locker.WaitAsync().ConfigureAwait(false);
try
{
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
}
finally { _locker.Release(); }
}, null, TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
var __ = OnGameStateUpdated?.Invoke(this);
return true;
}
finally { _locker.Release(); }
}
public async Task<bool> Input(ulong userId, int inputCol)
{
await _locker.WaitAsync().ConfigureAwait(false);
try
{
inputCol -= 1;
if (CurrentPhase == Phase.Ended || CurrentPhase == Phase.Joining)
return false;
if (!((_players[0].Value.UserId == userId && CurrentPhase == Phase.P1Move)
|| (_players[1].Value.UserId == userId && CurrentPhase == Phase.P2Move)))
return false;
if (inputCol < 0 || inputCol > NumberOfColumns) //invalid input
return false;
if (IsColumnFull(inputCol)) //can't play there event?
return false;
var start = NumberOfRows * inputCol;
for (int 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
for (int i = 0; i < NumberOfRows - 3; i++)
{
if (CurrentPhase == Phase.Ended)
break;
for (int j = 0; j < NumberOfColumns; j++)
{
if (CurrentPhase == Phase.Ended)
break;
var first = _gameState[i + j * NumberOfRows];
if (first != Field.Empty)
{
for (int k = 1; k < 4; k++)
{
var next = _gameState[i + k + j * NumberOfRows];
if (next == first)
{
if (k == 3)
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
else
continue;
}
else break;
}
}
}
}
// i'll go [0-1] in columns (and through all rows) and check to the right if 4 are connected
for (int i = 0; i < NumberOfColumns - 3; i++)
{
if (CurrentPhase == Phase.Ended)
break;
for (int j = 0; j < NumberOfRows; j++)
{
if (CurrentPhase == Phase.Ended)
break;
var first = _gameState[j + i * NumberOfRows];
if (first != Field.Empty)
{
for (int k = 1; k < 4; k++)
{
var next = _gameState[j + (i + k) * NumberOfRows];
if (next == first)
if (k == 3)
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
else
continue;
else break;
}
}
}
}
//need to check diagonal now
for (int col = 0; col < NumberOfColumns; col++)
{
if (CurrentPhase == Phase.Ended)
break;
for (int row = 0; row < NumberOfRows; row++)
{
if (CurrentPhase == Phase.Ended)
break;
var first = _gameState[row + col * NumberOfRows];
if (first != Field.Empty)
{
var same = 1;
//top left
for (int i = 1; i < 4; i++)
{
//while going top left, rows are increasing, columns are decreasing
var curRow = row + i;
var curCol = col - i;
//check if current values are in range
if (curRow >= NumberOfRows || curRow < 0)
break;
if (curCol < 0 || curCol >= NumberOfColumns)
break;
var cur = _gameState[curRow + curCol * NumberOfRows];
if (cur == first)
same++;
else break;
}
if (same == 4)
{
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
break;
}
same = 1;
//top right
for (int i = 1; i < 4; i++)
{
//while going top right, rows are increasing, columns are increasing
var curRow = row + i;
var curCol = col + i;
//check if current values are in range
if (curRow >= NumberOfRows || curRow < 0)
break;
if (curCol < 0 || curCol >= NumberOfColumns)
break;
var cur = _gameState[curRow + curCol * NumberOfRows];
if (cur == first)
same++;
else break;
}
if (same == 4)
{
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
break;
}
}
}
}
//check draw? if it's even possible
if (_gameState.All(x => x != Field.Empty))
{
EndGame(Result.Draw, null);
}
if (CurrentPhase != Phase.Ended)
{
if (CurrentPhase == Phase.P1Move)
CurrentPhase = Phase.P2Move;
else
CurrentPhase = Phase.P1Move;
ResetTimer();
}
var _ = OnGameStateUpdated?.Invoke(this);
return true;
}
finally { _locker.Release(); }
}
private void ResetTimer()
{
_playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
}
private void EndGame(Result result, ulong? winId)
{
if (CurrentPhase == Phase.Ended)
return;
var _ = OnGameEnded?.Invoke(this, result);
CurrentPhase = Phase.Ended;
if (result == Result.Draw)
{
_cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", this._options.Bet, true);
_cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", this._options.Bet, true);
return;
}
if (winId != null)
_cs.AddAsync(winId.Value, "Connnect4-win", (long)(this._options.Bet * 1.98), true);
}
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;
var start = NumberOfRows * inputCol;
for (int i = start; i < start + NumberOfRows; i++)
{
if (_gameState[i] == Field.Empty)
return false;
{
_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
for (int i = 0; i < NumberOfRows - 3; i++)
{
if (CurrentPhase == Phase.Ended)
break;
for (int j = 0; j < NumberOfColumns; j++)
{
if (CurrentPhase == Phase.Ended)
break;
var first = _gameState[i + j * NumberOfRows];
if (first != Field.Empty)
{
for (int k = 1; k < 4; k++)
{
var next = _gameState[i + k + j * NumberOfRows];
if (next == first)
{
if (k == 3)
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
else
continue;
}
else break;
}
}
}
}
// i'll go [0-1] in columns (and through all rows) and check to the right if 4 are connected
for (int i = 0; i < NumberOfColumns - 3; i++)
{
if (CurrentPhase == Phase.Ended)
break;
for (int j = 0; j < NumberOfRows; j++)
{
if (CurrentPhase == Phase.Ended)
break;
var first = _gameState[j + i * NumberOfRows];
if (first != Field.Empty)
{
for (int k = 1; k < 4; k++)
{
var next = _gameState[j + (i + k) * NumberOfRows];
if (next == first)
if (k == 3)
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
else
continue;
else break;
}
}
}
}
//need to check diagonal now
for (int col = 0; col < NumberOfColumns; col++)
{
if (CurrentPhase == Phase.Ended)
break;
for (int row = 0; row < NumberOfRows; row++)
{
if (CurrentPhase == Phase.Ended)
break;
var first = _gameState[row + col * NumberOfRows];
if (first != Field.Empty)
{
var same = 1;
//top left
for (int i = 1; i < 4; i++)
{
//while going top left, rows are increasing, columns are decreasing
var curRow = row + i;
var curCol = col - i;
//check if current values are in range
if (curRow >= NumberOfRows || curRow < 0)
break;
if (curCol < 0 || curCol >= NumberOfColumns)
break;
var cur = _gameState[curRow + curCol * NumberOfRows];
if (cur == first)
same++;
else break;
}
if (same == 4)
{
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
break;
}
same = 1;
//top right
for (int i = 1; i < 4; i++)
{
//while going top right, rows are increasing, columns are increasing
var curRow = row + i;
var curCol = col + i;
//check if current values are in range
if (curRow >= NumberOfRows || curRow < 0)
break;
if (curCol < 0 || curCol >= NumberOfColumns)
break;
var cur = _gameState[curRow + curCol * NumberOfRows];
if (cur == first)
same++;
else break;
}
if (same == 4)
{
EndGame(Result.CurrentPlayerWon, CurrentPlayer.UserId);
break;
}
}
}
}
//check draw? if it's even possible
if (_gameState.All(x => x != Field.Empty))
{
EndGame(Result.Draw, null);
}
if (CurrentPhase != Phase.Ended)
{
if (CurrentPhase == Phase.P1Move)
CurrentPhase = Phase.P2Move;
else
CurrentPhase = Phase.P1Move;
ResetTimer();
}
var _ = OnGameStateUpdated?.Invoke(this);
return true;
}
public void Dispose()
{
OnGameFailedToStart = null;
OnGameStateUpdated = null;
OnGameEnded = null;
_playerTimeoutTimer?.Change(Timeout.Infinite, Timeout.Infinite);
}
public class Options : INadekoCommandOptions
{
public void NormalizeOptions()
{
if (TurnTimer < 5 || TurnTimer > 60)
TurnTimer = 15;
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;
}
finally { _locker.Release(); }
}
}
private void ResetTimer()
{
_playerTimeoutTimer.Change(TimeSpan.FromSeconds(_options.TurnTimer), TimeSpan.FromSeconds(_options.TurnTimer));
}
private void EndGame(Result result, ulong? winId)
{
if (CurrentPhase == Phase.Ended)
return;
var _ = OnGameEnded?.Invoke(this, result);
CurrentPhase = Phase.Ended;
if (result == Result.Draw)
{
_cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", this._options.Bet, true);
_cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", this._options.Bet, true);
return;
}
if (winId != null)
_cs.AddAsync(winId.Value, "Connnect4-win", (long)(this._options.Bet * 1.98), true);
}
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 (int i = start; i < start + NumberOfRows; i++)
{
if (_gameState[i] == Field.Empty)
return false;
}
return true;
}
public void Dispose()
{
OnGameFailedToStart = null;
OnGameStateUpdated = null;
OnGameEnded = null;
_playerTimeoutTimer?.Change(Timeout.Infinite, Timeout.Infinite);
}
public class Options : INadekoCommandOptions
{
public void NormalizeOptions()
{
if (TurnTimer < 5 || TurnTimer > 60)
TurnTimer = 15;
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

@@ -6,204 +6,202 @@ using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Common.Connect4;
using NadekoBot.Modules.Gambling.Services;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NadekoBot.Common;
using NadekoBot.Services;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
[Group]
public class Connect4Commands : GamblingSubmodule<GamblingService>
{
[Group]
public class Connect4Commands : GamblingSubmodule<GamblingService>
private readonly DiscordSocketClient _client;
private readonly ICurrencyService _cs;
private static readonly string[] numbers = new string[] { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:" };
public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb)
: base(gamb)
{
private readonly DiscordSocketClient _client;
private readonly ICurrencyService _cs;
private static readonly string[] numbers = new string[] { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:" };
_client = client;
_cs = cs;
}
public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb)
: base(gamb)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[NadekoOptionsAttribute(typeof(Connect4Game.Options))]
public async Task Connect4(params string[] args)
{
var (options, _) = OptionsParser.ParseFrom(new Connect4Game.Options(), args);
if (!await CheckBetOptional(options.Bet).ConfigureAwait(false))
return;
var newGame = new Connect4Game(ctx.User.Id, ctx.User.ToString(), options, _cs);
Connect4Game game;
if ((game = _service.Connect4Games.GetOrAdd(ctx.Channel.Id, newGame)) != newGame)
{
_client = client;
_cs = cs;
if (game.CurrentPhase != Connect4Game.Phase.Joining)
return;
newGame.Dispose();
//means game already exists, try to join
var joined = await game.Join(ctx.User.Id, ctx.User.ToString(), options.Bet).ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[NadekoOptionsAttribute(typeof(Connect4Game.Options))]
public async Task Connect4(params string[] args)
if (options.Bet > 0)
{
var (options, _) = OptionsParser.ParseFrom(new Connect4Game.Options(), args);
if (!await CheckBetOptional(options.Bet).ConfigureAwait(false))
return;
var newGame = new Connect4Game(ctx.User.Id, ctx.User.ToString(), options, _cs);
Connect4Game game;
if ((game = _service.Connect4Games.GetOrAdd(ctx.Channel.Id, newGame)) != newGame)
if (!await _cs.RemoveAsync(ctx.User.Id, "Connect4-bet", options.Bet, true).ConfigureAwait(false))
{
if (game.CurrentPhase != Connect4Game.Phase.Joining)
return;
newGame.Dispose();
//means game already exists, try to join
var joined = await game.Join(ctx.User.Id, ctx.User.ToString(), options.Bet).ConfigureAwait(false);
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
_service.Connect4Games.TryRemove(ctx.Channel.Id, out _);
game.Dispose();
return;
}
}
if (options.Bet > 0)
{
if (!await _cs.RemoveAsync(ctx.User.Id, "Connect4-bet", options.Bet, true).ConfigureAwait(false))
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
_service.Connect4Games.TryRemove(ctx.Channel.Id, out _);
game.Dispose();
return;
}
}
game.OnGameStateUpdated += Game_OnGameStateUpdated;
game.OnGameFailedToStart += Game_OnGameFailedToStart;
game.OnGameEnded += Game_OnGameEnded;
_client.MessageReceived += _client_MessageReceived;
game.OnGameStateUpdated += Game_OnGameStateUpdated;
game.OnGameFailedToStart += Game_OnGameFailedToStart;
game.OnGameEnded += Game_OnGameEnded;
_client.MessageReceived += _client_MessageReceived;
game.Initialize();
if (options.Bet == 0)
{
await ReplyConfirmLocalizedAsync(strs.connect4_created).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync(strs.connect4_created_bet(options.Bet + CurrencySign));
}
game.Initialize();
if (options.Bet == 0)
{
await ReplyConfirmLocalizedAsync(strs.connect4_created).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync(strs.connect4_created_bet(options.Bet + CurrencySign));
}
Task _client_MessageReceived(SocketMessage arg)
{
if (ctx.Channel.Id != arg.Channel.Id)
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
bool success = false;
if (int.TryParse(arg.Content, out var col))
{
success = await game.Input(arg.Author.Id, col).ConfigureAwait(false);
}
if (success)
try { await arg.DeleteAsync().ConfigureAwait(false); } catch { }
else
{
if (game.CurrentPhase == Connect4Game.Phase.Joining
|| game.CurrentPhase == Connect4Game.Phase.Ended)
{
return;
}
RepostCounter++;
if (RepostCounter == 0)
try { msg = await ctx.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()).ConfigureAwait(false); } catch { }
}
});
Task _client_MessageReceived(SocketMessage arg)
{
if (ctx.Channel.Id != arg.Channel.Id)
return Task.CompletedTask;
}
Task Game_OnGameFailedToStart(Connect4Game arg)
var _ = Task.Run(async () =>
{
if (_service.Connect4Games.TryRemove(ctx.Channel.Id, out var toDispose))
bool success = false;
if (int.TryParse(arg.Content, out var col))
{
_client.MessageReceived -= _client_MessageReceived;
toDispose.Dispose();
}
return ErrorLocalizedAsync(strs.connect4_failed_to_start);
}
Task Game_OnGameEnded(Connect4Game arg, Connect4Game.Result result)
{
if (_service.Connect4Games.TryRemove(ctx.Channel.Id, out var toDispose))
{
_client.MessageReceived -= _client_MessageReceived;
toDispose.Dispose();
success = await game.Input(arg.Author.Id, col).ConfigureAwait(false);
}
string title;
if (result == Connect4Game.Result.CurrentPlayerWon)
{
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)));
}
if (success)
try { await arg.DeleteAsync().ConfigureAwait(false); } catch { }
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 < 0 || value > 7)
_repostCounter = 0;
else _repostCounter = value;
}
}
private async Task Game_OnGameStateUpdated(Connect4Game game)
{
var embed = _eb.Create()
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
.WithDescription(GetGameStateText(game))
.WithOkColor();
if (msg is null)
msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
else
await msg.ModifyAsync(x => x.Embed = embed.Build()).ConfigureAwait(false);
}
private string GetGameStateText(Connect4Game game)
{
var sb = new StringBuilder();
if (game.CurrentPhase == Connect4Game.Phase.P1Move ||
game.CurrentPhase == Connect4Game.Phase.P2Move)
sb.AppendLine(GetText(strs.connect4_player_to_move(Format.Bold(game.CurrentPlayer.Username))));
for (int i = Connect4Game.NumberOfRows; i > 0; i--)
{
for (int j = 0; j < Connect4Game.NumberOfColumns; j++)
{
var cur = game.GameState[i + (j * Connect4Game.NumberOfRows) - 1];
if (cur == Connect4Game.Field.Empty)
sb.Append("⚫"); //black circle
else if (cur == Connect4Game.Field.P1)
sb.Append("🔴"); //red circle
else
sb.Append("🔵"); //blue circle
if (game.CurrentPhase == Connect4Game.Phase.Joining
|| game.CurrentPhase == Connect4Game.Phase.Ended)
{
return;
}
RepostCounter++;
if (RepostCounter == 0)
try { msg = await ctx.Channel.SendMessageAsync("", embed: (Embed)msg.Embeds.First()).ConfigureAwait(false); } catch { }
}
sb.AppendLine();
});
return Task.CompletedTask;
}
Task Game_OnGameFailedToStart(Connect4Game arg)
{
if (_service.Connect4Games.TryRemove(ctx.Channel.Id, out var toDispose))
{
_client.MessageReceived -= _client_MessageReceived;
toDispose.Dispose();
}
return ErrorLocalizedAsync(strs.connect4_failed_to_start);
}
Task Game_OnGameEnded(Connect4Game arg, Connect4Game.Result result)
{
if (_service.Connect4Games.TryRemove(ctx.Channel.Id, out var toDispose))
{
_client.MessageReceived -= _client_MessageReceived;
toDispose.Dispose();
}
for (int i = 0; i < Connect4Game.NumberOfColumns; i++)
string title;
if (result == Connect4Game.Result.CurrentPlayerWon)
{
sb.Append(numbers[i]);
title = GetText(strs.connect4_won(Format.Bold(arg.CurrentPlayer.Username), Format.Bold(arg.OtherPlayer.Username)));
}
return sb.ToString();
else if (result == Connect4Game.Result.OtherPlayerWon)
{
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 < 0 || value > 7)
_repostCounter = 0;
else _repostCounter = value;
}
}
private async Task Game_OnGameStateUpdated(Connect4Game game)
{
var embed = _eb.Create()
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
.WithDescription(GetGameStateText(game))
.WithOkColor();
if (msg is null)
msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
else
await msg.ModifyAsync(x => x.Embed = embed.Build()).ConfigureAwait(false);
}
private string GetGameStateText(Connect4Game game)
{
var sb = new StringBuilder();
if (game.CurrentPhase == Connect4Game.Phase.P1Move ||
game.CurrentPhase == Connect4Game.Phase.P2Move)
sb.AppendLine(GetText(strs.connect4_player_to_move(Format.Bold(game.CurrentPlayer.Username))));
for (int i = Connect4Game.NumberOfRows; i > 0; i--)
{
for (int j = 0; j < Connect4Game.NumberOfColumns; j++)
{
var cur = game.GameState[i + (j * Connect4Game.NumberOfRows) - 1];
if (cur == Connect4Game.Field.Empty)
sb.Append("⚫"); //black circle
else if (cur == Connect4Game.Field.P1)
sb.Append("🔴"); //red circle
else
sb.Append("🔵"); //blue circle
}
sb.AppendLine();
}
for (int i = 0; i < Connect4Game.NumberOfColumns; i++)
{
sb.Append(numbers[i]);
}
return sb.ToString();
}
}
}
}

View File

@@ -5,80 +5,78 @@ using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Gambling.Common.Events;
using System;
using NadekoBot.Common;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Gambling.Common;
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
[Group]
public class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
{
public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf)
{
}
namespace NadekoBot.Modules.Gambling;
[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,
public partial class Gambling
{
[Group]
public class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
{
public CurrencyEventsCommands(GamblingConfigService gamblingConf) : base(gamblingConf)
{
}
[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))
{
await ReplyErrorLocalizedAsync(strs.start_event_fail).ConfigureAwait(false);
}
}
private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
{
return 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))),
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))),
_ => 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));
}
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));
await ReplyErrorLocalizedAsync(strs.start_event_fail).ConfigureAwait(false);
}
}
private IEmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
{
return 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))),
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))),
_ => 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));
}
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));
}
}
}
}

View File

@@ -2,59 +2,56 @@
using NadekoBot.Modules.Gambling.Services;
using System.Threading.Tasks;
using Discord;
using NadekoBot.Extensions;
using System.Linq;
using Discord.Commands;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Common;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
public class CurrencyRaffleCommands : GamblingSubmodule<CurrencyRaffleService>
{
public class CurrencyRaffleCommands : GamblingSubmodule<CurrencyRaffleService>
public enum Mixed { Mixed }
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
{
public enum Mixed { Mixed }
}
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public Task RaffleCur(Mixed _, ShmartNumber amount) =>
RaffleCur(amount, true);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task RaffleCur(ShmartNumber amount, bool mixed = false)
{
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
return;
async Task OnEnded(IUser arg, long won)
{
await SendConfirmAsync(GetText(strs.rafflecur_ended(CurrencyName, Format.Bold(arg.ToString()), won + CurrencySign)));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public Task RaffleCur(Mixed _, ShmartNumber amount) =>
RaffleCur(amount, true);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task RaffleCur(ShmartNumber amount, bool mixed = false)
{
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
return;
async Task OnEnded(IUser arg, long won)
{
await SendConfirmAsync(GetText(strs.rafflecur_ended(CurrencyName, Format.Bold(arg.ToString()), won + CurrencySign)));
}
var res = await _service.JoinOrCreateGame(ctx.Channel.Id,
var res = await _service.JoinOrCreateGame(ctx.Channel.Id,
ctx.User, amount, mixed, OnEnded)
.ConfigureAwait(false);
.ConfigureAwait(false);
if (res.Item1 != null)
{
await SendConfirmAsync(GetText(strs.rafflecur(res.Item1.GameType.ToString())),
string.Join("\n", res.Item1.Users.Select(x => $"{x.DiscordUser} ({x.Amount})")),
footer: GetText(strs.rafflecur_joined(ctx.User.ToString())));
}
else
{
if (res.Item2 == CurrencyRaffleService.JoinErrorType.AlreadyJoinedOrInvalidAmount)
await ReplyErrorLocalizedAsync(strs.rafflecur_already_joined).ConfigureAwait(false);
else if (res.Item2 == CurrencyRaffleService.JoinErrorType.NotEnoughCurrency)
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
if (res.Item1 != null)
{
await SendConfirmAsync(GetText(strs.rafflecur(res.Item1.GameType.ToString())),
string.Join("\n", res.Item1.Users.Select(x => $"{x.DiscordUser} ({x.Amount})")),
footer: GetText(strs.rafflecur_joined(ctx.User.ToString())));
}
else
{
if (res.Item2 == CurrencyRaffleService.JoinErrorType.AlreadyJoinedOrInvalidAmount)
await ReplyErrorLocalizedAsync(strs.rafflecur_already_joined).ConfigureAwait(false);
else if (res.Item2 == CurrencyRaffleService.JoinErrorType.NotEnoughCurrency)
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
}
}
}
}

View File

@@ -5,230 +5,226 @@ using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NadekoBot.Services;
using Image = SixLabors.ImageSharp.Image;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
[Group]
public class DiceRollCommands : NadekoSubmodule
{
[Group]
public class DiceRollCommands : NadekoSubmodule
private static readonly Regex dndRegex = new Regex(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
private static readonly Regex fudgeRegex = new Regex(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
private static readonly char[] _fateRolls = { '-', ' ', '+' };
private readonly IImageCache _images;
public DiceRollCommands(IDataCache data)
{
private static readonly Regex dndRegex = new Regex(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
private static readonly Regex fudgeRegex = new Regex(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
_images = data.LocalImages;
}
private static readonly char[] _fateRolls = { '-', ' ', '+' };
private readonly IImageCache _images;
[NadekoCommand, Aliases]
public async Task Roll()
{
var rng = new NadekoRandom();
var gen = rng.Next(1, 101);
public DiceRollCommands(IDataCache data)
var num1 = gen / 10;
var num2 = gen % 10;
using (var img1 = GetDice(num1))
using (var img2 = GetDice(num2))
using (var img = new[] { img1, img2 }.Merge(out var format))
using (var ms = img.ToStream(format))
{
_images = data.LocalImages;
await ctx.Channel.SendFileAsync(ms,
$"dice.{format.FileExtensions.First()}",
Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
}
}
[NadekoCommand, Aliases]
[Priority(1)]
public async Task Roll(int num)
{
await InternalRoll(num, true).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[Priority(1)]
public async Task Rolluo(int num = 1)
{
await InternalRoll(num, false).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[Priority(0)]
public async Task Roll(string arg)
{
await InternallDndRoll(arg, true).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[Priority(0)]
public async Task Rolluo(string arg)
{
await InternallDndRoll(arg, false).ConfigureAwait(false);
}
private async Task InternalRoll(int num, bool ordered)
{
if (num < 1 || num > 30)
{
await ReplyErrorLocalizedAsync(strs.dice_invalid_number(1, 30));
return;
}
[NadekoCommand, Aliases]
public async Task Roll()
var rng = new NadekoRandom();
var dice = new List<Image<Rgba32>>(num);
var values = new List<int>(num);
for (var i = 0; i < num; i++)
{
var rng = new NadekoRandom();
var gen = rng.Next(1, 101);
var num1 = gen / 10;
var num2 = gen % 10;
using (var img1 = GetDice(num1))
using (var img2 = GetDice(num2))
using (var img = new[] { img1, img2 }.Merge(out var format))
using (var ms = img.ToStream(format))
var randomNumber = rng.Next(1, 7);
var toInsert = dice.Count;
if (ordered)
{
await ctx.Channel.SendFileAsync(ms,
$"dice.{format.FileExtensions.First()}",
Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
}
}
[NadekoCommand, Aliases]
[Priority(1)]
public async Task Roll(int num)
{
await InternalRoll(num, true).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[Priority(1)]
public async Task Rolluo(int num = 1)
{
await InternalRoll(num, false).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[Priority(0)]
public async Task Roll(string arg)
{
await InternallDndRoll(arg, true).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[Priority(0)]
public async Task Rolluo(string arg)
{
await InternallDndRoll(arg, false).ConfigureAwait(false);
}
private async Task InternalRoll(int num, bool ordered)
{
if (num < 1 || num > 30)
{
await ReplyErrorLocalizedAsync(strs.dice_invalid_number(1, 30));
return;
}
var rng = new NadekoRandom();
var dice = new List<Image<Rgba32>>(num);
var values = new List<int>(num);
for (var i = 0; i < num; i++)
{
var randomNumber = rng.Next(1, 7);
var toInsert = dice.Count;
if (ordered)
{
if (randomNumber == 6 || dice.Count == 0)
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))
using (var ms = bitmap.ToStream(format))
{
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()),
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 int n1) &&
n1 > 0 && n1 < 500)
{
var rng = new NadekoRandom();
var rolls = new List<char>();
for (int 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}]"))));
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
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 int n2) &&
n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
{
if (!int.TryParse(match.Groups["add"].Value, out int add))
add = 0;
if (!int.TryParse(match.Groups["sub"].Value, out int sub))
sub = 0;
var arr = new int[n1];
for (int i = 0; i < n1; i++)
if (randomNumber == 6 || dice.Count == 0)
toInsert = 0;
else if (randomNumber != 1)
for (var j = 0; j < dice.Count; j++)
{
arr[i] = rng.Next(1, n2 + 1);
if (values[j] < randomNumber)
{
toInsert = j;
break;
}
}
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));
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
}
}
[NadekoCommand, Aliases]
public async Task NRoll([Leftover] string range)
{
int rolled;
if (range.Contains("-"))
{
var arr = range.Split('-')
.Take(2)
.Select(int.Parse)
.ToArray();
if (arr[0] > arr[1])
{
await ReplyErrorLocalizedAsync(strs.second_larger_than_first).ConfigureAwait(false);
return;
}
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
}
else
{
rolled = new NadekoRandom().Next(0, int.Parse(range) + 1);
toInsert = dice.Count;
}
await ReplyConfirmLocalizedAsync(strs.dice_rolled(Format.Bold(rolled.ToString()))).ConfigureAwait(false);
dice.Insert(toInsert, GetDice(randomNumber));
values.Insert(toInsert, randomNumber);
}
private Image<Rgba32> GetDice(int num)
using (var bitmap = dice.Merge(out var format))
using (var ms = bitmap.ToStream(format))
{
if (num < 0 || num > 10)
throw new ArgumentOutOfRangeException(nameof(num));
if (num == 10)
foreach (var d in dice)
{
var images = _images.Dice;
using (var imgOne = Image.Load(images[1]))
using (var imgZero = Image.Load(images[0]))
{
return new[] { imgOne, imgZero }.Merge();
}
d.Dispose();
}
return Image.Load(_images.Dice[num]);
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 int n1) &&
n1 > 0 && n1 < 500)
{
var rng = new NadekoRandom();
var rolls = new List<char>();
for (int 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}]"))));
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
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 int n2) &&
n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
{
if (!int.TryParse(match.Groups["add"].Value, out int add))
add = 0;
if (!int.TryParse(match.Groups["sub"].Value, out int sub))
sub = 0;
var arr = new int[n1];
for (int 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));
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
}
}
[NadekoCommand, Aliases]
public async Task NRoll([Leftover] string range)
{
int rolled;
if (range.Contains("-"))
{
var arr = range.Split('-')
.Take(2)
.Select(int.Parse)
.ToArray();
if (arr[0] > arr[1])
{
await ReplyErrorLocalizedAsync(strs.second_larger_than_first).ConfigureAwait(false);
return;
}
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
}
else
{
rolled = new NadekoRandom().Next(0, int.Parse(range) + 1);
}
await ReplyConfirmLocalizedAsync(strs.dice_rolled(Format.Bold(rolled.ToString()))).ConfigureAwait(false);
}
private Image<Rgba32> GetDice(int num)
{
if (num < 0 || num > 10)
throw new ArgumentOutOfRangeException(nameof(num));
if (num == 10)
{
var images = _images.Dice;
using (var imgOne = Image.Load(images[1]))
using (var imgZero = Image.Load(images[0]))
{
return new[] { imgOne, imgZero }.Merge();
}
}
return Image.Load(_images.Dice[num]);
}
}
}

View File

@@ -1,9 +1,7 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
@@ -13,112 +11,111 @@ using Image = SixLabors.ImageSharp.Image;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
[Group]
public class DrawCommands : NadekoSubmodule
{
[Group]
public class DrawCommands : NadekoSubmodule
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new ConcurrentDictionary<IGuild, Deck>();
private readonly IImageCache _images;
public DrawCommands(IDataCache data)
{
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new ConcurrentDictionary<IGuild, Deck>();
private readonly IImageCache _images;
_images = data.LocalImages;
}
public DrawCommands(IDataCache data)
private async Task<(Stream ImageStream, string ToSend)> InternalDraw(int num, ulong? guildId = null)
{
if (num < 1 || num > 10)
throw new ArgumentOutOfRangeException(nameof(num));
Deck cards = guildId is null ? new Deck() : _allDecks.GetOrAdd(ctx.Guild, (s) => new Deck());
var images = new List<Image<Rgba32>>();
var cardObjects = new List<Deck.Card>();
for (var i = 0; i < num; i++)
{
_images = data.LocalImages;
}
private async Task<(Stream ImageStream, string ToSend)> InternalDraw(int num, ulong? guildId = null)
{
if (num < 1 || num > 10)
throw new ArgumentOutOfRangeException(nameof(num));
Deck cards = guildId is null ? new Deck() : _allDecks.GetOrAdd(ctx.Guild, (s) => new Deck());
var images = new List<Image<Rgba32>>();
var cardObjects = new List<Deck.Card>();
for (var i = 0; i < num; i++)
if (cards.CardPool.Count == 0 && i != 0)
{
if (cards.CardPool.Count == 0 && i != 0)
try
{
try
{
await ReplyErrorLocalizedAsync(strs.no_more_cards).ConfigureAwait(false);
}
catch
{
// ignored
}
break;
await ReplyErrorLocalizedAsync(strs.no_more_cards).ConfigureAwait(false);
}
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)
catch
{
i.Dispose();
// ignored
}
var toSend = $"{Format.Bold(ctx.User.ToString())}";
if (cardObjects.Count == 5)
toSend += $" drew `{Deck.GetHandValue(cardObjects)}`";
if (guildId != null)
toSend += "\n" + GetText(strs.cards_left(Format.Bold(cards.CardPool.Count.ToString())));
return (img.ToStream(), toSend);
break;
}
var currentCard = cards.Draw();
cardObjects.Add(currentCard);
images.Add(Image.Load(_images.GetCard(currentCard.ToString().ToLowerInvariant().Replace(' ', '_'))));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Draw(int num = 1)
using (var img = images.Merge())
{
if (num < 1)
num = 1;
if (num > 10)
num = 10;
var (ImageStream, ToSend) = await InternalDraw(num, ctx.Guild.Id).ConfigureAwait(false);
using (ImageStream)
foreach (var i in images)
{
await ctx.Channel.SendFileAsync(ImageStream, num + " cards.jpg", ToSend).ConfigureAwait(false);
i.Dispose();
}
var toSend = $"{Format.Bold(ctx.User.ToString())}";
if (cardObjects.Count == 5)
toSend += $" drew `{Deck.GetHandValue(cardObjects)}`";
if (guildId != null)
toSend += "\n" + GetText(strs.cards_left(Format.Bold(cards.CardPool.Count.ToString())));
return (img.ToStream(), toSend);
}
}
[NadekoCommand, Aliases]
public async Task DrawNew(int num = 1)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Draw(int num = 1)
{
if (num < 1)
num = 1;
if (num > 10)
num = 10;
var (ImageStream, ToSend) = await InternalDraw(num, ctx.Guild.Id).ConfigureAwait(false);
using (ImageStream)
{
if (num < 1)
num = 1;
if (num > 10)
num = 10;
await ctx.Channel.SendFileAsync(ImageStream, num + " cards.jpg", ToSend).ConfigureAwait(false);
}
}
var (ImageStream, ToSend) = await InternalDraw(num).ConfigureAwait(false);
using (ImageStream)
[NadekoCommand, Aliases]
public async Task DrawNew(int num = 1)
{
if (num < 1)
num = 1;
if (num > 10)
num = 10;
var (ImageStream, ToSend) = await InternalDraw(num).ConfigureAwait(false);
using (ImageStream)
{
await ctx.Channel.SendFileAsync(ImageStream, num + " cards.jpg", ToSend).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task DeckShuffle()
{
//var channel = (ITextChannel)ctx.Channel;
_allDecks.AddOrUpdate(ctx.Guild,
(g) => new Deck(),
(g, c) =>
{
await ctx.Channel.SendFileAsync(ImageStream, num + " cards.jpg", ToSend).ConfigureAwait(false);
}
}
c.Restart();
return c;
});
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task DeckShuffle()
{
//var channel = (ITextChannel)ctx.Channel;
_allDecks.AddOrUpdate(ctx.Guild,
(g) => new Deck(),
(g, c) =>
{
c.Restart();
return c;
});
await ReplyConfirmLocalizedAsync(strs.deck_reshuffled).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.deck_reshuffled).ConfigureAwait(false);
}
}
}

View File

@@ -7,124 +7,121 @@ using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Services;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Services;
using Image = SixLabors.ImageSharp.Image;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
[Group]
public class FlipCoinCommands : GamblingSubmodule<GamblingService>
{
[Group]
public class FlipCoinCommands : GamblingSubmodule<GamblingService>
private readonly IImageCache _images;
private readonly ICurrencyService _cs;
private static readonly NadekoRandom rng = new NadekoRandom();
public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) : base(gss)
{
private readonly IImageCache _images;
private readonly ICurrencyService _cs;
private static readonly NadekoRandom rng = new NadekoRandom();
_images = data.LocalImages;
_cs = cs;
}
public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) : base(gss)
[NadekoCommand, Aliases]
public async Task Flip(int count = 1)
{
if (count > 10 || count < 1)
{
_images = data.LocalImages;
_cs = cs;
await ReplyErrorLocalizedAsync(strs.flip_invalid(10));
return;
}
[NadekoCommand, Aliases]
public async Task Flip(int count = 1)
var headCount = 0;
var tailCount = 0;
var imgs = new Image<Rgba32>[count];
for (var i = 0; i < count; i++)
{
if (count > 10 || count < 1)
var headsArr = _images.Heads[rng.Next(0, _images.Heads.Count)];
var tailsArr = _images.Tails[rng.Next(0, _images.Tails.Count)];
if (rng.Next(0, 10) < 5)
{
await ReplyErrorLocalizedAsync(strs.flip_invalid(10));
return;
}
var headCount = 0;
var tailCount = 0;
var imgs = new Image<Rgba32>[count];
for (var i = 0; i < count; i++)
{
var headsArr = _images.Heads[rng.Next(0, _images.Heads.Count)];
var tailsArr = _images.Tails[rng.Next(0, _images.Tails.Count)];
if (rng.Next(0, 10) < 5)
{
imgs[i] = Image.Load(headsArr);
headCount++;
}
else
{
imgs[i] = Image.Load(tailsArr);
tailCount++;
}
}
using (var img = imgs.Merge(out var format))
using (var stream = img.ToStream(format))
{
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))));
await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg).ConfigureAwait(false);
}
}
public enum BetFlipGuess
{
H = 1,
Head = 1,
Heads = 1,
T = 2,
Tail = 2,
Tails = 2
}
[NadekoCommand, Aliases]
public async Task Betflip(ShmartNumber amount, BetFlipGuess guess)
{
if (!await CheckBetMandatory(amount).ConfigureAwait(false) || amount == 1)
return;
var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, gamble: true).ConfigureAwait(false);
if (!removed)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
BetFlipGuess result;
Uri imageToSend;
var coins = _images.ImageUrls.Coins;
if (rng.Next(0, 1000) <= 499)
{
imageToSend = coins.Heads[rng.Next(0, coins.Heads.Length)];
result = BetFlipGuess.Heads;
imgs[i] = Image.Load(headsArr);
headCount++;
}
else
{
imageToSend = coins.Tails[rng.Next(0, coins.Tails.Length)];
result = BetFlipGuess.Tails;
imgs[i] = Image.Load(tailsArr);
tailCount++;
}
string str;
if (guess == result)
{
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).ConfigureAwait(false);
}
else
{
str = ctx.User.ToString() + " " + GetText(strs.better_luck);
}
await ctx.Channel.EmbedAsync(_eb.Create()
.WithDescription(str)
.WithOkColor()
.WithImageUrl(imageToSend.ToString())).ConfigureAwait(false);
}
using (var img = imgs.Merge(out var format))
using (var stream = img.ToStream(format))
{
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))));
await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg).ConfigureAwait(false);
}
}
public enum BetFlipGuess
{
H = 1,
Head = 1,
Heads = 1,
T = 2,
Tail = 2,
Tails = 2
}
[NadekoCommand, Aliases]
public async Task Betflip(ShmartNumber amount, BetFlipGuess guess)
{
if (!await CheckBetMandatory(amount).ConfigureAwait(false) || amount == 1)
return;
var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, gamble: true).ConfigureAwait(false);
if (!removed)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
BetFlipGuess result;
Uri imageToSend;
var coins = _images.ImageUrls.Coins;
if (rng.Next(0, 1000) <= 499)
{
imageToSend = coins.Heads[rng.Next(0, coins.Heads.Length)];
result = BetFlipGuess.Heads;
}
else
{
imageToSend = coins.Tails[rng.Next(0, coins.Tails.Length)];
result = BetFlipGuess.Tails;
}
string str;
if (guess == result)
{
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).ConfigureAwait(false);
}
else
{
str = ctx.User.ToString() + " " + GetText(strs.better_luck);
}
await ctx.Channel.EmbedAsync(_eb.Create()
.WithDescription(str)
.WithOkColor()
.WithImageUrl(imageToSend.ToString())).ConfigureAwait(false);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,121 +5,119 @@ using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
using NadekoBot.Modules.Gambling.Services;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Common;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
[Group]
public class PlantPickCommands : GamblingSubmodule<PlantPickService>
{
[Group]
public class PlantPickCommands : GamblingSubmodule<PlantPickService>
private readonly ILogCommandService logService;
public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss) : base(gss)
{
private readonly ILogCommandService logService;
this.logService = logService;
}
public PlantPickCommands(ILogCommandService logService, GamblingConfigService gss) : base(gss)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Pick(string pass = null)
{
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
{
this.logService = logService;
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Pick(string pass = null)
var picked = await _service.PickAsync(ctx.Guild.Id, (ITextChannel)ctx.Channel, ctx.User.Id, pass);
if (picked > 0)
{
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
{
return;
}
var picked = await _service.PickAsync(ctx.Guild.Id, (ITextChannel)ctx.Channel, ctx.User.Id, pass);
if (picked > 0)
{
var msg = await ReplyConfirmLocalizedAsync(strs.picked(picked + CurrencySign));
msg.DeleteAfter(10);
}
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
{
try
{
logService.AddDeleteIgnore(ctx.Message.Id);
await ctx.Message.DeleteAsync().ConfigureAwait(false);
}
catch { }
}
var msg = await ReplyConfirmLocalizedAsync(strs.picked(picked + CurrencySign));
msg.DeleteAfter(10);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Plant(ShmartNumber amount, string pass = null)
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
{
if (amount < 1)
return;
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
{
return;
}
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
try
{
logService.AddDeleteIgnore(ctx.Message.Id);
await ctx.Message.DeleteAsync().ConfigureAwait(false);
}
catch { }
}
}
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]
[RequireContext(ContextType.Guild)]
public async Task Plant(ShmartNumber amount, string pass = null)
{
if (amount < 1)
return;
if (!string.IsNullOrWhiteSpace(pass) && !pass.IsAlphaNumeric())
{
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
{
logService.AddDeleteIgnore(ctx.Message.Id);
await ctx.Message.DeleteAsync().ConfigureAwait(false);
}
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]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
#if GLOBAL_NADEKO
[OwnerOnly]
#endif
public async Task GenCurrency()
public async Task GenCurrency()
{
bool enabled = _service.ToggleCurrencyGeneration(ctx.Guild.Id, ctx.Channel.Id);
if (enabled)
{
bool enabled = _service.ToggleCurrencyGeneration(ctx.Guild.Id, ctx.Channel.Id);
if (enabled)
{
await ReplyConfirmLocalizedAsync(strs.curgen_enabled).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.curgen_disabled).ConfigureAwait(false);
}
await ReplyConfirmLocalizedAsync(strs.curgen_enabled).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[OwnerOnly]
public Task GenCurList(int page = 1)
else
{
if (--page < 0)
return Task.CompletedTask;
var enabledIn = _service.GetAllGeneratingChannels();
return ctx.SendPaginatedConfirmAsync(page, (cur) =>
{
var items = enabledIn.Skip(page * 9).Take(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);
await ReplyConfirmLocalizedAsync(strs.curgen_disabled).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[OwnerOnly]
public Task GenCurList(int page = 1)
{
if (--page < 0)
return Task.CompletedTask;
var enabledIn = _service.GetAllGeneratingChannels();
return ctx.SendPaginatedConfirmAsync(page, (cur) =>
{
var items = enabledIn.Skip(page * 9).Take(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

@@ -1,12 +1,10 @@
using System.Threading.Tasks;
using NadekoBot.Services;
using NadekoBot.Services;
using System.Collections.Concurrent;
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public class AnimalRaceService : INService
{
public class AnimalRaceService : INService
{
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
}
}
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
}

View File

@@ -2,10 +2,9 @@
using NadekoBot.Services;
using System.Collections.Concurrent;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public class BlackJackService : INService
{
public class BlackJackService : INService
{
public ConcurrentDictionary<ulong, Blackjack> Games { get; } = new ConcurrentDictionary<ulong, Blackjack>();
}
}
public ConcurrentDictionary<ulong, Blackjack> Games { get; } = new ConcurrentDictionary<ulong, Blackjack>();
}

View File

@@ -2,83 +2,77 @@
using NadekoBot.Modules.Gambling.Common.Events;
using System.Collections.Concurrent;
using NadekoBot.Modules.Gambling.Common;
using Discord;
using Discord.WebSocket;
using System.Threading.Tasks;
using System;
using NadekoBot.Services.Database.Models;
using System.Net.Http;
using NadekoBot.Modules.Gambling.Services;
using Serilog;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public class CurrencyEventsService : INService
{
public class CurrencyEventsService : INService
private readonly DiscordSocketClient _client;
private readonly ICurrencyService _cs;
private readonly GamblingConfigService _configService;
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events =
new ConcurrentDictionary<ulong, ICurrencyEvent>();
public CurrencyEventsService(
DiscordSocketClient client,
ICurrencyService cs,
GamblingConfigService configService)
{
private readonly DiscordSocketClient _client;
private readonly ICurrencyService _cs;
private readonly GamblingConfigService _configService;
_client = client;
_cs = cs;
_configService = configService;
}
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events =
new ConcurrentDictionary<ulong, ICurrencyEvent>();
public async Task<bool> TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type,
EventOptions opts, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embed)
{
SocketGuild g = _client.GetGuild(guildId);
SocketTextChannel ch = g?.GetChannel(channelId) as SocketTextChannel;
if (ch is null)
return false;
ICurrencyEvent ce;
public CurrencyEventsService(
DiscordSocketClient client,
ICurrencyService cs,
GamblingConfigService configService)
if (type == CurrencyEvent.Type.Reaction)
{
_client = client;
_cs = cs;
_configService = configService;
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;
}
public async Task<bool> TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type,
EventOptions opts, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embed)
var added = _events.TryAdd(guildId, ce);
if (added)
{
SocketGuild g = _client.GetGuild(guildId);
SocketTextChannel ch = g?.GetChannel(channelId) as SocketTextChannel;
if (ch is null)
return false;
ICurrencyEvent ce;
if (type == CurrencyEvent.Type.Reaction)
try
{
ce = new ReactionEvent(_client, _cs, g, ch, opts, _configService.Data, embed);
ce.OnEnded += OnEventEnded;
await ce.StartEvent().ConfigureAwait(false);
}
else if (type == CurrencyEvent.Type.GameStatus)
{
ce = new GameStatusEvent(_client, _cs, g, ch, opts, embed);
}
else
catch (Exception ex)
{
Log.Warning(ex, "Error starting event");
_events.TryRemove(guildId, out ce);
return false;
}
var added = _events.TryAdd(guildId, ce);
if (added)
{
try
{
ce.OnEnded += OnEventEnded;
await ce.StartEvent().ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex, "Error starting event");
_events.TryRemove(guildId, out ce);
return false;
}
}
return added;
}
private Task OnEventEnded(ulong gid)
{
_events.TryRemove(gid, out _);
return Task.CompletedTask;
}
return added;
}
private Task OnEventEnded(ulong gid)
{
_events.TryRemove(gid, out _);
return Task.CompletedTask;
}
}

View File

@@ -2,87 +2,83 @@
using NadekoBot.Services;
using NadekoBot.Modules.Gambling.Common;
using System.Threading;
using System.Linq;
using System.Collections.Generic;
using Discord;
using System;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public class CurrencyRaffleService : INService
{
public class CurrencyRaffleService : INService
public enum JoinErrorType
{
public enum JoinErrorType
{
NotEnoughCurrency,
AlreadyJoinedOrInvalidAmount
}
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
private readonly DbService _db;
private readonly ICurrencyService _cs;
NotEnoughCurrency,
AlreadyJoinedOrInvalidAmount
}
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
private readonly DbService _db;
private readonly ICurrencyService _cs;
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new Dictionary<ulong, CurrencyRaffleGame>();
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new Dictionary<ulong, CurrencyRaffleGame>();
public CurrencyRaffleService(DbService db, ICurrencyService cs)
{
_db = db;
_cs = cs;
}
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().ConfigureAwait(false);
try
{
await _locker.WaitAsync().ConfigureAwait(false);
try
var newGame = false;
if (!Games.TryGetValue(channelId, out var crg))
{
var newGame = false;
if (!Games.TryGetValue(channelId, out var crg))
{
newGame = true;
crg = new CurrencyRaffleGame(mixed
? CurrencyRaffleGame.Type.Mixed
: CurrencyRaffleGame.Type.Normal);
Games.Add(channelId, crg);
}
newGame = true;
crg = new CurrencyRaffleGame(mixed
? CurrencyRaffleGame.Type.Mixed
: CurrencyRaffleGame.Type.Normal);
Games.Add(channelId, crg);
}
//remove money, and stop the game if this
// user created it and doesn't have the money
if (!await _cs.RemoveAsync(user.Id, "Currency Raffle Join", amount).ConfigureAwait(false))
{
if (newGame)
Games.Remove(channelId);
return (null, JoinErrorType.NotEnoughCurrency);
}
if (!crg.AddUser(user, amount))
{
await _cs.AddAsync(user.Id, "Curency Raffle Refund", amount).ConfigureAwait(false);
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
}
//remove money, and stop the game if this
// user created it and doesn't have the money
if (!await _cs.RemoveAsync(user.Id, "Currency Raffle Join", amount).ConfigureAwait(false))
{
if (newGame)
{
var _t = Task.Run(async () =>
{
await Task.Delay(60000).ConfigureAwait(false);
await _locker.WaitAsync().ConfigureAwait(false);
try
{
var winner = crg.GetWinner();
var won = crg.Users.Sum(x => x.Amount);
Games.Remove(channelId);
return (null, JoinErrorType.NotEnoughCurrency);
}
await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win",
won).ConfigureAwait(false);
Games.Remove(channelId, out _);
var oe = onEnded(winner.DiscordUser, won);
}
catch { }
finally { _locker.Release(); }
});
}
return (crg, null);
}
finally
if (!crg.AddUser(user, amount))
{
_locker.Release();
await _cs.AddAsync(user.Id, "Curency Raffle Refund", amount).ConfigureAwait(false);
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
}
if (newGame)
{
var _t = Task.Run(async () =>
{
await Task.Delay(60000).ConfigureAwait(false);
await _locker.WaitAsync().ConfigureAwait(false);
try
{
var winner = crg.GetWinner();
var won = crg.Users.Sum(x => x.Amount);
await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win",
won).ConfigureAwait(false);
Games.Remove(channelId, out _);
var oe = onEnded(winner.DiscordUser, won);
}
catch { }
finally { _locker.Release(); }
});
}
return (crg, null);
}
finally
{
_locker.Release();
}
}
}

View File

@@ -1,85 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NadekoBot.Common;
using NadekoBot.Common;
using NadekoBot.Common.Configs;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
{
public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
{
public override string Name { get; } = "gambling";
private const string FilePath = "data/gambling.yml";
private static TypedKey<GamblingConfig> changeKey = new TypedKey<GamblingConfig>("config.gambling.updated");
public override string Name { get; } = "gambling";
private const string FilePath = "data/gambling.yml";
private static TypedKey<GamblingConfig> changeKey = new TypedKey<GamblingConfig>("config.gambling.updated");
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);
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("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 >= 0 && val <= 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 >= 0 && val <= 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("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 >= 0 && val <= 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 >= 0 && val <= 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();
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;
});
}
private readonly IEnumerable<WaifuItemModel> antiGiftSeed = new[]
if (_data.Version < 3)
{
new WaifuItemModel("🥀", 100, "WiltedRose", true),
new WaifuItemModel("✂️", 1000, "Haircut", true),
new WaifuItemModel("🧻", 10000, "ToiletPaper", true),
};
public void Migrate()
ModifyConfig(c =>
{
c.Version = 3;
c.VoteReward = 100;
});
}
if (_data.Version < 4)
{
if (_data.Version < 2)
ModifyConfig(c =>
{
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;
});
}
c.Version = 4;
});
}
}
}

View File

@@ -4,69 +4,63 @@ using NadekoBot.Services;
using NadekoBot.Modules.Gambling.Common.Connect4;
using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common;
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Common.Slot;
using NadekoBot.Modules.Gambling.Services;
using Serilog;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public class GamblingService : INService
{
public class GamblingService : INService
private readonly DbService _db;
private readonly ICurrencyService _cs;
private readonly Bot _bot;
private readonly DiscordSocketClient _client;
private readonly IDataCache _cache;
private readonly GamblingConfigService _gss;
public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new ConcurrentDictionary<(ulong, ulong), RollDuelGame>();
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new ConcurrentDictionary<ulong, Connect4Game>();
private readonly Timer _decayTimer;
public GamblingService(DbService db, Bot bot, ICurrencyService cs,
DiscordSocketClient client, IDataCache cache, GamblingConfigService gss)
{
private readonly DbService _db;
private readonly ICurrencyService _cs;
private readonly Bot _bot;
private readonly DiscordSocketClient _client;
private readonly IDataCache _cache;
private readonly GamblingConfigService _gss;
public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new ConcurrentDictionary<(ulong, ulong), RollDuelGame>();
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new ConcurrentDictionary<ulong, Connect4Game>();
private readonly Timer _decayTimer;
public GamblingService(DbService db, Bot bot, ICurrencyService cs,
DiscordSocketClient client, IDataCache cache, GamblingConfigService gss)
{
_db = db;
_cs = cs;
_bot = bot;
_client = client;
_cache = cache;
_gss = gss;
_db = db;
_cs = cs;
_bot = bot;
_client = client;
_cache = cache;
_gss = gss;
if (_bot.Client.ShardId == 0)
if (_bot.Client.ShardId == 0)
{
_decayTimer = new Timer(_ =>
{
_decayTimer = new Timer(_ =>
{
var config = _gss.Data;
var maxDecay = config.Decay.MaxDecay;
if (config.Decay.Percent <= 0 || config.Decay.Percent > 1 || maxDecay < 0)
return;
var config = _gss.Data;
var maxDecay = config.Decay.MaxDecay;
if (config.Decay.Percent <= 0 || config.Decay.Percent > 1 || maxDecay < 0)
return;
using (var uow = _db.GetDbContext())
{
var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
using (var uow = _db.GetDbContext())
{
var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval))
return;
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}");
Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% " +
$"| max: {maxDecay} " +
$"| threshold: {config.Decay.MinThreshold}");
if (maxDecay == 0)
maxDecay = int.MaxValue;
if (maxDecay == 0)
maxDecay = int.MaxValue;
uow.Database.ExecuteSqlInterpolated($@"
uow.Database.ExecuteSqlInterpolated($@"
UPDATE DiscordUser
SET CurrencyAmount=
CASE WHEN
@@ -78,99 +72,98 @@ SET CurrencyAmount=
END
WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentUser.Id};");
_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 SlotResponse
{
Error = GamblingError.NotEnough
};
}
var game = new SlotGame();
var result = game.Spin();
long won = 0;
if (result.Multiplier > 0)
{
won = (long)(result.Multiplier * amount);
await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true);
}
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);
_cache.SetLastCurrencyDecay();
uow.SaveChanges();
}
catch { }
}
decimal cash;
decimal onePercent;
decimal planted;
decimal waifus;
long bot;
using (var uow = _db.GetDbContext())
{
cash = uow.DiscordUser.GetTotalCurrency();
onePercent = uow.DiscordUser.GetTopOnePercentCurrency(_client.CurrentUser.Id);
planted = uow.PlantedCurrency.AsQueryable().Sum(x => x.Amount);
waifus = uow.WaifuInfo.GetTotalValue();
bot = uow.DiscordUser.GetUserCurrency(_client.CurrentUser.Id);
}
var result = new EconomyResult
{
Cash = cash,
Planted = planted,
Bot = bot,
Waifus = waifus,
OnePercent = onePercent,
};
_cache.SetEconomy(JsonConvert.SerializeObject(result));
return result;
}
public Task<WheelOfFortuneGame.Result> WheelOfFortuneSpinAsync(ulong userId, long bet)
{
return new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync();
}, 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 SlotResponse
{
Error = GamblingError.NotEnough
};
}
var game = new SlotGame();
var result = game.Spin();
long won = 0;
if (result.Multiplier > 0)
{
won = (long)(result.Multiplier * amount);
await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true);
}
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;
decimal planted;
decimal waifus;
long bot;
using (var uow = _db.GetDbContext())
{
cash = uow.DiscordUser.GetTotalCurrency();
onePercent = uow.DiscordUser.GetTopOnePercentCurrency(_client.CurrentUser.Id);
planted = uow.PlantedCurrency.AsQueryable().Sum(x => x.Amount);
waifus = uow.WaifuInfo.GetTotalValue();
bot = uow.DiscordUser.GetUserCurrency(_client.CurrentUser.Id);
}
var result = new EconomyResult
{
Cash = cash,
Planted = planted,
Bot = bot,
Waifus = waifus,
OnePercent = onePercent,
};
_cache.SetEconomy(JsonConvert.SerializeObject(result));
return result;
}
public Task<WheelOfFortuneGame.Result> WheelOfFortuneSpinAsync(ulong userId, long bet)
{
return new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync();
}
}

View File

@@ -1,43 +1,42 @@
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public interface IShopService
{
public interface IShopService
{
/// <summary>
/// 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>
/// <param name="newPrice">New item price</param>
/// <returns>Success status</returns>
Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice);
/// <summary>
/// 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>
/// <param name="newPrice">New item price</param>
/// <returns>Success status</returns>
Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice);
/// <summary>
/// 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>
/// <param name="newName">New item name</param>
/// <returns>Success status</returns>
Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName);
/// <summary>
/// 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>
/// <param name="newName">New item name</param>
/// <returns>Success status</returns>
Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName);
/// <summary>
/// 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>
/// <param name="index2">Second entry's index</param>
/// <returns>Whether swap was successful</returns>
Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2);
/// <summary>
/// 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>
/// <param name="index2">Second entry's index</param>
/// <returns>Whether swap was successful</returns>
Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2);
/// <summary>
/// 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);
}
/// <summary>
/// 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,5 +1,4 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.Collections;
using NadekoBot.Services;
@@ -7,102 +6,100 @@ using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Db;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public class ShopService : IShopService, INService
{
public class ShopService : IShopService, INService
private readonly DbService _db;
public ShopService(DbService db)
{
private readonly DbService _db;
_db = db;
}
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)
{
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
if (newPrice <= 0)
throw new ArgumentOutOfRangeException(nameof(newPrice));
public async Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice)
{
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
if (newPrice <= 0)
throw new ArgumentOutOfRangeException(nameof(newPrice));
using var uow = _db.GetDbContext();
var entries = GetEntriesInternal(uow, guildId);
using var uow = _db.GetDbContext();
var entries = GetEntriesInternal(uow, guildId);
if (index >= entries.Count)
return false;
if (index >= entries.Count)
return false;
entries[index].Price = newPrice;
await uow.SaveChangesAsync();
return true;
}
entries[index].Price = newPrice;
await uow.SaveChangesAsync();
return true;
}
public async Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName)
{
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
if (string.IsNullOrWhiteSpace(newName))
throw new ArgumentNullException(nameof(newName));
public async Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName)
{
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
if (string.IsNullOrWhiteSpace(newName))
throw new ArgumentNullException(nameof(newName));
using var uow = _db.GetDbContext();
var entries = GetEntriesInternal(uow, guildId);
using var uow = _db.GetDbContext();
var entries = GetEntriesInternal(uow, guildId);
if (index >= entries.Count)
return false;
if (index >= entries.Count)
return false;
entries[index].Name = newName.TrimTo(100);
await uow.SaveChangesAsync();
return true;
}
entries[index].Name = newName.TrimTo(100);
await uow.SaveChangesAsync();
return true;
}
public async Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2)
{
if (index1 < 0)
throw new ArgumentOutOfRangeException(nameof(index1));
if (index2 < 0)
throw new ArgumentOutOfRangeException(nameof(index2));
public async Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2)
{
if (index1 < 0)
throw new ArgumentOutOfRangeException(nameof(index1));
if (index2 < 0)
throw new ArgumentOutOfRangeException(nameof(index2));
using var uow = _db.GetDbContext();
var entries = GetEntriesInternal(uow, guildId);
using var uow = _db.GetDbContext();
var entries = GetEntriesInternal(uow, guildId);
if (index1 >= entries.Count || index2 >= entries.Count || index1 == index2)
return false;
if (index1 >= entries.Count || index2 >= entries.Count || index1 == index2)
return false;
entries[index1].Index = index2;
entries[index2].Index = index1;
entries[index1].Index = index2;
entries[index2].Index = index1;
await uow.SaveChangesAsync();
return true;
}
await uow.SaveChangesAsync();
return true;
}
public async Task<bool> MoveEntryAsync(ulong guildId, int fromIndex, int toIndex)
{
if (fromIndex < 0)
throw new ArgumentOutOfRangeException(nameof(fromIndex));
if (toIndex < 0)
throw new ArgumentOutOfRangeException(nameof(toIndex));
public async Task<bool> MoveEntryAsync(ulong guildId, int fromIndex, int toIndex)
{
if (fromIndex < 0)
throw new ArgumentOutOfRangeException(nameof(fromIndex));
if (toIndex < 0)
throw new ArgumentOutOfRangeException(nameof(toIndex));
using var uow = _db.GetDbContext();
var entries = GetEntriesInternal(uow, guildId);
using var uow = _db.GetDbContext();
var entries = GetEntriesInternal(uow, guildId);
if (fromIndex >= entries.Count || toIndex >= entries.Count || fromIndex == toIndex)
return false;
if (fromIndex >= entries.Count || toIndex >= entries.Count || fromIndex == toIndex)
return false;
var entry = entries[fromIndex];
entries.RemoveAt(fromIndex);
entries.Insert(toIndex, entry);
var entry = entries[fromIndex];
entries.RemoveAt(fromIndex);
entries.Insert(toIndex, entry);
await uow.SaveChangesAsync();
return true;
}
await uow.SaveChangesAsync();
return true;
}
}

View File

@@ -11,373 +11,369 @@ using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NadekoBot.Db;
using Image = SixLabors.ImageSharp.Image;
using Color = SixLabors.ImageSharp.Color;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public class PlantPickService : INService
{
public class PlantPickService : INService
private readonly DbService _db;
private readonly IBotStrings _strings;
private readonly IImageCache _images;
private readonly FontProvider _fonts;
private readonly ICurrencyService _cs;
private readonly CommandHandler _cmdHandler;
private readonly NadekoRandom _rng;
private readonly DiscordSocketClient _client;
private readonly GamblingConfigService _gss;
public readonly ConcurrentHashSet<ulong> _generationChannels = new ConcurrentHashSet<ulong>();
//channelId/last generation
public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new ConcurrentDictionary<ulong, DateTime>();
private readonly SemaphoreSlim pickLock = new SemaphoreSlim(1, 1);
public PlantPickService(DbService db, CommandHandler cmd, IBotStrings strings,
IDataCache cache, FontProvider fonts, ICurrencyService cs,
CommandHandler cmdHandler, DiscordSocketClient client, GamblingConfigService gss)
{
private readonly DbService _db;
private readonly IBotStrings _strings;
private readonly IImageCache _images;
private readonly FontProvider _fonts;
private readonly ICurrencyService _cs;
private readonly CommandHandler _cmdHandler;
private readonly NadekoRandom _rng;
private readonly DiscordSocketClient _client;
private readonly GamblingConfigService _gss;
_db = db;
_strings = strings;
_images = cache.LocalImages;
_fonts = fonts;
_cs = cs;
_cmdHandler = cmdHandler;
_rng = new NadekoRandom();
_client = client;
_gss = gss;
public readonly ConcurrentHashSet<ulong> _generationChannels = new ConcurrentHashSet<ulong>();
//channelId/last generation
public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new ConcurrentDictionary<ulong, DateTime>();
private readonly SemaphoreSlim pickLock = new SemaphoreSlim(1, 1);
public PlantPickService(DbService db, CommandHandler cmd, IBotStrings strings,
IDataCache cache, FontProvider fonts, ICurrencyService cs,
CommandHandler cmdHandler, DiscordSocketClient client, GamblingConfigService gss)
cmd.OnMessageNoTrigger += PotentialFlowerGeneration;
using (var uow = db.GetDbContext())
{
_db = db;
_strings = strings;
_images = cache.LocalImages;
_fonts = fonts;
_cs = cs;
_cmdHandler = cmdHandler;
_rng = new NadekoRandom();
_client = client;
_gss = gss;
cmd.OnMessageNoTrigger += PotentialFlowerGeneration;
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();
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 ConcurrentHashSet<ulong>(configs
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
}
_generationChannels = new ConcurrentHashSet<ulong>(configs
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
}
}
private string GetText(ulong gid, LocStr str)
=> _strings.GetText(str, gid);
private string GetText(ulong gid, LocStr str)
=> _strings.GetText(str, gid);
public bool ToggleCurrencyGeneration(ulong gid, ulong cid)
public bool ToggleCurrencyGeneration(ulong gid, ulong cid)
{
bool enabled;
using (var uow = _db.GetDbContext())
{
bool enabled;
using (var uow = _db.GetDbContext())
{
var guildConfig = uow.GuildConfigsForId(gid, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
var guildConfig = uow.GuildConfigsForId(gid, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
var toAdd = new GCChannelId() { ChannelId = cid };
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
{
guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
_generationChannels.Add(cid);
enabled = true;
}
else
{
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
if (toDelete != null)
{
uow.Remove(toDelete);
}
_generationChannels.TryRemove(cid);
enabled = false;
}
uow.SaveChanges();
var toAdd = new GCChannelId() { ChannelId = cid };
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
{
guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
_generationChannels.Add(cid);
enabled = true;
}
return enabled;
else
{
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
if (toDelete != null)
{
uow.Remove(toDelete);
}
_generationChannels.TryRemove(cid);
enabled = false;
}
uow.SaveChanges();
}
return enabled;
}
public IEnumerable<GuildConfigExtensions.GeneratingChannel> GetAllGeneratingChannels()
public IEnumerable<GuildConfigExtensions.GeneratingChannel> GetAllGeneratingChannels()
{
using (var uow = _db.GetDbContext())
{
using (var uow = _db.GetDbContext())
{
var chs = uow.GuildConfigs.GetGeneratingChannels();
return chs;
}
var chs = uow.GuildConfigs.GetGeneratingChannels();
return chs;
}
}
/// <summary>
/// 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>
public Stream GetRandomCurrencyImage(string pass, out string extension)
/// <summary>
/// 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>
public Stream GetRandomCurrencyImage(string pass, out string extension)
{
// get a random currency image bytes
var rng = new NadekoRandom();
var curImg = _images.Currency[rng.Next(0, _images.Currency.Count)];
if (string.IsNullOrWhiteSpace(pass))
{
// get a random currency image bytes
var rng = new NadekoRandom();
var curImg = _images.Currency[rng.Next(0, _images.Currency.Count)];
if (string.IsNullOrWhiteSpace(pass))
// determine the extension
using (var img = Image.Load(curImg, out var format))
{
// determine the extension
using (var img = Image.Load(curImg, out var format))
{
extension = format.FileExtensions.FirstOrDefault() ?? "png";
}
// return the image
return curImg.ToStream();
extension = format.FileExtensions.FirstOrDefault() ?? "png";
}
// get the image stream and extension
var (s, ext) = AddPassword(curImg, pass);
// set the out extension parameter to the extension we've got
extension = ext;
// return the image
return s;
return curImg.ToStream();
}
/// <summary>
/// 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>
/// <returns>Image with the password in the top left corner.</returns>
private (Stream, string) AddPassword(byte[] curImg, string pass)
// get the image stream and extension
var (s, ext) = AddPassword(curImg, pass);
// set the out extension parameter to the extension we've got
extension = ext;
// return the image
return s;
}
/// <summary>
/// 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>
/// <returns>Image with the password in the top left corner.</returns>
private (Stream, string) AddPassword(byte[] curImg, string pass)
{
// draw lower, it looks better
pass = pass.TrimTo(10, true).ToLowerInvariant();
using (var img = Image.Load<Rgba32>(curImg, out var format))
{
// draw lower, it looks better
pass = pass.TrimTo(10, true).ToLowerInvariant();
using (var img = Image.Load<Rgba32>(curImg, out var format))
// choose font size based on the image height, so that it's visible
var font = _fonts.NotoSans.CreateFont(img.Height / 12, FontStyle.Bold);
img.Mutate(x =>
{
// choose font size based on the image height, so that it's visible
var font = _fonts.NotoSans.CreateFont(img.Height / 12, FontStyle.Bold);
img.Mutate(x =>
{
// measure the size of the text to be drawing
var size = TextMeasurer.Measure(pass, new RendererOptions(font, new PointF(0, 0)));
// measure the size of the text to be drawing
var size = TextMeasurer.Measure(pass, new RendererOptions(font, new PointF(0, 0)));
// fill the background with black, add 5 pixels on each side to make it look better
x.FillPolygon(Color.ParseHex("00000080"),
new PointF(0, 0),
new PointF(size.Width + 5, 0),
new PointF(size.Width + 5, size.Height + 10),
new PointF(0, size.Height + 10));
// fill the background with black, add 5 pixels on each side to make it look better
x.FillPolygon(Color.ParseHex("00000080"),
new PointF(0, 0),
new PointF(size.Width + 5, 0),
new PointF(size.Width + 5, size.Height + 10),
new PointF(0, size.Height + 10));
// draw the password over the background
x.DrawText(pass,
font,
SixLabors.ImageSharp.Color.White,
new PointF(0, 0));
});
// return image as a stream for easy sending
return (img.ToStream(format), format.FileExtensions.FirstOrDefault() ?? "png");
}
}
private Task PotentialFlowerGeneration(IUserMessage imsg)
{
var msg = imsg as SocketUserMessage;
if (msg is null || msg.Author.IsBot)
return Task.CompletedTask;
if (!(imsg.Channel is ITextChannel channel))
return Task.CompletedTask;
if (!_generationChannels.Contains(channel.Id))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
var config = _gss.Data;
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
return;
var num = rng.Next(1, 101) + config.Generation.Chance * 100;
if (num > 100 && LastGenerations.TryUpdate(channel.Id, DateTime.UtcNow, lastGeneration))
{
var dropAmount = config.Generation.MinAmount;
var dropAmountMax = config.Generation.MaxAmount;
if (dropAmountMax > dropAmount)
dropAmount = new NadekoRandom().Next(dropAmount, dropAmountMax + 1);
if (dropAmount > 0)
{
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.curgen_pl(dropAmount, config.Currency.Sign))
+ " " + GetText(channel.GuildId, strs.pick_pl(prefix));
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
IUserMessage sent;
using (var stream = GetRandomCurrencyImage(pw, out var ext))
{
sent = await channel.SendFileAsync(stream, $"currency_image.{ext}", toSend).ConfigureAwait(false);
}
await AddPlantToDatabase(channel.GuildId,
channel.Id,
_client.CurrentUser.Id,
sent.Id,
dropAmount,
pw).ConfigureAwait(false);
}
}
}
catch
{
}
// draw the password over the background
x.DrawText(pass,
font,
SixLabors.ImageSharp.Color.White,
new PointF(0, 0));
});
// return image as a stream for easy sending
return (img.ToStream(format), format.FileExtensions.FirstOrDefault() ?? "png");
}
}
private Task PotentialFlowerGeneration(IUserMessage imsg)
{
var msg = imsg as SocketUserMessage;
if (msg is null || msg.Author.IsBot)
return Task.CompletedTask;
}
/// <summary>
/// Generate a hexadecimal string from 1000 to ffff.
/// </summary>
/// <returns>A hexadecimal string from 1000 to ffff</returns>
private string GenerateCurrencyPassword()
{
// generate a number from 1000 to ffff
var num = _rng.Next(4096, 65536);
// convert it to hexadecimal
return num.ToString("x4");
}
if (!(imsg.Channel is ITextChannel channel))
return Task.CompletedTask;
public async Task<long> PickAsync(ulong gid, ITextChannel ch, ulong uid, string pass)
if (!_generationChannels.Contains(channel.Id))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
await pickLock.WaitAsync();
try
{
long amount;
ulong[] ids;
using (var uow = _db.GetDbContext())
var config = _gss.Data;
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
return;
var num = rng.Next(1, 101) + config.Generation.Chance * 100;
if (num > 100 && LastGenerations.TryUpdate(channel.Id, DateTime.UtcNow, lastGeneration))
{
// this method will sum all plants with that password,
// remove them, and get messageids of the removed plants
var dropAmount = config.Generation.MinAmount;
var dropAmountMax = config.Generation.MaxAmount;
pass = pass?.Trim().TrimTo(10, hideDots: 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();
// 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();
// remove them from the database
uow.RemoveRange(entries);
if (dropAmountMax > dropAmount)
dropAmount = new NadekoRandom().Next(dropAmount, dropAmountMax + 1);
if (amount > 0)
if (dropAmount > 0)
{
// give the picked currency to the user
await _cs.AddAsync(uid, "Picked currency", amount, gamble: false);
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.curgen_pl(dropAmount, config.Currency.Sign))
+ " " + GetText(channel.GuildId, strs.pick_pl(prefix));
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
IUserMessage sent;
using (var stream = GetRandomCurrencyImage(pw, out var ext))
{
sent = await channel.SendFileAsync(stream, $"currency_image.{ext}", toSend).ConfigureAwait(false);
}
await AddPlantToDatabase(channel.GuildId,
channel.Id,
_client.CurrentUser.Id,
sent.Id,
dropAmount,
pw).ConfigureAwait(false);
}
uow.SaveChanges();
}
try
{
// delete all of the plant messages which have just been picked
var _ = ch.DeleteMessagesAsync(ids);
}
catch { }
// return the amount of currency the user picked
return amount;
}
finally
{
pickLock.Release();
}
}
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));
if (amount > 1)
msgToSend += " " + GetText(gid, strs.pick_pl(prefix));
else
msgToSend += " " + GetText(gid, strs.pick_sn(prefix));
//get the image
using (var stream = GetRandomCurrencyImage(pass, out var ext))
{
// send it
var msg = await ch.SendFileAsync(stream, $"img.{ext}", msgToSend).ConfigureAwait(false);
// return sent message's id (in order to be able to delete it when it's picked)
return msg.Id;
}
}
catch
{
// if sending fails, return null as message id
return null;
}
}
});
return Task.CompletedTask;
}
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();
// 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))
{
// try to send the message with the currency image
var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass).ConfigureAwait(false);
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);
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).ConfigureAwait(false);
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)
/// <summary>
/// Generate a hexadecimal string from 1000 to ffff.
/// </summary>
/// <returns>A hexadecimal string from 1000 to ffff</returns>
private string GenerateCurrencyPassword()
{
// generate a number from 1000 to ffff
var num = _rng.Next(4096, 65536);
// convert it to hexadecimal
return num.ToString("x4");
}
public async Task<long> PickAsync(ulong gid, ITextChannel ch, ulong uid, string pass)
{
await pickLock.WaitAsync();
try
{
long amount;
ulong[] ids;
using (var uow = _db.GetDbContext())
{
uow.PlantedCurrency.Add(new PlantedCurrency
// 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();
// 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();
// 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();
// remove them from the database
uow.RemoveRange(entries);
if (amount > 0)
{
Amount = amount,
GuildId = gid,
ChannelId = cid,
Password = pass,
UserId = uid,
MessageId = mid,
});
await uow.SaveChangesAsync();
// give the picked currency to the user
await _cs.AddAsync(uid, "Picked currency", amount, gamble: false);
}
uow.SaveChanges();
}
try
{
// delete all of the plant messages which have just been picked
var _ = ch.DeleteMessagesAsync(ids);
}
catch { }
// return the amount of currency the user picked
return amount;
}
finally
{
pickLock.Release();
}
}
}
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));
if (amount > 1)
msgToSend += " " + GetText(gid, strs.pick_pl(prefix));
else
msgToSend += " " + GetText(gid, strs.pick_sn(prefix));
//get the image
using (var stream = GetRandomCurrencyImage(pass, out var ext))
{
// send it
var msg = await ch.SendFileAsync(stream, $"img.{ext}", msgToSend).ConfigureAwait(false);
// return sent message's id (in order to be able to delete it when it's picked)
return msg.Id;
}
}
catch
{
// if sending fails, return null as message id
return null;
}
}
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();
// 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))
{
// try to send the message with the currency image
var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass).ConfigureAwait(false);
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);
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).ConfigureAwait(false);
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)
{
using (var uow = _db.GetDbContext())
{
uow.PlantedCurrency.Add(new PlantedCurrency
{
Amount = amount,
GuildId = gid,
ChannelId = cid,
Password = pass,
UserId = uid,
MessageId = mid,
});
await uow.SaveChangesAsync();
}
}
}

View File

@@ -1,121 +1,116 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Services;
using Discord.WebSocket;
using Serilog;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public class VoteModel
{
public class VoteModel
{
[JsonPropertyName("userId")]
public ulong UserId { get; set; }
}
[JsonPropertyName("userId")]
public ulong UserId { get; set; }
}
public class VoteRewardService : INService, IReadyExecutor
public class VoteRewardService : INService, IReadyExecutor
{
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ICurrencyService _currencyService;
private readonly GamblingConfigService _gamb;
private HttpClient _http;
public VoteRewardService(
DiscordSocketClient client,
IBotCredentials creds,
IHttpClientFactory httpClientFactory,
ICurrencyService currencyService,
GamblingConfigService gamb)
{
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ICurrencyService _currencyService;
private readonly GamblingConfigService _gamb;
private HttpClient _http;
public VoteRewardService(
DiscordSocketClient client,
IBotCredentials creds,
IHttpClientFactory httpClientFactory,
ICurrencyService currencyService,
GamblingConfigService gamb)
{
_client = client;
_creds = creds;
_httpClientFactory = httpClientFactory;
_currencyService = currencyService;
_gamb = gamb;
}
_client = client;
_creds = creds;
_httpClientFactory = httpClientFactory;
_currencyService = currencyService;
_gamb = gamb;
}
public async Task OnReadyAsync()
public async Task OnReadyAsync()
{
if (_client.ShardId != 0)
return;
_http = new HttpClient(new HttpClientHandler()
{
if (_client.ShardId != 0)
return;
AllowAutoRedirect = false,
ServerCertificateCustomValidationCallback = delegate { return true; }
});
_http = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = false,
ServerCertificateCustomValidationCallback = delegate { return true; }
});
while (true)
{
await Task.Delay(30000);
while (true)
{
await Task.Delay(30000);
var topggKey = _creds.Votes?.TopggKey;
var topggServiceUrl = _creds.Votes?.TopggServiceUrl;
var topggKey = _creds.Votes?.TopggKey;
var topggServiceUrl = _creds.Votes?.TopggServiceUrl;
try
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");
var res = await _http.GetStringAsync(uri);
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
if (data is { Count: > 0 })
{
_http.DefaultRequestHeaders.Authorization = new(topggKey);
var uri = new Uri(new(topggServiceUrl), "topgg/new");
var res = await _http.GetStringAsync(uri);
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
var ids = data.Select(x => x.UserId).ToList();
if (data is { Count: > 0 })
{
var ids = data.Select(x => x.UserId).ToList();
await _currencyService.AddBulkAsync(ids,
data.Select(_ => "top.gg vote reward"),
data.Select(x => _gamb.Data.VoteReward),
true);
await _currencyService.AddBulkAsync(ids,
data.Select(_ => "top.gg vote reward"),
data.Select(x => _gamb.Data.VoteReward),
true);
Log.Information("Rewarding {Count} top.gg voters", ids.Count());
}
Log.Information("Rewarding {Count} top.gg voters", ids.Count());
}
}
catch (Exception ex)
{
Log.Error(ex, "Critical error loading top.gg vote rewards.");
}
}
catch (Exception ex)
{
Log.Error(ex, "Critical error loading top.gg vote rewards.");
}
var discordsKey = _creds.Votes?.DiscordsKey;
var discordsServiceUrl = _creds.Votes?.DiscordsServiceUrl;
var discordsKey = _creds.Votes?.DiscordsKey;
var discordsServiceUrl = _creds.Votes?.DiscordsServiceUrl;
try
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"));
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
if (data is { Count: > 0 })
{
_http.DefaultRequestHeaders.Authorization = new(discordsKey);
var res = await _http.GetStringAsync(new Uri(new(discordsServiceUrl), "discords/new"));
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
if (data is { Count: > 0 })
{
var ids = data.Select(x => x.UserId).ToList();
var ids = data.Select(x => x.UserId).ToList();
await _currencyService.AddBulkAsync(ids,
data.Select(_ => "discords.com vote reward"),
data.Select(x => _gamb.Data.VoteReward),
true);
await _currencyService.AddBulkAsync(ids,
data.Select(_ => "discords.com vote reward"),
data.Select(x => _gamb.Data.VoteReward),
true);
Log.Information("Rewarding {Count} discords.com voters", ids.Count());
}
Log.Information("Rewarding {Count} discords.com voters", ids.Count());
}
}
catch (Exception ex)
{
Log.Error(ex, "Critical error loading discords.com vote rewards.");
}
}
catch (Exception ex)
{
Log.Error(ex, "Critical error loading discords.com vote rewards.");
}
}
}

View File

@@ -2,527 +2,523 @@
using NadekoBot.Modules.Gambling.Common.Waifu;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Db.Models;
namespace NadekoBot.Modules.Gambling.Services
namespace NadekoBot.Modules.Gambling.Services;
public class WaifuService : INService
{
public class WaifuService : INService
public class FullWaifuInfo
{
public class FullWaifuInfo
{
public WaifuInfo Waifu { get; set; }
public IEnumerable<string> Claims { get; set; }
public int Divorces { get; set; }
}
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;
private readonly DbService _db;
private readonly ICurrencyService _cs;
private readonly IDataCache _cache;
private readonly GamblingConfigService _gss;
public WaifuService(DbService db, ICurrencyService cs, IDataCache cache,
GamblingConfigService gss)
{
_db = db;
_cs = cs;
_cache = cache;
_gss = gss;
}
public WaifuService(DbService db, ICurrencyService cs, IDataCache cache,
GamblingConfigService gss)
{
_db = db;
_cs = cs;
_cache = cache;
_gss = gss;
}
public async Task<bool> WaifuTransfer(IUser owner, ulong waifuId, IUser newOwner)
{
if (owner.Id == newOwner.Id || waifuId == newOwner.Id)
return false;
public async Task<bool> WaifuTransfer(IUser owner, ulong waifuId, IUser newOwner)
{
if (owner.Id == newOwner.Id || waifuId == newOwner.Id)
return false;
var settings = _gss.Data;
var settings = _gss.Data;
using (var uow = _db.GetDbContext())
{
var waifu = uow.WaifuInfo.ByWaifuUserId(waifuId);
var ownerUser = uow.GetOrCreateUser(owner);
using (var uow = _db.GetDbContext())
{
var waifu = uow.WaifuInfo.ByWaifuUserId(waifuId);
var ownerUser = uow.GetOrCreateUser(owner);
// owner has to be the owner of the waifu
if (waifu is null || waifu.ClaimerId != ownerUser.Id)
return false;
// 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,
// 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))
{
// 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)
waifu.Price = settings.Waifu.MinPrice;
}
else // if not, pay 10% fee
{
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, gamble: true))
{
return false;
}
waifu.Price = (int) (waifu.Price * 0.95); // half of 10% = 5% price reduction
if (waifu.Price < settings.Waifu.MinPrice)
waifu.Price = settings.Waifu.MinPrice;
}
//new claimerId is the id of the new owner
var newOwnerUser = uow.GetOrCreateUser(newOwner);
waifu.ClaimerId = newOwnerUser.Id;
await uow.SaveChangesAsync();
}
return true;
}
public int GetResetPrice(IUser user)
{
var settings = _gss.Data;
using (var uow = _db.GetDbContext())
{
var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
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();
return (int) Math.Ceiling(waifu.Price * 1.25f) +
((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
}
}
public async Task<bool> TryReset(IUser user)
{
using (var uow = _db.GetDbContext())
{
var price = GetResetPrice(user);
if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, gamble: true))
// unable to pay 60% penalty
return false;
}
var affs = uow.WaifuUpdates
.AsQueryable()
.Where(w => w.User.UserId == user.Id
&& w.UpdateType == WaifuUpdateType.AffinityChanged
&& w.New != null);
waifu.Price = (int)(waifu.Price * 0.7); // half of 60% = 30% price reduction
if (waifu.Price < settings.Waifu.MinPrice)
waifu.Price = settings.Waifu.MinPrice;
}
else // if not, pay 10% fee
{
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, gamble: true))
{
return false;
}
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);
//reset divorces to 0
uow.WaifuUpdates.RemoveRange(divorces);
var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
//reset price, remove items
//remove owner, remove affinity
waifu.Price = 50;
waifu.Items.Clear();
waifu.ClaimerId = null;
waifu.AffinityId = null;
//wives stay though
uow.SaveChanges();
waifu.Price = (int) (waifu.Price * 0.95); // half of 10% = 5% price reduction
if (waifu.Price < settings.Waifu.MinPrice)
waifu.Price = settings.Waifu.MinPrice;
}
return true;
//new claimerId is the id of the new owner
var newOwnerUser = uow.GetOrCreateUser(newOwner);
waifu.ClaimerId = newOwnerUser.Id;
await uow.SaveChangesAsync();
}
public async Task<(WaifuInfo, bool, WaifuClaimResult)> ClaimWaifuAsync(IUser user, IUser target, int amount)
return true;
}
public int GetResetPrice(IUser user)
{
var settings = _gss.Data;
using (var uow = _db.GetDbContext())
{
var settings = _gss.Data;
WaifuClaimResult result;
WaifuInfo w;
bool isAffinity;
using (var uow = _db.GetDbContext())
var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
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();
return (int) Math.Ceiling(waifu.Price * 1.25f) +
((divorces + affs + 2) * settings.Waifu.Multipliers.WaifuReset);
}
}
public async Task<bool> TryReset(IUser user)
{
using (var uow = _db.GetDbContext())
{
var price = GetResetPrice(user);
if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, gamble: true))
return false;
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);
//reset changes of heart to 0
uow.WaifuUpdates.RemoveRange(affs);
//reset divorces to 0
uow.WaifuUpdates.RemoveRange(divorces);
var waifu = uow.WaifuInfo.ByWaifuUserId(user.Id);
//reset price, remove items
//remove owner, remove affinity
waifu.Price = 50;
waifu.Items.Clear();
waifu.ClaimerId = null;
waifu.AffinityId = null;
//wives stay though
uow.SaveChanges();
}
return true;
}
public async Task<(WaifuInfo, bool, WaifuClaimResult)> ClaimWaifuAsync(IUser user, IUser target, int amount)
{
var settings = _gss.Data;
WaifuClaimResult result;
WaifuInfo w;
bool isAffinity;
using (var uow = _db.GetDbContext())
{
w = uow.WaifuInfo.ByWaifuUserId(target.Id);
isAffinity = (w?.Affinity?.UserId == user.Id);
if (w is null)
{
w = uow.WaifuInfo.ByWaifuUserId(target.Id);
isAffinity = (w?.Affinity?.UserId == user.Id);
if (w is null)
var claimer = uow.GetOrCreateUser(user);
var waifu = uow.GetOrCreateUser(target);
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
{
var claimer = uow.GetOrCreateUser(user);
var waifu = uow.GetOrCreateUser(target);
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
{
result = WaifuClaimResult.NotEnoughFunds;
}
else
{
uow.WaifuInfo.Add(w = new WaifuInfo()
{
Waifu = waifu,
Claimer = claimer,
Affinity = null,
Price = amount
});
uow.WaifuUpdates.Add(new WaifuUpdate()
{
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))
{
result = WaifuClaimResult.NotEnoughFunds;
}
else
{
var oldClaimer = w.Claimer;
w.Claimer = uow.GetOrCreateUser(user);
w.Price = amount + (amount / 4);
result = WaifuClaimResult.Success;
uow.WaifuUpdates.Add(new WaifuUpdate()
{
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))
{
result = WaifuClaimResult.NotEnoughFunds;
}
else
{
var oldClaimer = w.Claimer;
w.Claimer = uow.GetOrCreateUser(user);
w.Price = amount;
result = WaifuClaimResult.Success;
uow.WaifuUpdates.Add(new WaifuUpdate()
{
User = w.Waifu,
Old = oldClaimer,
New = w.Claimer,
UpdateType = WaifuUpdateType.Claimed
});
}
result = WaifuClaimResult.NotEnoughFunds;
}
else
result = WaifuClaimResult.InsufficientAmount;
await uow.SaveChangesAsync();
}
return (w, isAffinity, result);
}
public async Task<(DiscordUser, bool, TimeSpan?)> ChangeAffinityAsync(IUser user, IGuildUser target)
{
DiscordUser oldAff = null;
var success = false;
TimeSpan? remaining = null;
using (var uow = _db.GetDbContext())
{
var w = uow.WaifuInfo.ByWaifuUserId(user.Id);
var newAff = target is null ? null : uow.GetOrCreateUser(target);
if (w?.Affinity?.UserId == target?.Id)
{
}
else if (!_cache.TryAddAffinityCooldown(user.Id, out remaining))
{
}
else if (w is null)
{
var thisUser = uow.GetOrCreateUser(user);
uow.WaifuInfo.Add(new WaifuInfo()
uow.WaifuInfo.Add(w = new WaifuInfo()
{
Affinity = newAff,
Waifu = thisUser,
Price = 1,
Claimer = null
Waifu = waifu,
Claimer = claimer,
Affinity = null,
Price = amount
});
success = true;
uow.WaifuUpdates.Add(new WaifuUpdate()
{
User = thisUser,
User = waifu,
Old = null,
New = newAff,
UpdateType = WaifuUpdateType.AffinityChanged
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))
{
result = WaifuClaimResult.NotEnoughFunds;
}
else
{
if (w.Affinity != null)
oldAff = w.Affinity;
w.Affinity = newAff;
success = true;
uow.WaifuUpdates.Add(new WaifuUpdate()
{
User = w.Waifu,
Old = oldAff,
New = newAff,
UpdateType = WaifuUpdateType.AffinityChanged
});
}
await uow.SaveChangesAsync();
}
return (oldAff, success, remaining);
}
public IEnumerable<WaifuLbResult> GetTopWaifusAtPage(int page)
{
using (var uow = _db.GetDbContext())
{
return uow.WaifuInfo.GetTop(9, page * 9);
}
}
public ulong GetWaifuUserId(ulong ownerId, string name)
{
using var uow = _db.GetDbContext();
return uow.WaifuInfo.GetWaifuUserId(ownerId, name);
}
public async Task<(WaifuInfo, DivorceResult, long, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId)
{
DivorceResult result;
TimeSpan? remaining = null;
long amount = 0;
WaifuInfo w = null;
using (var uow = _db.GetDbContext())
{
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;
}
else
{
amount = w.Price / 2;
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);
result = DivorceResult.SucessWithPenalty;
}
else
{
await _cs.AddAsync(user.Id, "Waifu Refund", amount, gamble: true);
result = DivorceResult.Success;
}
var oldClaimer = w.Claimer;
w.Claimer = null;
w.Claimer = uow.GetOrCreateUser(user);
w.Price = amount + (amount / 4);
result = WaifuClaimResult.Success;
uow.WaifuUpdates.Add(new WaifuUpdate()
{
User = w.Waifu,
Old = oldClaimer,
New = null,
New = w.Claimer,
UpdateType = WaifuUpdateType.Claimed
});
}
await uow.SaveChangesAsync();
}
return (w, result, amount, remaining);
}
public async Task<bool> GiftWaifuAsync(IUser from, IUser giftedWaifu, WaifuItemModel itemObj)
{
if (!await _cs.RemoveAsync(from, "Bought waifu item", itemObj.Price, gamble: true))
else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity
{
return false;
}
using (var uow = _db.GetDbContext())
{
var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id,
set => set.Include(x => x.Items)
.Include(x => x.Claimer));
if (w is null)
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, gamble: true))
{
uow.WaifuInfo.Add(w = new WaifuInfo()
{
Affinity = null,
Claimer = null,
Price = 1,
Waifu = uow.GetOrCreateUser(giftedWaifu),
});
}
if (!itemObj.Negative)
{
w.Items.Add(new WaifuItem()
{
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;
}
result = WaifuClaimResult.NotEnoughFunds;
}
else
{
w.Price -= (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
if (w.Price < 1)
w.Price = 1;
}
var oldClaimer = w.Claimer;
w.Claimer = uow.GetOrCreateUser(user);
w.Price = amount;
result = WaifuClaimResult.Success;
await uow.SaveChangesAsync();
}
return true;
}
public WaifuInfoStats GetFullWaifuInfoAsync(ulong targetId)
{
using (var uow = _db.GetDbContext())
{
var wi = uow.GetWaifuInfo(targetId);
if (wi is null)
{
wi = new WaifuInfoStats
uow.WaifuUpdates.Add(new WaifuUpdate()
{
AffinityCount = 0,
AffinityName = null,
ClaimCount = 0,
ClaimerName = null,
Claims = new List<string>(),
Fans = new List<string>(),
DivorceCount = 0,
FullName = null,
Items = new List<WaifuItem>(),
Price = 1
};
User = w.Waifu,
Old = oldClaimer,
New = w.Claimer,
UpdateType = WaifuUpdateType.Claimed
});
}
return wi;
}
else
result = WaifuClaimResult.InsufficientAmount;
await uow.SaveChangesAsync();
}
public WaifuInfoStats GetFullWaifuInfoAsync(IGuildUser target)
return (w, isAffinity, result);
}
public async Task<(DiscordUser, bool, TimeSpan?)> ChangeAffinityAsync(IUser user, IGuildUser target)
{
DiscordUser oldAff = null;
var success = false;
TimeSpan? remaining = null;
using (var uow = _db.GetDbContext())
{
using (var uow = _db.GetDbContext())
var w = uow.WaifuInfo.ByWaifuUserId(user.Id);
var newAff = target is null ? null : uow.GetOrCreateUser(target);
if (w?.Affinity?.UserId == target?.Id)
{
var du = uow.GetOrCreateUser(target);
return GetFullWaifuInfoAsync(target.Id);
}
}
else if (!_cache.TryAddAffinityCooldown(user.Id, out remaining))
{
}
else if (w is null)
{
var thisUser = uow.GetOrCreateUser(user);
uow.WaifuInfo.Add(new WaifuInfo()
{
Affinity = newAff,
Waifu = thisUser,
Price = 1,
Claimer = null
});
success = true;
public string GetClaimTitle(int count)
{
ClaimTitle title;
if (count == 0)
title = ClaimTitle.Lonely;
else if (count == 1)
title = ClaimTitle.Devoted;
else if (count < 3)
title = ClaimTitle.Rookie;
else if (count < 6)
title = ClaimTitle.Schemer;
else if (count < 10)
title = ClaimTitle.Dilettante;
else if (count < 17)
title = ClaimTitle.Intermediate;
else if (count < 25)
title = ClaimTitle.Seducer;
else if (count < 35)
title = ClaimTitle.Expert;
else if (count < 50)
title = ClaimTitle.Veteran;
else if (count < 75)
title = ClaimTitle.Incubis;
else if (count < 100)
title = ClaimTitle.Harem_King;
uow.WaifuUpdates.Add(new WaifuUpdate()
{
User = thisUser,
Old = null,
New = newAff,
UpdateType = WaifuUpdateType.AffinityChanged
});
}
else
title = ClaimTitle.Harem_God;
{
if (w.Affinity != null)
oldAff = w.Affinity;
w.Affinity = newAff;
success = true;
return title.ToString().Replace('_', ' ');
uow.WaifuUpdates.Add(new WaifuUpdate()
{
User = w.Waifu,
Old = oldAff,
New = newAff,
UpdateType = WaifuUpdateType.AffinityChanged
});
}
await uow.SaveChangesAsync();
}
public string GetAffinityTitle(int count)
{
AffinityTitle title;
if (count < 1)
title = AffinityTitle.Pure;
else if (count < 2)
title = AffinityTitle.Faithful;
else if (count < 4)
title = AffinityTitle.Playful;
else if (count < 8)
title = AffinityTitle.Cheater;
else if (count < 11)
title = AffinityTitle.Tainted;
else if (count < 15)
title = AffinityTitle.Corrupted;
else if (count < 20)
title = AffinityTitle.Lewd;
else if (count < 25)
title = AffinityTitle.Sloot;
else if (count < 35)
title = AffinityTitle.Depraved;
else
title = AffinityTitle.Harlot;
return (oldAff, success, remaining);
}
return title.ToString().Replace('_', ' ');
}
public IReadOnlyList<WaifuItemModel> GetWaifuItems()
public IEnumerable<WaifuLbResult> GetTopWaifusAtPage(int page)
{
using (var uow = _db.GetDbContext())
{
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 uow.WaifuInfo.GetTop(9, page * 9);
}
}
public ulong GetWaifuUserId(ulong ownerId, string name)
{
using var uow = _db.GetDbContext();
return uow.WaifuInfo.GetWaifuUserId(ownerId, name);
}
public async Task<(WaifuInfo, DivorceResult, long, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId)
{
DivorceResult result;
TimeSpan? remaining = null;
long amount = 0;
WaifuInfo w = null;
using (var uow = _db.GetDbContext())
{
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;
}
else
{
amount = w.Price / 2;
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);
result = DivorceResult.SucessWithPenalty;
}
else
{
await _cs.AddAsync(user.Id, "Waifu Refund", amount, gamble: true);
result = DivorceResult.Success;
}
var oldClaimer = w.Claimer;
w.Claimer = null;
uow.WaifuUpdates.Add(new WaifuUpdate()
{
User = w.Waifu,
Old = oldClaimer,
New = null,
UpdateType = WaifuUpdateType.Claimed
});
}
await uow.SaveChangesAsync();
}
return (w, result, amount, remaining);
}
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;
}
using (var uow = _db.GetDbContext())
{
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 WaifuInfo()
{
Affinity = null,
Claimer = null,
Price = 1,
Waifu = uow.GetOrCreateUser(giftedWaifu),
});
}
if (!itemObj.Negative)
{
w.Items.Add(new WaifuItem()
{
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
{
w.Price -= (int)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
if (w.Price < 1)
w.Price = 1;
}
await uow.SaveChangesAsync();
}
return true;
}
public WaifuInfoStats GetFullWaifuInfoAsync(ulong targetId)
{
using (var uow = _db.GetDbContext())
{
var wi = uow.GetWaifuInfo(targetId);
if (wi is null)
{
wi = new WaifuInfoStats
{
AffinityCount = 0,
AffinityName = null,
ClaimCount = 0,
ClaimerName = null,
Claims = new List<string>(),
Fans = new List<string>(),
DivorceCount = 0,
FullName = null,
Items = new List<WaifuItem>(),
Price = 1
};
}
return wi;
}
}
public WaifuInfoStats GetFullWaifuInfoAsync(IGuildUser target)
{
using (var uow = _db.GetDbContext())
{
var du = uow.GetOrCreateUser(target);
return GetFullWaifuInfoAsync(target.Id);
}
}
public string GetClaimTitle(int count)
{
ClaimTitle title;
if (count == 0)
title = ClaimTitle.Lonely;
else if (count == 1)
title = ClaimTitle.Devoted;
else if (count < 3)
title = ClaimTitle.Rookie;
else if (count < 6)
title = ClaimTitle.Schemer;
else if (count < 10)
title = ClaimTitle.Dilettante;
else if (count < 17)
title = ClaimTitle.Intermediate;
else if (count < 25)
title = ClaimTitle.Seducer;
else if (count < 35)
title = ClaimTitle.Expert;
else if (count < 50)
title = ClaimTitle.Veteran;
else if (count < 75)
title = ClaimTitle.Incubis;
else if (count < 100)
title = ClaimTitle.Harem_King;
else
title = ClaimTitle.Harem_God;
return title.ToString().Replace('_', ' ');
}
public string GetAffinityTitle(int count)
{
AffinityTitle title;
if (count < 1)
title = AffinityTitle.Pure;
else if (count < 2)
title = AffinityTitle.Faithful;
else if (count < 4)
title = AffinityTitle.Playful;
else if (count < 8)
title = AffinityTitle.Cheater;
else if (count < 11)
title = AffinityTitle.Tainted;
else if (count < 15)
title = AffinityTitle.Corrupted;
else if (count < 20)
title = AffinityTitle.Lewd;
else if (count < 25)
title = AffinityTitle.Sloot;
else if (count < 35)
title = AffinityTitle.Depraved;
else
title = AffinityTitle.Harlot;
return title.ToString().Replace('_', ' ');
}
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();
}
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Microsoft.EntityFrameworkCore;
@@ -14,462 +11,459 @@ using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Db;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration;
using Serilog;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
[Group]
public class ShopCommands : GamblingSubmodule<IShopService>
{
[Group]
public class ShopCommands : GamblingSubmodule<IShopService>
private readonly DbService _db;
private readonly ICurrencyService _cs;
public enum Role
{
private readonly DbService _db;
private readonly ICurrencyService _cs;
Role
}
public enum Role
{
Role
}
public enum List
{
List
}
public enum List
{
List
}
public ShopCommands(DbService db, ICurrencyService cs, GamblingConfigService gamblingConf)
: base(gamblingConf)
{
_db = db;
_cs = cs;
}
public ShopCommands(DbService db, ICurrencyService cs, GamblingConfigService gamblingConf)
: base(gamblingConf)
{
_db = db;
_cs = cs;
}
private Task ShopInternalAsync(int page = 0)
{
if (page < 0)
throw new ArgumentOutOfRangeException(nameof(page));
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) =>
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 (int i = 0; i < theseEntries.Length; i++)
{
var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray();
var entry = theseEntries[i];
embed.AddField(
$"#{curPage * 9 + i + 1} - {entry.Price}{CurrencySign}",
EntryToString(entry),
true);
}
return embed;
}, entries.Count, 9, true);
}
if (!theseEntries.Any())
return _eb.Create().WithErrorColor()
.WithDescription(GetText(strs.shop_none));
var embed = _eb.Create().WithOkColor()
.WithTitle(GetText(strs.shop));
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public Task Shop(int page = 1)
{
if (--page < 0)
return Task.CompletedTask;
return ShopInternalAsync(page);
}
for (int 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, true);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Buy(int index)
{
index -= 1;
if (index < 0)
return;
ShopEntry entry;
using (var uow = _db.GetDbContext())
{
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();
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public Task Shop(int page = 1)
if (entry is null)
{
if (--page < 0)
return Task.CompletedTask;
return ShopInternalAsync(page);
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Buy(int index)
if (entry.Type == ShopEntryType.Role)
{
index -= 1;
if (index < 0)
return;
ShopEntry entry;
using (var uow = _db.GetDbContext())
{
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();
}
var guser = (IGuildUser)ctx.User;
var role = ctx.Guild.GetRole(entry.RoleId);
if (entry is null)
if (role is null)
{
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
await ReplyErrorLocalizedAsync(strs.shop_role_not_found).ConfigureAwait(false);
return;
}
if (entry.Type == ShopEntryType.Role)
{
var guser = (IGuildUser)ctx.User;
var role = ctx.Guild.GetRole(entry.RoleId);
if (role is null)
{
await ReplyErrorLocalizedAsync(strs.shop_role_not_found).ConfigureAwait(false);
return;
}
if (guser.RoleIds.Any(id => id == role.Id))
{
await ReplyErrorLocalizedAsync(strs.shop_role_already_bought).ConfigureAwait(false);
return;
}
if (await _cs.RemoveAsync(ctx.User.Id, $"Shop purchase - {entry.Type}", entry.Price).ConfigureAwait(false))
{
try
{
await guser.AddRoleAsync(role).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex, "Error adding shop role");
await _cs.AddAsync(ctx.User.Id, $"Shop error refund", entry.Price).ConfigureAwait(false);
await ReplyErrorLocalizedAsync(strs.shop_role_purchase_error).ConfigureAwait(false);
return;
}
var profit = GetProfitAmount(entry.Price);
await _cs.AddAsync(entry.AuthorId, $"Shop sell item - {entry.Type}", profit).ConfigureAwait(false);
await _cs.AddAsync(ctx.Client.CurrentUser.Id, $"Shop sell item - cut", entry.Price - profit).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.shop_role_purchase(Format.Bold(role.Name))).ConfigureAwait(false);
return;
}
else
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
}
else if (entry.Type == ShopEntryType.List)
if (guser.RoleIds.Any(id => id == role.Id))
{
if (entry.Items.Count == 0)
await ReplyErrorLocalizedAsync(strs.shop_role_already_bought).ConfigureAwait(false);
return;
}
if (await _cs.RemoveAsync(ctx.User.Id, $"Shop purchase - {entry.Type}", entry.Price).ConfigureAwait(false))
{
try
{
await ReplyErrorLocalizedAsync(strs.out_of_stock).ConfigureAwait(false);
await guser.AddRoleAsync(role).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex, "Error adding shop role");
await _cs.AddAsync(ctx.User.Id, $"Shop error refund", entry.Price).ConfigureAwait(false);
await ReplyErrorLocalizedAsync(strs.shop_role_purchase_error).ConfigureAwait(false);
return;
}
var profit = GetProfitAmount(entry.Price);
await _cs.AddAsync(entry.AuthorId, $"Shop sell item - {entry.Type}", profit).ConfigureAwait(false);
await _cs.AddAsync(ctx.Client.CurrentUser.Id, $"Shop sell item - cut", entry.Price - profit).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.shop_role_purchase(Format.Bold(role.Name))).ConfigureAwait(false);
return;
}
else
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
}
else if (entry.Type == ShopEntryType.List)
{
if (entry.Items.Count == 0)
{
await ReplyErrorLocalizedAsync(strs.out_of_stock).ConfigureAwait(false);
return;
}
var item = entry.Items.ToArray()[new NadekoRandom().Next(0, entry.Items.Count)];
var item = entry.Items.ToArray()[new NadekoRandom().Next(0, entry.Items.Count)];
if (await _cs.RemoveAsync(ctx.User.Id, $"Shop purchase - {entry.Type}", entry.Price).ConfigureAwait(false))
if (await _cs.RemoveAsync(ctx.User.Id, $"Shop purchase - {entry.Type}", entry.Price).ConfigureAwait(false))
{
using (var uow = _db.GetDbContext())
{
using (var uow = _db.GetDbContext())
{
var x = uow.Set<ShopEntryItem>().Remove(item);
uow.SaveChanges();
}
try
{
await (await ctx.User.GetOrCreateDMChannelAsync().ConfigureAwait(false))
.EmbedAsync(_eb.Create().WithOkColor()
var x = uow.Set<ShopEntryItem>().Remove(item);
uow.SaveChanges();
}
try
{
await (await ctx.User.GetOrCreateDMChannelAsync().ConfigureAwait(false))
.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))
.ConfigureAwait(false);
.ConfigureAwait(false);
await _cs.AddAsync(entry.AuthorId,
$"Shop sell item - {entry.Name}",
GetProfitAmount(entry.Price)).ConfigureAwait(false);
}
catch
await _cs.AddAsync(entry.AuthorId,
$"Shop sell item - {entry.Name}",
GetProfitAmount(entry.Price)).ConfigureAwait(false);
}
catch
{
await _cs.AddAsync(ctx.User.Id,
$"Shop error refund - {entry.Name}",
entry.Price).ConfigureAwait(false);
using (var uow = _db.GetDbContext())
{
await _cs.AddAsync(ctx.User.Id,
$"Shop error refund - {entry.Name}",
entry.Price).ConfigureAwait(false);
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 = entries.ElementAtOrDefault(index);
if (entry != null)
{
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
set => set.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items)).ShopEntries);
entry = entries.ElementAtOrDefault(index);
if (entry != null)
if (entry.Items.Add(item))
{
if (entry.Items.Add(item))
{
uow.SaveChanges();
}
uow.SaveChanges();
}
}
await ReplyErrorLocalizedAsync(strs.shop_buy_error).ConfigureAwait(false);
return;
}
await ReplyConfirmLocalizedAsync(strs.shop_item_purchase).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
await ReplyErrorLocalizedAsync(strs.shop_buy_error).ConfigureAwait(false);
return;
}
await ReplyConfirmLocalizedAsync(strs.shop_item_purchase).ConfigureAwait(false);
}
}
private static long GetProfitAmount(int price) =>
(int)(Math.Ceiling(0.90 * price));
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task ShopAdd(Role _, int price, [Leftover] IRole role)
{
if (price < 1)
return;
var entry = new ShopEntry()
{
Name = "-",
Price = price,
Type = ShopEntryType.Role,
AuthorId = ctx.User.Id,
RoleId = role.Id,
RoleName = role.Name
};
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
};
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
uow.SaveChanges();
}
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopAdd(List _, int price, [Leftover] string name)
{
if (price < 1)
return;
var entry = new ShopEntry()
{
Name = name.TrimTo(100),
Price = price,
Type = ShopEntryType.List,
AuthorId = ctx.User.Id,
Items = new HashSet<ShopEntryItem>(),
};
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
};
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
uow.SaveChanges();
}
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopListAdd(int index, [Leftover] string itemText)
{
index -= 1;
if (index < 0)
return;
var item = new ShopEntryItem()
{
Text = itemText
};
ShopEntry entry;
bool rightType = false;
bool added = false;
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 = 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).ConfigureAwait(false);
else if (!rightType)
await ReplyErrorLocalizedAsync(strs.shop_item_wrong_type).ConfigureAwait(false);
else if (added == false)
await ReplyErrorLocalizedAsync(strs.shop_list_item_not_unique).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.shop_list_item_added).ConfigureAwait(false);
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopRemove(int index)
{
index -= 1;
if (index < 0)
return;
ShopEntry removed;
using (var uow = _db.GetDbContext())
{
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);
if (removed != null)
private static long GetProfitAmount(int price) =>
(int)(Math.Ceiling(0.90 * price));
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task ShopAdd(Role _, int price, [Leftover] IRole role)
{
if (price < 1)
return;
var entry = new ShopEntry()
{
Name = "-",
Price = price,
Type = ShopEntryType.Role,
AuthorId = ctx.User.Id,
RoleId = role.Id,
RoleName = role.Name
};
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
};
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
uow.SaveChanges();
}
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopAdd(List _, int price, [Leftover] string name)
{
if (price < 1)
return;
var entry = new ShopEntry()
{
Name = name.TrimTo(100),
Price = price,
Type = ShopEntryType.List,
AuthorId = ctx.User.Id,
Items = new HashSet<ShopEntryItem>(),
};
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
};
uow.GuildConfigsForId(ctx.Guild.Id, set => set).ShopEntries = entries;
uow.SaveChanges();
}
await ctx.Channel.EmbedAsync(EntryToEmbed(entry)
.WithTitle(GetText(strs.shop_item_add))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopListAdd(int index, [Leftover] string itemText)
{
index -= 1;
if (index < 0)
return;
var item = new ShopEntryItem()
{
Text = itemText
};
ShopEntry entry;
bool rightType = false;
bool added = false;
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 = entries.ElementAtOrDefault(index);
if (entry != null && (rightType = (entry.Type == ShopEntryType.List)))
{
if (added = entry.Items.Add(item))
{
uow.RemoveRange(removed.Items);
uow.Remove(removed);
uow.SaveChanges();
}
}
if (removed is null)
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
else
await ctx.Channel.EmbedAsync(EntryToEmbed(removed)
.WithTitle(GetText(strs.shop_item_rm))).ConfigureAwait(false);
}
if (entry is null)
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
else if (!rightType)
await ReplyErrorLocalizedAsync(strs.shop_item_wrong_type).ConfigureAwait(false);
else if (added == false)
await ReplyErrorLocalizedAsync(strs.shop_list_item_not_unique).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync(strs.shop_list_item_added).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopChangePrice(int index, int price)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopRemove(int index)
{
index -= 1;
if (index < 0)
return;
ShopEntry removed;
using (var uow = _db.GetDbContext())
{
if (--index < 0 || price <= 0)
return;
var config = uow.GuildConfigsForId(ctx.Guild.Id, set => set
.Include(x => x.ShopEntries)
.ThenInclude(x => x.Items));
var succ = await _service.ChangeEntryPriceAsync(ctx.Guild.Id, index, price);
if (succ)
var entries = new IndexedCollection<ShopEntry>(config.ShopEntries);
removed = entries.ElementAtOrDefault(index);
if (removed != null)
{
await ShopInternalAsync(index / 9);
await ctx.OkAsync();
}
else
{
await ctx.ErrorAsync();
uow.RemoveRange(removed.Items);
uow.Remove(removed);
uow.SaveChanges();
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopChangeName(int index, [Leftover] string newName)
if (removed is null)
await ReplyErrorLocalizedAsync(strs.shop_item_not_found).ConfigureAwait(false);
else
await ctx.Channel.EmbedAsync(EntryToEmbed(removed)
.WithTitle(GetText(strs.shop_item_rm))).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopChangePrice(int index, int price)
{
if (--index < 0 || price <= 0)
return;
var succ = await _service.ChangeEntryPriceAsync(ctx.Guild.Id, index, price);
if (succ)
{
if (--index < 0 || string.IsNullOrWhiteSpace(newName))
return;
await ShopInternalAsync(index / 9);
await ctx.OkAsync();
}
else
{
await ctx.ErrorAsync();
}
}
[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)
{
await ShopInternalAsync(index / 9);
await ctx.OkAsync();
}
else
{
await ctx.ErrorAsync();
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopSwap(int index1, int index2)
var succ = await _service.ChangeEntryNameAsync(ctx.Guild.Id, index, newName);
if (succ)
{
if (--index1 < 0 || --index2 < 0 || index1 == index2)
return;
await ShopInternalAsync(index / 9);
await ctx.OkAsync();
}
else
{
await ctx.ErrorAsync();
}
}
[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)
{
await ShopInternalAsync(index1 / 9);
await ctx.OkAsync();
}
else
{
await ctx.ErrorAsync();
}
var succ = await _service.SwapEntriesAsync(ctx.Guild.Id, index1, index2);
if (succ)
{
await ShopInternalAsync(index1 / 9);
await ctx.OkAsync();
}
else
{
await ctx.ErrorAsync();
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopMove(int fromIndex, int toIndex)
{
if (--fromIndex < 0 || --toIndex < 0 || fromIndex == toIndex)
return;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task ShopMove(int fromIndex, int toIndex)
{
if (--fromIndex < 0 || --toIndex < 0 || fromIndex == toIndex)
return;
var succ = await _service.MoveEntryAsync(ctx.Guild.Id, fromIndex, toIndex);
if (succ)
{
await ShopInternalAsync(toIndex / 9);
await ctx.OkAsync();
}
else
{
await ctx.ErrorAsync();
}
var succ = await _service.MoveEntryAsync(ctx.Guild.Id, fromIndex, toIndex);
if (succ)
{
await ShopInternalAsync(toIndex / 9);
await ctx.OkAsync();
}
else
{
await ctx.ErrorAsync();
}
}
public IEmbedBuilder EntryToEmbed(ShopEntry entry)
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), entry.Name, 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;
}
public string EntryToString(ShopEntry entry)
{
if (entry.Type == ShopEntryType.Role)
{
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), entry.Name, 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 GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE")));
}
public string EntryToString(ShopEntry entry)
else if (entry.Type == ShopEntryType.List)
{
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)
{
return GetText(strs.unique_items_left(entry.Items.Count)) + "\n" + entry.Name;
}
//else if (entry.Type == ShopEntryType.Infinite_List)
//{
//}
return "";
return GetText(strs.unique_items_left(entry.Items.Count)) + "\n" + entry.Name;
}
//else if (entry.Type == ShopEntryType.Infinite_List)
//{
//}
return "";
}
}
}

View File

@@ -1,9 +1,6 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -21,248 +18,247 @@ using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using Color = SixLabors.ImageSharp.Color;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
[Group]
public class SlotCommands : GamblingSubmodule<GamblingService>
{
[Group]
public class SlotCommands : GamblingSubmodule<GamblingService>
private static long _totalBet;
private static long _totalPaidOut;
private static readonly HashSet<ulong> _runningUsers = new HashSet<ulong>();
//here is a payout chart
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
//thanks to judge for helping me with this
private readonly IImageCache _images;
private FontProvider _fonts;
private readonly DbService _db;
public SlotCommands(IDataCache data,
FontProvider fonts, DbService db,
GamblingConfigService gamb) : base(gamb)
{
private static long _totalBet;
private static long _totalPaidOut;
_images = data.LocalImages;
_fonts = fonts;
_db = db;
}
private static readonly HashSet<ulong> _runningUsers = new HashSet<ulong>();
public sealed class SlotMachine
{
public const int MaxValue = 5;
//here is a payout chart
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
//thanks to judge for helping me with this
private readonly IImageCache _images;
private FontProvider _fonts;
private readonly DbService _db;
public SlotCommands(IDataCache data,
FontProvider fonts, DbService db,
GamblingConfigService gamb) : base(gamb)
static readonly List<Func<int[], int>> _winningCombos = new List<Func<int[], int>>()
{
_images = data.LocalImages;
_fonts = fonts;
_db = db;
//three flowers
(arr) => arr.All(a=>a==MaxValue) ? 30 : 0,
//three of the same
(arr) => !arr.Any(a => a != arr[0]) ? 10 : 0,
//two flowers
(arr) => arr.Count(a => a == MaxValue) == 2 ? 4 : 0,
//one flower
(arr) => arr.Any(a => a == MaxValue) ? 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, MaxValue + 1);
}
var multi = 0;
foreach (var t in _winningCombos)
{
multi = t(numbers);
if (multi != 0)
break;
}
return new SlotResult(numbers, multi);
}
public sealed class SlotMachine
public struct SlotResult
{
public const int MaxValue = 5;
static readonly List<Func<int[], int>> _winningCombos = new List<Func<int[], int>>()
public int[] Numbers { get; }
public int Multiplier { get; }
public SlotResult(int[] nums, int multi)
{
//three flowers
(arr) => arr.All(a=>a==MaxValue) ? 30 : 0,
//three of the same
(arr) => !arr.Any(a => a != arr[0]) ? 10 : 0,
//two flowers
(arr) => arr.Count(a => a == MaxValue) == 2 ? 4 : 0,
//one flower
(arr) => arr.Any(a => a == MaxValue) ? 1 : 0,
};
Numbers = nums;
Multiplier = multi;
}
}
}
public static SlotResult Pull()
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SlotStats()
{
//i remembered to not be a moron
var paid = _totalPaidOut;
var bet = _totalBet;
if (bet <= 0)
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}%");
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SlotTest(int tests = 1000)
{
if (tests <= 0)
return;
//multi vs how many times it occured
var dict = new Dictionary<int, int>();
for (int i = 0; i < tests; i++)
{
var res = SlotMachine.Pull();
if (dict.ContainsKey(res.Multiplier))
dict[res.Multiplier] += 1;
else
dict.Add(res.Multiplier, 1);
}
var sb = new StringBuilder();
const int bet = 1;
int payout = 0;
foreach (var key in dict.Keys.OrderByDescending(x => x))
{
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(),
footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%").ConfigureAwait(false);
}
[NadekoCommand, Aliases]
public async Task Slot(ShmartNumber amount)
{
if (!_runningUsers.Add(ctx.User.Id))
return;
try
{
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
return;
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
var result = await _service.SlotAsync(ctx.User.Id, amount);
if (result.Error != GamblingError.None)
{
if (result.Error == GamblingError.NotEnough)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
return;
}
Interlocked.Add(ref _totalBet, amount);
Interlocked.Add(ref _totalPaidOut, result.Won);
long ownedAmount;
using (var uow = _db.GetDbContext())
{
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];
for (var i = 0; i < numbers.Length; i++)
{
numbers[i] = new NadekoRandom().Next(0, MaxValue + 1);
}
var multi = 0;
foreach (var t in _winningCombos)
{
multi = t(numbers);
if (multi != 0)
break;
}
result.Rolls.CopyTo(numbers, 0);
return new SlotResult(numbers, multi);
}
public struct SlotResult
{
public int[] Numbers { get; }
public int Multiplier { get; }
public SlotResult(int[] nums, int multi)
{
Numbers = nums;
Multiplier = multi;
}
}
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SlotStats()
{
//i remembered to not be a moron
var paid = _totalPaidOut;
var bet = _totalBet;
if (bet <= 0)
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}%");
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task SlotTest(int tests = 1000)
{
if (tests <= 0)
return;
//multi vs how many times it occured
var dict = new Dictionary<int, int>();
for (int i = 0; i < tests; i++)
{
var res = SlotMachine.Pull();
if (dict.ContainsKey(res.Multiplier))
dict[res.Multiplier] += 1;
else
dict.Add(res.Multiplier, 1);
}
var sb = new StringBuilder();
const int bet = 1;
int payout = 0;
foreach (var key in dict.Keys.OrderByDescending(x => x))
{
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(),
footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%").ConfigureAwait(false);
}
[NadekoCommand, Aliases]
public async Task Slot(ShmartNumber amount)
{
if (!_runningUsers.Add(ctx.User.Id))
return;
try
{
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
return;
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
var result = await _service.SlotAsync(ctx.User.Id, amount);
if (result.Error != GamblingError.None)
{
if (result.Error == GamblingError.NotEnough)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
return;
}
Interlocked.Add(ref _totalBet, amount);
Interlocked.Add(ref _totalPaidOut, result.Won);
long ownedAmount;
using (var uow = _db.GetDbContext())
{
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;
Color fontColor = _config.Slots.CurrencyFontColor;
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
{
TextOptions = new TextOptions()
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 140,
}
}, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), fontColor,
new PointF(227, 92)));
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
{
TextOptions = new TextOptions()
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 140,
}
}, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), fontColor,
new PointF(227, 92)));
var bottomFont = _fonts.DottyFont.CreateFont(50);
var bottomFont = _fonts.DottyFont.CreateFont(50);
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
{
TextOptions = new TextOptions()
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 135,
}
}, amount.ToString(), bottomFont, fontColor,
new PointF(129, 472)));
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
{
TextOptions = new TextOptions()
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 135,
}
}, amount.ToString(), bottomFont, fontColor,
new PointF(129, 472)));
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
{
TextOptions = new TextOptions()
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 135,
}
}, ownedAmount.ToString(), bottomFont, fontColor,
new PointF(325, 472)));
//sw.PrintLap("drew red text");
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
{
TextOptions = new TextOptions()
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 135,
}
}, ownedAmount.ToString(), bottomFont, fontColor,
new PointF(325, 472)));
//sw.PrintLap("drew red text");
for (var i = 0; i < 3; i++)
{
using (var img = Image.Load(_images.SlotEmojis[numbers[i]]))
{
bgImage.Mutate(x => x.DrawImage(img, new Point(148 + 105 * i, 217), 1f));
}
}
for (var i = 0; i < 3; i++)
{
using (var img = Image.Load(_images.SlotEmojis[numbers[i]]))
{
bgImage.Mutate(x => x.DrawImage(img, new Point(148 + 105 * i, 217), 1f));
}
}
var msg = GetText(strs.better_luck);
if (result.Multiplier > 0)
{
if (result.Multiplier == 1f)
msg = GetText(strs.slot_single(CurrencySign, 1));
else if (result.Multiplier == 4f)
msg = GetText(strs.slot_two(CurrencySign, 4));
else if (result.Multiplier == 10f)
msg = GetText(strs.slot_three(10));
else if (result.Multiplier == 30f)
msg = GetText(strs.slot_jackpot(30));
}
var msg = GetText(strs.better_luck);
if (result.Multiplier > 0)
{
if (result.Multiplier == 1f)
msg = GetText(strs.slot_single(CurrencySign, 1));
else if (result.Multiplier == 4f)
msg = GetText(strs.slot_two(CurrencySign, 4));
else if (result.Multiplier == 10f)
msg = GetText(strs.slot_three(10));
else if (result.Multiplier == 30f)
msg = GetText(strs.slot_jackpot(30));
}
using (var imgStream = bgImage.ToStream())
{
await ctx.Channel.SendFileAsync(imgStream,
filename: "result.png",
text: Format.Bold(ctx.User.ToString()) + " " + msg).ConfigureAwait(false);
}
}
}
finally
{
var _ = Task.Run(async () =>
{
await Task.Delay(1000).ConfigureAwait(false);
_runningUsers.Remove(ctx.User.Id);
});
}
using (var imgStream = bgImage.ToStream())
{
await ctx.Channel.SendFileAsync(imgStream,
filename: "result.png",
text: Format.Bold(ctx.User.ToString()) + " " + msg).ConfigureAwait(false);
}
}
}
finally
{
var _ = Task.Run(async () =>
{
await Task.Delay(1000).ConfigureAwait(false);
_runningUsers.Remove(ctx.User.Id);
});
}
}
}
}
}

View File

@@ -4,359 +4,356 @@ using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Gambling.Common.Waifu;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Modules.Gambling.Common;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
[Group]
public class WaifuClaimCommands : GamblingSubmodule<WaifuService>
{
[Group]
public class WaifuClaimCommands : GamblingSubmodule<WaifuService>
public WaifuClaimCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
{
}
public WaifuClaimCommands(GamblingConfigService gamblingConfService) : base(gamblingConfService)
[NadekoCommand, Aliases]
public async Task WaifuReset()
{
var price = _service.GetResetPrice(ctx.User);
var embed = _eb.Create()
.WithTitle(GetText(strs.waifu_reset_confirm))
.WithDescription(GetText(strs.waifu_reset_price(Format.Bold(price + CurrencySign))));
if (!await PromptUserConfirmAsync(embed))
return;
if (await _service.TryReset(ctx.User))
{
await ReplyConfirmLocalizedAsync(strs.waifu_reset);
return;
}
await ReplyErrorLocalizedAsync(strs.waifu_reset_fail);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuClaim(int amount, [Leftover]IUser target)
{
if (amount < _config.Waifu.MinPrice)
{
await ReplyErrorLocalizedAsync(strs.waifu_isnt_cheap(_config.Waifu.MinPrice + CurrencySign));
return;
}
[NadekoCommand, Aliases]
public async Task WaifuReset()
if (target.Id == ctx.User.Id)
{
var price = _service.GetResetPrice(ctx.User);
var embed = _eb.Create()
.WithTitle(GetText(strs.waifu_reset_confirm))
.WithDescription(GetText(strs.waifu_reset_price(Format.Bold(price + CurrencySign))));
if (!await PromptUserConfirmAsync(embed))
return;
if (await _service.TryReset(ctx.User))
{
await ReplyConfirmLocalizedAsync(strs.waifu_reset);
return;
}
await ReplyErrorLocalizedAsync(strs.waifu_reset_fail);
await ReplyErrorLocalizedAsync(strs.waifu_not_yourself);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuClaim(int amount, [Leftover]IUser target)
var (w, isAffinity, result) = await _service.ClaimWaifuAsync(ctx.User, target, amount);
if (result == WaifuClaimResult.InsufficientAmount)
{
if (amount < _config.Waifu.MinPrice)
{
await ReplyErrorLocalizedAsync(strs.waifu_isnt_cheap(_config.Waifu.MinPrice + CurrencySign));
return;
}
if (target.Id == ctx.User.Id)
{
await ReplyErrorLocalizedAsync(strs.waifu_not_yourself);
return;
}
var (w, isAffinity, result) = await _service.ClaimWaifuAsync(ctx.User, target, amount);
if (result == WaifuClaimResult.InsufficientAmount)
{
await ReplyErrorLocalizedAsync(strs.waifu_not_enough(Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))));
return;
}
if (result == WaifuClaimResult.NotEnoughFunds)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
var msg = GetText(strs.waifu_claimed(
Format.Bold(target.ToString()),
amount + CurrencySign));
if (w.Affinity?.UserId == ctx.User.Id)
msg += "\n" + GetText(strs.waifu_fulfilled(target, w.Price + CurrencySign));
else
msg = " " + msg;
await SendConfirmAsync(ctx.User.Mention + msg);
await ReplyErrorLocalizedAsync(strs.waifu_not_enough(Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))));
return;
}
if (result == WaifuClaimResult.NotEnoughFunds)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
var msg = GetText(strs.waifu_claimed(
Format.Bold(target.ToString()),
amount + CurrencySign));
if (w.Affinity?.UserId == ctx.User.Id)
msg += "\n" + GetText(strs.waifu_fulfilled(target, w.Price + CurrencySign));
else
msg = " " + msg;
await SendConfirmAsync(ctx.User.Mention + msg);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task WaifuTransfer(ulong waifuId, IUser newOwner)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task WaifuTransfer(ulong waifuId, IUser newOwner)
{
if (!await _service.WaifuTransfer(ctx.User, waifuId, newOwner)
)
{
if (!await _service.WaifuTransfer(ctx.User, waifuId, newOwner)
)
{
await ReplyErrorLocalizedAsync(strs.waifu_transfer_fail);
return;
}
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(
Format.Bold(waifuId.ToString()),
Format.Bold(ctx.User.ToString()),
Format.Bold(newOwner.ToString())));
await ReplyErrorLocalizedAsync(strs.waifu_transfer_fail);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task WaifuTransfer(IUser waifu, IUser newOwner)
{
if (!await _service.WaifuTransfer(ctx.User, waifu.Id, newOwner))
{
await ReplyErrorLocalizedAsync(strs.waifu_transfer_fail);
return;
}
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(
Format.Bold(waifuId.ToString()),
Format.Bold(ctx.User.ToString()),
Format.Bold(newOwner.ToString())));
}
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(
Format.Bold(waifu.ToString()),
Format.Bold(ctx.User.ToString()),
Format.Bold(newOwner.ToString())));
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task WaifuTransfer(IUser waifu, IUser newOwner)
{
if (!await _service.WaifuTransfer(ctx.User, waifu.Id, newOwner))
{
await ReplyErrorLocalizedAsync(strs.waifu_transfer_fail);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(-1)]
public Task Divorce([Leftover] string target)
{
var waifuUserId = _service.GetWaifuUserId(ctx.User.Id, target);
if (waifuUserId == default)
{
return ReplyErrorLocalizedAsync(strs.waifu_not_yours);
}
await ReplyConfirmLocalizedAsync(strs.waifu_transfer_success(
Format.Bold(waifu.ToString()),
Format.Bold(ctx.User.ToString()),
Format.Bold(newOwner.ToString())));
}
return Divorce(waifuUserId);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(-1)]
public Task Divorce([Leftover] string target)
{
var waifuUserId = _service.GetWaifuUserId(ctx.User.Id, target);
if (waifuUserId == default)
{
return ReplyErrorLocalizedAsync(strs.waifu_not_yours);
}
return Divorce(waifuUserId);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public Task Divorce([Leftover]IGuildUser target)
=> Divorce(target.Id);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public Task Divorce([Leftover]IGuildUser target)
=> Divorce(target.Id);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task Divorce([Leftover]ulong targetId)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task Divorce([Leftover]ulong targetId)
{
if (targetId == ctx.User.Id)
return;
var (w, result, amount, remaining) = await _service.DivorceWaifuAsync(ctx.User, targetId);
if (result == DivorceResult.SucessWithPenalty)
{
if (targetId == ctx.User.Id)
return;
await ReplyConfirmLocalizedAsync(strs.waifu_divorced_like(Format.Bold(w.Waifu.ToString()), amount + CurrencySign));
}
else if (result == DivorceResult.Success)
{
await ReplyConfirmLocalizedAsync(strs.waifu_divorced_notlike(amount + CurrencySign));
}
else if (result == DivorceResult.NotYourWife)
{
await ReplyErrorLocalizedAsync(strs.waifu_not_yours);
}
else
{
await ReplyErrorLocalizedAsync(strs.waifu_recent_divorce(
Format.Bold(((int)remaining?.TotalHours).ToString()),
Format.Bold(remaining?.Minutes.ToString())));
}
}
var (w, result, amount, remaining) = await _service.DivorceWaifuAsync(ctx.User, targetId);
if (result == DivorceResult.SucessWithPenalty)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Affinity([Leftover]IGuildUser u = null)
{
if (u?.Id == ctx.User.Id)
{
await ReplyErrorLocalizedAsync(strs.waifu_egomaniac);
return;
}
var (oldAff, sucess, remaining) = await _service.ChangeAffinityAsync(ctx.User, u);
if (!sucess)
{
if (remaining != null)
{
await ReplyConfirmLocalizedAsync(strs.waifu_divorced_like(Format.Bold(w.Waifu.ToString()), amount + CurrencySign));
}
else if (result == DivorceResult.Success)
{
await ReplyConfirmLocalizedAsync(strs.waifu_divorced_notlike(amount + CurrencySign));
}
else if (result == DivorceResult.NotYourWife)
{
await ReplyErrorLocalizedAsync(strs.waifu_not_yours);
}
else
{
await ReplyErrorLocalizedAsync(strs.waifu_recent_divorce(
await ReplyErrorLocalizedAsync(strs.waifu_affinity_cooldown(
Format.Bold(((int)remaining?.TotalHours).ToString()),
Format.Bold(remaining?.Minutes.ToString())));
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Affinity([Leftover]IGuildUser u = null)
{
if (u?.Id == ctx.User.Id)
{
await ReplyErrorLocalizedAsync(strs.waifu_egomaniac);
return;
}
var (oldAff, sucess, remaining) = await _service.ChangeAffinityAsync(ctx.User, u);
if (!sucess)
{
if (remaining != null)
{
await ReplyErrorLocalizedAsync(strs.waifu_affinity_cooldown(
Format.Bold(((int)remaining?.TotalHours).ToString()),
Format.Bold(remaining?.Minutes.ToString())));
}
else
{
await ReplyErrorLocalizedAsync(strs.waifu_affinity_already);
}
return;
}
if (u is null)
{
await ReplyConfirmLocalizedAsync(strs.waifu_affinity_reset);
}
else if (oldAff is null)
{
await ReplyConfirmLocalizedAsync(strs.waifu_affinity_set(Format.Bold(u.ToString())));
}
else
{
await ReplyConfirmLocalizedAsync(strs.waifu_affinity_changed(Format.Bold(oldAff.ToString()), Format.Bold(u.ToString())));
await ReplyErrorLocalizedAsync(strs.waifu_affinity_already);
}
return;
}
if (u is null)
{
await ReplyConfirmLocalizedAsync(strs.waifu_affinity_reset);
}
else if (oldAff is null)
{
await ReplyConfirmLocalizedAsync(strs.waifu_affinity_set(Format.Bold(u.ToString())));
}
else
{
await ReplyConfirmLocalizedAsync(strs.waifu_affinity_changed(Format.Bold(oldAff.ToString()), Format.Bold(u.ToString())));
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuLb(int page = 1)
{
page--;
if (page < 0)
return;
if (page > 100)
page = 100;
var waifus = _service.GetTopWaifusAtPage(page);
if (waifus.Count() == 0)
{
await ReplyConfirmLocalizedAsync(strs.waifus_none);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuLb(int page = 1)
var embed = _eb.Create()
.WithTitle(GetText(strs.waifus_top_waifus))
.WithOkColor();
var i = 0;
foreach (var w in waifus)
{
page--;
var j = i++;
embed.AddField("#" + ((page * 9) + j + 1) + " - " + w.Price + CurrencySign, w.ToString(), false);
}
if (page < 0)
return;
await ctx.Channel.EmbedAsync(embed);
}
if (page > 100)
page = 100;
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public Task WaifuInfo([Leftover]IUser target = null)
{
if (target is null)
target = ctx.User;
var waifus = _service.GetTopWaifusAtPage(page);
return InternalWaifuInfo(target.Id, target.ToString());
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public Task WaifuInfo(ulong targetId)
=> InternalWaifuInfo(targetId);
if (waifus.Count() == 0)
{
await ReplyConfirmLocalizedAsync(strs.waifus_none);
return;
}
private Task InternalWaifuInfo(ulong targetId, string name = null)
{
var wi = _service.GetFullWaifuInfoAsync(targetId);
var affInfo = _service.GetAffinityTitle(wi.AffinityCount);
var waifuItems = _service.GetWaifuItems()
.ToDictionary(x => x.ItemEmoji, x => x);
var nobody = GetText(strs.nobody);
var itemsStr = !wi.Items.Any()
? "-"
: string.Join("\n", wi.Items
.Where(x => waifuItems.TryGetValue(x.ItemEmoji, out _))
.OrderBy(x => waifuItems[x.ItemEmoji].Price)
.GroupBy(x => x.ItemEmoji)
.Select(x => $"{x.Key} x{x.Count(),-3}")
.Chunk(2)
.Select(x => string.Join(" ", x)));
var fansStr = wi
.Fans
.Shuffle()
.Take(30)
.Select(x => wi.Claims.Contains(x) ? $"{x} 💞" : x)
.JoinWith('\n');
if (string.IsNullOrWhiteSpace(fansStr))
fansStr = "-";
var embed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.waifu) + " " + (wi.FullName ?? name ?? targetId.ToString()) + " - \"the " +
_service.GetClaimTitle(wi.ClaimCount) + "\"")
.AddField(GetText(strs.price), wi.Price.ToString(), true)
.AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true)
.AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true)
.AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true)
.AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
.AddField("\u200B", "\u200B", true)
.AddField(GetText(strs.fans(wi.Fans.Count)), fansStr, true)
.AddField($"Waifus ({wi.ClaimCount})", wi.ClaimCount == 0
? nobody
: string.Join("\n", wi.Claims.Shuffle().Take(30)), true)
.AddField(GetText(strs.gifts), itemsStr, true);
return ctx.Channel.EmbedAsync(embed);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task WaifuGift(int page = 1)
{
if (--page < 0 || page > (_config.Waifu.Items.Count - 1) / 9)
return;
var waifuItems = _service.GetWaifuItems();
await ctx.SendPaginatedConfirmAsync(page, (cur) =>
{
var embed = _eb.Create()
.WithTitle(GetText(strs.waifus_top_waifus))
.WithTitle(GetText(strs.waifu_gift_shop))
.WithOkColor();
var i = 0;
foreach (var w in waifus)
{
var j = i++;
embed.AddField("#" + ((page * 9) + j + 1) + " - " + w.Price + CurrencySign, w.ToString(), false);
}
waifuItems
.OrderBy(x => x.Negative)
.ThenBy(x => x.Price)
.Skip(9 * cur)
.Take(9)
.ForEach(x => embed
.AddField($"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}",
Format.Bold(x.Price.ToString()) + _config.Currency.Sign,
true));
await ctx.Channel.EmbedAsync(embed);
}
return embed;
}, waifuItems.Count, 9);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public Task WaifuInfo([Leftover]IUser target = null)
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task WaifuGift(string itemName, [Leftover] IUser waifu)
{
if (waifu.Id == ctx.User.Id)
return;
var allItems = _service.GetWaifuItems();
var item = allItems.FirstOrDefault(x => x.Name.ToLowerInvariant() == itemName.ToLowerInvariant());
if (item is null)
{
if (target is null)
target = ctx.User;
return InternalWaifuInfo(target.Id, target.ToString());
await ReplyErrorLocalizedAsync(strs.waifu_gift_not_exist);
return;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public Task WaifuInfo(ulong targetId)
=> InternalWaifuInfo(targetId);
var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, item);
private Task InternalWaifuInfo(ulong targetId, string name = null)
if (sucess)
{
var wi = _service.GetFullWaifuInfoAsync(targetId);
var affInfo = _service.GetAffinityTitle(wi.AffinityCount);
var waifuItems = _service.GetWaifuItems()
.ToDictionary(x => x.ItemEmoji, x => x);
var nobody = GetText(strs.nobody);
var itemsStr = !wi.Items.Any()
? "-"
: string.Join("\n", wi.Items
.Where(x => waifuItems.TryGetValue(x.ItemEmoji, out _))
.OrderBy(x => waifuItems[x.ItemEmoji].Price)
.GroupBy(x => x.ItemEmoji)
.Select(x => $"{x.Key} x{x.Count(),-3}")
.Chunk(2)
.Select(x => string.Join(" ", x)));
var fansStr = wi
.Fans
.Shuffle()
.Take(30)
.Select(x => wi.Claims.Contains(x) ? $"{x} 💞" : x)
.JoinWith('\n');
if (string.IsNullOrWhiteSpace(fansStr))
fansStr = "-";
var embed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.waifu) + " " + (wi.FullName ?? name ?? targetId.ToString()) + " - \"the " +
_service.GetClaimTitle(wi.ClaimCount) + "\"")
.AddField(GetText(strs.price), wi.Price.ToString(), true)
.AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true)
.AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true)
.AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true)
.AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
.AddField("\u200B", "\u200B", true)
.AddField(GetText(strs.fans(wi.Fans.Count)), fansStr, true)
.AddField($"Waifus ({wi.ClaimCount})", wi.ClaimCount == 0
? nobody
: string.Join("\n", wi.Claims.Shuffle().Take(30)), true)
.AddField(GetText(strs.gifts), itemsStr, true);
return ctx.Channel.EmbedAsync(embed);
await ReplyConfirmLocalizedAsync(strs.waifu_gift(
Format.Bold(item.ToString() + " " + item.ItemEmoji),
Format.Bold(waifu.ToString())));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task WaifuGift(int page = 1)
else
{
if (--page < 0 || page > (_config.Waifu.Items.Count - 1) / 9)
return;
var waifuItems = _service.GetWaifuItems();
await ctx.SendPaginatedConfirmAsync(page, (cur) =>
{
var embed = _eb.Create()
.WithTitle(GetText(strs.waifu_gift_shop))
.WithOkColor();
waifuItems
.OrderBy(x => x.Negative)
.ThenBy(x => x.Price)
.Skip(9 * cur)
.Take(9)
.ForEach(x => embed
.AddField($"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}",
Format.Bold(x.Price.ToString()) + _config.Currency.Sign,
true));
return embed;
}, waifuItems.Count, 9);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task WaifuGift(string itemName, [Leftover] IUser waifu)
{
if (waifu.Id == ctx.User.Id)
return;
var allItems = _service.GetWaifuItems();
var item = allItems.FirstOrDefault(x => x.Name.ToLowerInvariant() == itemName.ToLowerInvariant());
if (item is null)
{
await ReplyErrorLocalizedAsync(strs.waifu_gift_not_exist);
return;
}
var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, item);
if (sucess)
{
await ReplyConfirmLocalizedAsync(strs.waifu_gift(
Format.Bold(item.ToString() + " " + item.ItemEmoji),
Format.Bold(waifu.ToString())));
}
else
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
}
}
}

View File

@@ -1,6 +1,5 @@
using Discord;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using System.Threading.Tasks;
using Wof = NadekoBot.Modules.Gambling.Common.WheelOfFortune.WheelOfFortuneGame;
using NadekoBot.Modules.Gambling.Services;
@@ -9,13 +8,13 @@ using System.Collections.Immutable;
using NadekoBot.Common;
using NadekoBot.Services;
namespace NadekoBot.Modules.Gambling
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class Gambling
public class WheelOfFortuneCommands : GamblingSubmodule<GamblingService>
{
public class WheelOfFortuneCommands : GamblingSubmodule<GamblingService>
{
private static readonly ImmutableArray<string> _emojis = new string[] {
private static readonly ImmutableArray<string> _emojis = new string[] {
"⬆",
"↖",
"⬅",
@@ -25,40 +24,39 @@ namespace NadekoBot.Modules.Gambling
"➡",
"↗" }.ToImmutableArray();
private readonly ICurrencyService _cs;
private readonly DbService _db;
private readonly ICurrencyService _cs;
private readonly DbService _db;
public WheelOfFortuneCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConfService)
: base(gamblingConfService)
public WheelOfFortuneCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConfService)
: base(gamblingConfService)
{
_cs = cs;
_db = db;
}
[NadekoCommand, Aliases]
public async Task WheelOfFortune(ShmartNumber amount)
{
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
return;
if (!await _cs.RemoveAsync(ctx.User.Id, "Wheel Of Fortune - bet", amount, gamble: true).ConfigureAwait(false))
{
_cs = cs;
_db = db;
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
[NadekoCommand, Aliases]
public async Task WheelOfFortune(ShmartNumber amount)
{
if (!await CheckBetMandatory(amount).ConfigureAwait(false))
return;
var result = await _service.WheelOfFortuneSpinAsync(ctx.User.Id, amount).ConfigureAwait(false);
if (!await _cs.RemoveAsync(ctx.User.Id, "Wheel Of Fortune - bet", amount, gamble: true).ConfigureAwait(false))
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
var result = await _service.WheelOfFortuneSpinAsync(ctx.User.Id, amount).ConfigureAwait(false);
var wofMultipliers = _config.WheelOfFortune.Multipliers;
await SendConfirmAsync(
Format.Bold($@"{ctx.User.ToString()} won: {result.Amount + CurrencySign}
var wofMultipliers = _config.WheelOfFortune.Multipliers;
await SendConfirmAsync(
Format.Bold($@"{ctx.User.ToString()} won: {result.Amount + CurrencySign}
『{wofMultipliers[1]}』 『{wofMultipliers[0]}』 『{wofMultipliers[7]}』
『{wofMultipliers[2]}』 {_emojis[result.Index]} 『{wofMultipliers[6]}』
『{wofMultipliers[3]}』 『{wofMultipliers[4]}』 『{wofMultipliers[5]}』")).ConfigureAwait(false);
}
}
}
}

View File

@@ -2,146 +2,143 @@
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Extensions;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Games.Common.Acrophobia;
using NadekoBot.Modules.Games.Services;
using NadekoBot.Common;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class AcropobiaCommands : NadekoSubmodule<GamesService>
{
private readonly DiscordSocketClient _client;
namespace NadekoBot.Modules.Games;
public AcropobiaCommands(DiscordSocketClient client)
public partial class Games
{
[Group]
public class AcropobiaCommands : NadekoSubmodule<GamesService>
{
private readonly DiscordSocketClient _client;
public AcropobiaCommands(DiscordSocketClient client)
{
_client = client;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[NadekoOptions(typeof(AcrophobiaGame.Options))]
public async Task Acrophobia(params string[] args)
{
var (options, _) = OptionsParser.ParseFrom(new AcrophobiaGame.Options(), args);
var channel = (ITextChannel)ctx.Channel;
var game = new AcrophobiaGame(options);
if (_service.AcrophobiaGames.TryAdd(channel.Id, game))
{
_client = client;
try
{
game.OnStarted += Game_OnStarted;
game.OnEnded += Game_OnEnded;
game.OnVotingStarted += Game_OnVotingStarted;
game.OnUserVoted += Game_OnUserVoted;
_client.MessageReceived += _client_MessageReceived;
await game.Run().ConfigureAwait(false);
}
finally
{
_client.MessageReceived -= _client_MessageReceived;
_service.AcrophobiaGames.TryRemove(channel.Id, out game);
game.Dispose();
}
}
else
{
await ReplyErrorLocalizedAsync(strs.acro_running).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[NadekoOptions(typeof(AcrophobiaGame.Options))]
public async Task Acrophobia(params string[] args)
Task _client_MessageReceived(SocketMessage msg)
{
var (options, _) = OptionsParser.ParseFrom(new AcrophobiaGame.Options(), args);
var channel = (ITextChannel)ctx.Channel;
if (msg.Channel.Id != ctx.Channel.Id)
return Task.CompletedTask;
var game = new AcrophobiaGame(options);
if (_service.AcrophobiaGames.TryAdd(channel.Id, game))
var _ = Task.Run(async () =>
{
try
{
game.OnStarted += Game_OnStarted;
game.OnEnded += Game_OnEnded;
game.OnVotingStarted += Game_OnVotingStarted;
game.OnUserVoted += Game_OnUserVoted;
_client.MessageReceived += _client_MessageReceived;
await game.Run().ConfigureAwait(false);
var success = await game.UserInput(msg.Author.Id, msg.Author.ToString(), msg.Content)
.ConfigureAwait(false);
if (success)
await msg.DeleteAsync().ConfigureAwait(false);
}
finally
{
_client.MessageReceived -= _client_MessageReceived;
_service.AcrophobiaGames.TryRemove(channel.Id, out game);
game.Dispose();
}
}
else
{
await ReplyErrorLocalizedAsync(strs.acro_running).ConfigureAwait(false);
}
catch { }
});
Task _client_MessageReceived(SocketMessage msg)
{
if (msg.Channel.Id != ctx.Channel.Id)
return Task.CompletedTask;
return Task.CompletedTask;
}
}
var _ = Task.Run(async () =>
{
try
{
var success = await game.UserInput(msg.Author.Id, msg.Author.ToString(), msg.Content)
.ConfigureAwait(false);
if (success)
await msg.DeleteAsync().ConfigureAwait(false);
}
catch { }
});
private Task Game_OnStarted(AcrophobiaGame game)
{
var embed = _eb.Create().WithOkColor()
.WithTitle(GetText(strs.acrophobia))
.WithDescription(GetText(strs.acro_started(Format.Bold(string.Join(".", game.StartingLetters)))))
.WithFooter(GetText(strs.acro_started_footer(game.Opts.SubmissionTime)));
return Task.CompletedTask;
}
return ctx.Channel.EmbedAsync(embed);
}
private Task Game_OnUserVoted(string user)
{
return SendConfirmAsync(
GetText(strs.acrophobia),
GetText(strs.acro_vote_cast(Format.Bold(user))));
}
private async Task Game_OnVotingStarted(AcrophobiaGame game, ImmutableArray<KeyValuePair<AcrophobiaUser, int>> submissions)
{
if (submissions.Length == 0)
{
await SendErrorAsync(GetText(strs.acrophobia), GetText(strs.acro_ended_no_sub)).ConfigureAwait(false);
return;
}
if (submissions.Length == 1)
{
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
.WithDescription(GetText(strs.acro_winner_only(Format.Bold(submissions.First().Key.UserName))))
.WithFooter(submissions.First().Key.Input))
.ConfigureAwait(false);
return;
}
private Task Game_OnStarted(AcrophobiaGame game)
{
var embed = _eb.Create().WithOkColor()
.WithTitle(GetText(strs.acrophobia))
.WithDescription(GetText(strs.acro_started(Format.Bold(string.Join(".", game.StartingLetters)))))
.WithFooter(GetText(strs.acro_started_footer(game.Opts.SubmissionTime)));
return ctx.Channel.EmbedAsync(embed);
}
private Task Game_OnUserVoted(string user)
{
return SendConfirmAsync(
GetText(strs.acrophobia),
GetText(strs.acro_vote_cast(Format.Bold(user))));
}
private async Task Game_OnVotingStarted(AcrophobiaGame game, ImmutableArray<KeyValuePair<AcrophobiaUser, int>> submissions)
{
if (submissions.Length == 0)
{
await SendErrorAsync(GetText(strs.acrophobia), GetText(strs.acro_ended_no_sub)).ConfigureAwait(false);
return;
}
if (submissions.Length == 1)
{
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
.WithDescription(GetText(strs.acro_winner_only(Format.Bold(submissions.First().Key.UserName))))
.WithFooter(submissions.First().Key.Input))
.ConfigureAwait(false);
return;
}
var i = 0;
var embed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.acrophobia) + " - " + GetText(strs.submissions_closed))
.WithDescription(GetText(strs.acro_nym_was(Format.Bold(string.Join(".", game.StartingLetters)) + "\n" +
$@"--
var i = 0;
var embed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.acrophobia) + " - " + GetText(strs.submissions_closed))
.WithDescription(GetText(strs.acro_nym_was(Format.Bold(string.Join(".", game.StartingLetters)) + "\n" +
$@"--
{submissions.Aggregate("", (agg, cur) => agg + $"`{++i}.` **{cur.Key.Input}**\n")}
--")))
.WithFooter(GetText(strs.acro_vote));
.WithFooter(GetText(strs.acro_vote));
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
private async Task Game_OnEnded(AcrophobiaGame game, ImmutableArray<KeyValuePair<AcrophobiaUser, int>> votes)
private async Task Game_OnEnded(AcrophobiaGame game, ImmutableArray<KeyValuePair<AcrophobiaUser, int>> votes)
{
if (!votes.Any() || votes.All(x => x.Value == 0))
{
if (!votes.Any() || votes.All(x => x.Value == 0))
{
await SendErrorAsync(GetText(strs.acrophobia), GetText(strs.acro_no_votes_cast)).ConfigureAwait(false);
return;
}
var table = votes.OrderByDescending(v => v.Value);
var winner = table.First();
var embed = _eb.Create().WithOkColor()
.WithTitle(GetText(strs.acrophobia))
.WithDescription(GetText(strs.acro_winner(Format.Bold(winner.Key.UserName),
Format.Bold(winner.Value.ToString()))))
.WithFooter(winner.Key.Input);
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
await SendErrorAsync(GetText(strs.acrophobia), GetText(strs.acro_no_votes_cast)).ConfigureAwait(false);
return;
}
var table = votes.OrderByDescending(v => v.Value);
var winner = table.First();
var embed = _eb.Create().WithOkColor()
.WithTitle(GetText(strs.acrophobia))
.WithDescription(GetText(strs.acro_winner(Format.Bold(winner.Key.UserName),
Format.Bold(winner.Value.ToString()))))
.WithFooter(winner.Key.Input);
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
}
}

View File

@@ -1,60 +1,57 @@
using Discord;
using Discord.Commands;
using System;
using System.Threading.Tasks;
using NadekoBot.Common;
using NadekoBot.Common.Attributes;
using NadekoBot.Services;
using NadekoBot.Db;
using NadekoBot.Modules.Administration;
using NadekoBot.Modules.Games.Services;
using NadekoBot.Modules.Games.Common.ChatterBot;
namespace NadekoBot.Modules.Games
namespace NadekoBot.Modules.Games;
public partial class Games
{
public partial class Games
[Group]
public class ChatterBotCommands : NadekoSubmodule<ChatterBotService>
{
[Group]
public class ChatterBotCommands : NadekoSubmodule<ChatterBotService>
private readonly DbService _db;
public ChatterBotCommands(DbService db)
{
private readonly DbService _db;
public ChatterBotCommands(DbService db)
{
_db = db;
}
[NoPublicBot]
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
public async Task Cleverbot()
{
var channel = (ITextChannel)ctx.Channel;
if (_service.ChatterBotGuilds.TryRemove(channel.Guild.Id, out _))
{
using (var uow = _db.GetDbContext())
{
uow.GuildConfigs.SetCleverbotEnabled(ctx.Guild.Id, false);
await uow.SaveChangesAsync();
}
await ReplyConfirmLocalizedAsync(strs.cleverbot_disabled).ConfigureAwait(false);
return;
}
_service.ChatterBotGuilds.TryAdd(channel.Guild.Id, new Lazy<IChatterBotSession>(() => _service.CreateSession(), true));
using (var uow = _db.GetDbContext())
{
uow.GuildConfigs.SetCleverbotEnabled(ctx.Guild.Id, true);
await uow.SaveChangesAsync();
}
await ReplyConfirmLocalizedAsync(strs.cleverbot_enabled).ConfigureAwait(false);
}
_db = db;
}
[NoPublicBot]
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
public async Task Cleverbot()
{
var channel = (ITextChannel)ctx.Channel;
if (_service.ChatterBotGuilds.TryRemove(channel.Guild.Id, out _))
{
using (var uow = _db.GetDbContext())
{
uow.GuildConfigs.SetCleverbotEnabled(ctx.Guild.Id, false);
await uow.SaveChangesAsync();
}
await ReplyConfirmLocalizedAsync(strs.cleverbot_disabled).ConfigureAwait(false);
return;
}
_service.ChatterBotGuilds.TryAdd(channel.Guild.Id, new Lazy<IChatterBotSession>(() => _service.CreateSession(), true));
using (var uow = _db.GetDbContext())
{
uow.GuildConfigs.SetCleverbotEnabled(ctx.Guild.Id, true);
await uow.SaveChangesAsync();
}
await ReplyConfirmLocalizedAsync(strs.cleverbot_enabled).ConfigureAwait(false);
}
}
}

Some files were not shown because too many files have changed in this diff Show More