Restructured folders and project names, ci should be fixed

This commit is contained in:
Kwoth
2021-06-17 23:40:48 +02:00
parent 7aca29ae8a
commit 91ecf9ca41
788 changed files with 204 additions and 146 deletions

View File

@@ -0,0 +1,335 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Core.Common.TypeReaders.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration : NadekoModule<AdministrationService>
{
public enum List
{
List = 0,
Ls = 0
}
[NadekoCommand, Usage, Description, 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 null) && (time.Time < TimeSpan.FromSeconds(0) || time.Time > TimeSpan.FromHours(6)))
return;
await ((ITextChannel) Context.Channel).ModifyAsync(tcp =>
{
tcp.SlowModeInterval = seconds;
});
await Context.OkAsync();
}
[NadekoCommand, Usage, Description, 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 = new EmbedBuilder()
.WithOkColor()
.WithTitle(GetText("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("channel_delmsgoncmd"), str);
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
public enum Server
{
Server
}
[NadekoCommand, Usage, Description, 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("delmsg_on").ConfigureAwait(false);
}
else
{
_service.DeleteMessagesOnCommand.TryRemove(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync("delmsg_off").ConfigureAwait(false);
}
}
public enum Channel
{
Channel,
Ch,
Chnl,
Chan
}
public enum State
{
Enable,
Disable,
Inherit
}
[NadekoCommand, Usage, Description, 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, Usage, Description, 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("delmsg_channel_off").ConfigureAwait(false);
}
else if (s == State.Enable)
{
await ReplyConfirmLocalizedAsync("delmsg_channel_on").ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync("delmsg_channel_inherit").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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("deafen").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("undeafen").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task DelVoiChanl([Leftover] IVoiceChannel voiceChannel)
{
await voiceChannel.DeleteAsync().ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("delvoich", Format.Bold(voiceChannel.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("createvoich", Format.Bold(ch.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async Task DelTxtChanl([Leftover] ITextChannel toDelete)
{
await toDelete.DeleteAsync().ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("deltextchan", Format.Bold(toDelete.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("createtextchan", Format.Bold(txtCh.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("set_topic").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("set_channel_name").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("nsfw_set_false").ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("nsfw_set_true").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[Priority(0)]
public Task Edit(ulong messageId, [Leftover] string text)
=> Edit((ITextChannel) ctx.Channel, messageId, text);
[NadekoCommand, Usage, Description, 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("insuf_perms_u").ConfigureAwait(false);
return;
}
if (!botPerms.Has(ChannelPermission.ViewChannel))
{
await ReplyErrorLocalizedAsync("insuf_perms_i").ConfigureAwait(false);
return;
}
await _service.EditMessage(ctx, channel, messageId, text);
}
[NadekoCommand, Usage, Description, 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, Usage, Description, 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("insuf_perms_u").ConfigureAwait(false);
return;
}
if (!botPerms.Has(ChannelPermission.ManageMessages))
{
await ReplyErrorLocalizedAsync("insuf_perms_i").ConfigureAwait(false);
return;
}
var msg = await channel.GetMessageAsync(messageId).ConfigureAwait(false);
if (msg == null)
{
await ReplyErrorLocalizedAsync("msg_not_found").ConfigureAwait(false);
return;
}
if (time == 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("time_too_long").ConfigureAwait(false);
return;
}
await ctx.OkAsync();
}
}
}

View File

@@ -0,0 +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
{
public partial class Administration
{
[Group]
public class AutoAssignRoleCommands : NadekoSubmodule<AutoAssignRoleService>
{
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(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)
{
await ReplyErrorLocalizedAsync("hierarchy");
return;
}
var roles = await _service.ToggleAarAsync(ctx.Guild.Id, role.Id);
if (roles.Count == 0)
{
await ReplyConfirmLocalizedAsync("aar_disabled");
}
else if (roles.Contains(role.Id))
{
await AutoAssignRole();
}
else
{
await ReplyConfirmLocalizedAsync("aar_role_removed", Format.Bold(role.ToString()));
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async Task AutoAssignRole()
{
if (!_service.TryGetRoles(ctx.Guild.Id, out var roles))
{
await ReplyConfirmLocalizedAsync("aar_none");
return;
}
var existing = roles.Select(rid => ctx.Guild.GetRole(rid)).Where(r => !(r is null))
.ToList();
if (existing.Count != roles.Count)
{
await _service.SetAarRolesAsync(ctx.Guild.Id, existing.Select(x => x.Id));
}
await ReplyConfirmLocalizedAsync("aar_roles",
'\n' + existing.Select(x => Format.Bold(x.ToString()))
.JoinWith(",\n"));
}
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using Discord;
using NadekoBot.Common.Collections;
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Modules.Administration.Common
{
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);
}
}

View File

@@ -0,0 +1,14 @@
using Discord;
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Modules.Administration.Common
{
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; }
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using Discord;
namespace NadekoBot.Modules.Administration.Common
{
public sealed class UserSpamStats : IDisposable
{
public int Count => timers.Count;
public string LastMessage { get; set; }
private ConcurrentQueue<Timer> timers { get; }
public UserSpamStats(IUserMessage msg)
{
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()))
{
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);
}
}
public void Dispose()
{
while (timers.TryDequeue(out var old))
old.Change(Timeout.Infinite, Timeout.Infinite);
}
}
}

View File

@@ -0,0 +1,132 @@
using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using System;
using System.Threading.Tasks;
using Discord;
using NadekoBot.Core.Modules.Administration.Services;
using System.Linq;
#if !GLOBAL_NADEKO
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
[OwnerOnly]
public class DangerousCommands : NadekoSubmodule<DangerousCommandsService>
{
private async Task InternalExecSql(string sql, params object[] reps)
{
sql = string.Format(sql, reps);
try
{
var embed = new EmbedBuilder()
.WithTitle(GetText("sql_confirm_exec"))
.WithDescription(Format.Code(sql));
if (!await PromptUserConfirmAsync(embed).ConfigureAwait(false))
{
return;
}
var res = await _service.ExecuteSql(sql).ConfigureAwait(false);
await ctx.Channel.SendConfirmAsync(res.ToString()).ConfigureAwait(false);
}
catch (Exception ex)
{
await ctx.Channel.SendErrorAsync(ex.ToString()).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task SqlSelect([Leftover]string sql)
{
var result = _service.SelectSql(sql);
return ctx.SendPaginatedConfirmAsync(0, (cur) =>
{
var items = result.Results.Skip(cur * 20).Take(20);
if (!items.Any())
{
return new EmbedBuilder()
.WithErrorColor()
.WithFooter(sql)
.WithDescription("-");
}
return new EmbedBuilder()
.WithOkColor()
.WithFooter(sql)
.WithTitle(string.Join(" ║ ", result.ColumnNames))
.WithDescription(string.Join('\n', items.Select(x => string.Join(" ║ ", x))));
}, result.Results.Count, 20);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task SqlExec([Leftover]string sql) =>
InternalExecSql(sql);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task DeleteWaifus() =>
SqlExec(DangerousCommandsService.WaifusDeleteSql);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task DeleteWaifu(IUser user) =>
DeleteWaifu(user.Id);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task DeleteWaifu(ulong userId) =>
InternalExecSql(DangerousCommandsService.WaifuDeleteSql, userId);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task DeleteCurrency() =>
SqlExec(DangerousCommandsService.CurrencyDeleteSql);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task DeletePlaylists() =>
SqlExec(DangerousCommandsService.MusicPlaylistDeleteSql);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task DeleteXp() =>
SqlExec(DangerousCommandsService.XpDeleteSql);
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task PurgeUser(ulong userId)
{
var embed = new EmbedBuilder()
.WithDescription(GetText("purge_user_confirm", Format.Bold(userId.ToString())));
if (!await PromptUserConfirmAsync(embed).ConfigureAwait(false))
{
return;
}
await _service.PurgeUserAsync(userId);
await ctx.OkAsync();
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task PurgeUser([Leftover]IUser user)
=> PurgeUser(user.Id);
//[NadekoCommand, Usage, Description, Aliases]
//[OwnerOnly]
//public Task DeleteUnusedCrnQ() =>
// SqlExec(DangerousCommandsService.DeleteUnusedCustomReactionsAndQuotes);
}
}
}
#endif

View File

@@ -0,0 +1,87 @@
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Common.TypeReaders;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[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, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task DiscordPermOverride(CommandOrCrInfo cmd, params GuildPerm[] perms)
{
if (perms is null || perms.Length == 0)
{
await _service.RemoveOverride(ctx.Guild.Id, cmd.Name);
await ReplyConfirmLocalizedAsync("perm_override_reset");
return;
}
var aggregatePerms = perms.Aggregate((acc, seed) => seed | acc);
await _service.AddOverride(Context.Guild.Id, cmd.Name, aggregatePerms);
await ReplyConfirmLocalizedAsync("perm_override",
Format.Bold(aggregatePerms.ToString()),
Format.Code(cmd.Name));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task DiscordPermOverrideReset()
{
var result = await PromptUserConfirmAsync(new EmbedBuilder()
.WithOkColor()
.WithDescription(GetText("perm_override_all_confirm")));
if (!result)
return;
await _service.ClearAllOverrides(Context.Guild.Id);
await ReplyConfirmLocalizedAsync("perm_override_all");
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task DiscordPermOverrideList(int page = 1)
{
if (--page < 0)
return;
var overrides = await _service.GetAllOverrides(Context.Guild.Id);
await ctx.SendPaginatedConfirmAsync(page, curPage =>
{
var eb = new EmbedBuilder()
.WithTitle(GetText("perm_overrides"))
.WithOkColor();
var thisPageOverrides = overrides
.Skip(9 * curPage)
.Take(9)
.ToList();
if (thisPageOverrides.Count == 0)
eb.WithDescription(GetText("perm_override_page_none"));
else
eb.WithDescription(string.Join("\n",
thisPageOverrides.Select(ov => $"{ov.Command} => {ov.Perm.ToString()}")));
return eb;
}, overrides.Count, 9, true);
}
}
}
}

View File

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

View File

@@ -0,0 +1,263 @@
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
{
public partial class Administration
{
[Group]
public class LocalizationCommands : NadekoSubmodule
{
private static readonly IReadOnlyDictionary<string, string> supportedLocales =
new Dictionary<string, string>()
{
{"ar", "العربية"},
{"zh-TW", "繁體中文, 台灣"},
{"zh-CN", "简体中文, 中华人民共和国"},
{"nl-NL", "Nederlands, Nederland"},
{"en-US", "English, United States"},
{"fr-FR", "Français, France"},
{"cs-CZ", "Čeština, Česká republika"},
{"da-DK", "Dansk, Danmark"},
{"de-DE", "Deutsch, Deutschland"},
{"he-IL", "עברית, ישראל"},
{"hu-HU", "Magyar, Magyarország"},
{"id-ID", "Bahasa Indonesia, Indonesia"},
{"it-IT", "Italiano, Italia"},
{"ja-JP", "日本語, 日本"},
{"ko-KR", "한국어, 대한민국"},
{"nb-NO", "Norsk, Norge"},
{"pl-PL", "Polski, Polska"},
{"pt-BR", "Português Brasileiro, Brasil"},
{"ro-RO", "Română, România"},
{"ru-RU", "Русский, Россия"},
{"sr-Cyrl-RS", "Српски, Србија"},
{"es-ES", "Español, España"},
{"sv-SE", "Svenska, Sverige"},
{"tr-TR", "Türkçe, Türkiye"},
{"ts-TS", "Tsundere, You Baka"},
{"uk-UA", "Українська, Україна"}
};
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task LanguageSet()
{
await ReplyConfirmLocalizedAsync("lang_set_show", Format.Bold(_cultureInfo.ToString()),
Format.Bold(_cultureInfo.NativeName))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(1)]
public async Task LanguageSet(string name)
{
try
{
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("lang_set", Format.Bold(ci.ToString()), Format.Bold(ci.NativeName))
.ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalizedAsync("lang_set_fail").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task LanguageSetDefault()
{
var cul = Localization.DefaultCultureInfo;
await ReplyConfirmLocalizedAsync("lang_set_bot_show", cul, cul.NativeName).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task LanguageSetDefault(string name)
{
try
{
CultureInfo ci;
if (name.Trim().ToLowerInvariant() == "default")
{
Localization.ResetDefaultCulture();
ci = Localization.DefaultCultureInfo;
}
else
{
ci = new CultureInfo(name);
Localization.SetDefaultCulture(ci);
}
await ReplyConfirmLocalizedAsync("lang_set_bot", Format.Bold(ci.ToString()),
Format.Bold(ci.NativeName)).ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalizedAsync("lang_set_fail").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task LanguagesList()
{
await ctx.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("lang_list"))
.WithDescription(string.Join("\n",
supportedLocales.Select(x => $"{Format.Code(x.Key),-10} => {x.Value}")))).ConfigureAwait(false);
}
}
}
}
/* list of language codes for reference.
* taken from https://github.com/dotnet/coreclr/blob/ee5862c6a257e60e263537d975ab6c513179d47f/src/mscorlib/src/System/Globalization/CultureData.cs#L192
{ "029", "en-029" },
{ "AE", "ar-AE" },
{ "AF", "prs-AF" },
{ "AL", "sq-AL" },
{ "AM", "hy-AM" },
{ "AR", "es-AR" },
{ "AT", "de-AT" },
{ "AU", "en-AU" },
{ "AZ", "az-Cyrl-AZ" },
{ "BA", "bs-Latn-BA" },
{ "BD", "bn-BD" },
{ "BE", "nl-BE" },
{ "BG", "bg-BG" },
{ "BH", "ar-BH" },
{ "BN", "ms-BN" },
{ "BO", "es-BO" },
{ "BR", "pt-BR" },
{ "BY", "be-BY" },
{ "BZ", "en-BZ" },
{ "CA", "en-CA" },
{ "CH", "it-CH" },
{ "CL", "es-CL" },
{ "CN", "zh-CN" },
{ "CO", "es-CO" },
{ "CR", "es-CR" },
{ "CS", "sr-Cyrl-CS" },
{ "CZ", "cs-CZ" },
{ "DE", "de-DE" },
{ "DK", "da-DK" },
{ "DO", "es-DO" },
{ "DZ", "ar-DZ" },
{ "EC", "es-EC" },
{ "EE", "et-EE" },
{ "EG", "ar-EG" },
{ "ES", "es-ES" },
{ "ET", "am-ET" },
{ "FI", "fi-FI" },
{ "FO", "fo-FO" },
{ "FR", "fr-FR" },
{ "GB", "en-GB" },
{ "GE", "ka-GE" },
{ "GL", "kl-GL" },
{ "GR", "el-GR" },
{ "GT", "es-GT" },
{ "HK", "zh-HK" },
{ "HN", "es-HN" },
{ "HR", "hr-HR" },
{ "HU", "hu-HU" },
{ "ID", "id-ID" },
{ "IE", "en-IE" },
{ "IL", "he-IL" },
{ "IN", "hi-IN" },
{ "IQ", "ar-IQ" },
{ "IR", "fa-IR" },
{ "IS", "is-IS" },
{ "IT", "it-IT" },
{ "IV", "" },
{ "JM", "en-JM" },
{ "JO", "ar-JO" },
{ "JP", "ja-JP" },
{ "KE", "sw-KE" },
{ "KG", "ky-KG" },
{ "KH", "km-KH" },
{ "KR", "ko-KR" },
{ "KW", "ar-KW" },
{ "KZ", "kk-KZ" },
{ "LA", "lo-LA" },
{ "LB", "ar-LB" },
{ "LI", "de-LI" },
{ "LK", "si-LK" },
{ "LT", "lt-LT" },
{ "LU", "lb-LU" },
{ "LV", "lv-LV" },
{ "LY", "ar-LY" },
{ "MA", "ar-MA" },
{ "MC", "fr-MC" },
{ "ME", "sr-Latn-ME" },
{ "MK", "mk-MK" },
{ "MN", "mn-MN" },
{ "MO", "zh-MO" },
{ "MT", "mt-MT" },
{ "MV", "dv-MV" },
{ "MX", "es-MX" },
{ "MY", "ms-MY" },
{ "NG", "ig-NG" },
{ "NI", "es-NI" },
{ "NL", "nl-NL" },
{ "NO", "nn-NO" },
{ "NP", "ne-NP" },
{ "NZ", "en-NZ" },
{ "OM", "ar-OM" },
{ "PA", "es-PA" },
{ "PE", "es-PE" },
{ "PH", "en-PH" },
{ "PK", "ur-PK" },
{ "PL", "pl-PL" },
{ "PR", "es-PR" },
{ "PT", "pt-PT" },
{ "PY", "es-PY" },
{ "QA", "ar-QA" },
{ "RO", "ro-RO" },
{ "RS", "sr-Latn-RS" },
{ "RU", "ru-RU" },
{ "RW", "rw-RW" },
{ "SA", "ar-SA" },
{ "SE", "sv-SE" },
{ "SG", "zh-SG" },
{ "SI", "sl-SI" },
{ "SK", "sk-SK" },
{ "SN", "wo-SN" },
{ "SV", "es-SV" },
{ "SY", "ar-SY" },
{ "TH", "th-TH" },
{ "TJ", "tg-Cyrl-TJ" },
{ "TM", "tk-TM" },
{ "TN", "ar-TN" },
{ "TR", "tr-TR" },
{ "TT", "en-TT" },
{ "TW", "zh-TW" },
{ "UA", "uk-UA" },
{ "US", "en-US" },
{ "UY", "es-UY" },
{ "UZ", "uz-Cyrl-UZ" },
{ "VE", "es-VE" },
{ "VN", "vi-VN" },
{ "YE", "ar-YE" },
{ "ZA", "af-ZA" },
{ "ZW", "en-ZW" }
*/

View File

@@ -0,0 +1,134 @@
#if !GLOBAL_NADEKO
using Discord;
using Discord.Commands;
using NadekoBot.Common;
using NadekoBot.Common.Attributes;
using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
using static NadekoBot.Modules.Administration.Services.LogCommandService;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
[NoPublicBot]
public class LogCommands : NadekoSubmodule<LogCommandService>
{
public enum EnableDisable
{
Enable,
Disable
}
[NadekoCommand, Usage, Description, 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("log_all").ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("log_disabled").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task LogIgnore()
{
var channel = (ITextChannel)ctx.Channel;
var removed = _service.LogIgnore(ctx.Guild.Id, ctx.Channel.Id);
if (!removed)
await ReplyConfirmLocalizedAsync("log_ignore", Format.Bold(channel.Mention + "(" + channel.Id + ")")).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("log_not_ignore", Format.Bold(channel.Mention + "(" + channel.Id + ")")).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task LogEvents()
{
_service.GuildLogSettings.TryGetValue(ctx.Guild.Id, out LogSetting l);
var str = string.Join("\n", Enum.GetNames(typeof(LogType))
.Select(x =>
{
var val = l == null ? null : GetLogProperty(l, Enum.Parse<LogType>(x));
if (val != null)
return $"{Format.Bold(x)} <#{val}>";
return Format.Bold(x);
}));
await ctx.Channel.SendConfirmAsync(Format.Bold(GetText("log_events")) + "\n" +
str)
.ConfigureAwait(false);
}
private static ulong? GetLogProperty(LogSetting l, LogType type)
{
switch (type)
{
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, Usage, Description, 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("log", Format.Bold(type.ToString())).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("log_stop", Format.Bold(type.ToString())).ConfigureAwait(false);
}
}
}
}
#endif

View File

@@ -0,0 +1,91 @@
//using Discord.Commands;
//using NadekoBot.Common.Attributes;
//using NadekoBot.Modules.Administration.Services;
//using NadekoBot.Extensions;
//using System;
//using System.IO;
//using System.Reflection;
//using System.Text.RegularExpressions;
//using System.Threading.Tasks;
//using System.Linq;
//namespace NadekoBot.Modules.Administration
//{
// public partial class Administration
// {
// [Group]
// public class PackagesCommands : NadekoSubmodule<PackagesService>
// {
// private readonly NadekoBot _bot;
// public PackagesCommands(NadekoBot bot)
// {
// _bot = bot;
// }
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// public async Task PackageList()
// {
// _service.ReloadAvailablePackages();
// await Context.Channel.SendConfirmAsync(
// string.Join(
// "\n",
// _service.Packages
// .Select(x => _bot.LoadedPackages.Contains(x)
// ? "【✘】" + x
// : "【 】" + x)));
// }
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// [OwnerOnly]
// public async Task PackageUnload(string name)
// {
// if (name.Contains(":") || name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
// return;
// name = name.ToTitleCase();
// var package = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory,
// "modules",
// $"NadekoBot.Modules.{name}",
// $"NadekoBot.Modules.{name}.dll"));
// await _bot.UnloadPackage(name).ConfigureAwait(false);
// await ReplyAsync(":ok:");
// }
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// [OwnerOnly]
// public async Task PackageLoad(string name)
// {
// if (name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
// return;
// name = name.ToTitleCase();
// if (await _bot.LoadPackage(name))
// await ReplyAsync(":ok:");
// else
// await ReplyAsync(":x:");
// }
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// [OwnerOnly]
// public async Task PackageReload(string name)
// {
// if (name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
// return;
// name = name.ToTitleCase();
// if (await _bot.UnloadPackage(name))
// {
// await _bot.LoadPackage(name);
// await ReplyAsync(":ok:");
// }
// else
// await ReplyAsync(":x:");
// }
// }
// }
//}

View File

@@ -0,0 +1,237 @@
using Discord;
using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Core.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
{
public partial class Administration
{
[Group]
public class MuteCommands : NadekoSubmodule<MuteService>
{
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))
{
await ReplyErrorLocalizedAsync("mute_perms").ConfigureAwait(false);
return false;
}
return true;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async Task MuteRole([Leftover] IRole role = null)
{
if (role is null)
{
var muteRole = await _service.GetMuteRole(ctx.Guild).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("mute_role", Format.Code(muteRole.Name)).ConfigureAwait(false);
return;
}
if (Context.User.Id != Context.Guild.OwnerId &&
role.Position >= ((SocketGuildUser) Context.User).Roles.Max(x => x.Position))
{
await ReplyErrorLocalizedAsync("insuf_perms_u").ConfigureAwait(false);
return;
}
await _service.SetMuteRoleAsync(ctx.Guild.Id, role.Name).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("mute_role_set").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
[Priority(0)]
public async Task Mute(IGuildUser target, [Leftover] string reason = "")
{
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, target))
return;
await _service.MuteUser(target, ctx.User, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("user_muted", Format.Bold(target.ToString())).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex.ToString());
await ReplyErrorLocalizedAsync("mute_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.TimedMute(user, ctx.User, time.Time, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("user_muted_time", Format.Bold(user.ToString()), (int)time.Time.TotalMinutes).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex, "Error in mute command");
await ReplyErrorLocalizedAsync("mute_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
public async Task Unmute(IGuildUser user, [Leftover] string reason = "")
{
try
{
await _service.UnmuteUser(user.GuildId, user.Id, ctx.User, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("user_unmuted", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync("mute_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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;
await _service.MuteUser(user, ctx.User, MuteType.Chat, reason: reason).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("user_chat_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex.ToString());
await ReplyErrorLocalizedAsync("mute_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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("user_chat_mute_time", Format.Bold(user.ToString()), (int)time.Time.TotalMinutes).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex.ToString());
await ReplyErrorLocalizedAsync("mute_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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("user_chat_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync("mute_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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("user_voice_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync("mute_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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("user_voice_mute_time", Format.Bold(user.ToString()), (int)time.Time.TotalMinutes).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync("mute_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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("user_voice_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync("mute_error").ConfigureAwait(false);
}
}
}
}
}

View File

@@ -0,0 +1,69 @@
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
{
public partial class Administration
{
[Group]
public class PlayingRotateCommands : NadekoSubmodule<PlayingRotateService>
{
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task RotatePlaying()
{
if (_service.ToggleRotatePlaying())
await ReplyConfirmLocalizedAsync("ropl_enabled").ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("ropl_disabled").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task AddPlaying(ActivityType t, [Leftover] string status)
{
await _service.AddPlaying(t, status).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("ropl_added").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ListPlaying()
{
var statuses = _service.GetRotatingStatuses();
if (!statuses.Any())
{
await ReplyErrorLocalizedAsync("ropl_not_set").ConfigureAwait(false);
}
else
{
var i = 1;
await ReplyConfirmLocalizedAsync("ropl_list",
string.Join("\n\t", statuses.Select(rs => $"`{i++}.` *{rs.Type}* {rs.Status}")))
.ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task RemovePlaying(int index)
{
index -= 1;
var msg = await _service.RemovePlayingAsync(index).ConfigureAwait(false);
if (msg == null)
return;
await ReplyConfirmLocalizedAsync("reprm", msg).ConfigureAwait(false);
}
}
}
}

View File

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

View File

@@ -0,0 +1,295 @@
using System;
using Discord;
using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Core.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.Core.Common.TypeReaders.Models;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class ProtectionCommands : NadekoSubmodule<ProtectionService>
{
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task AntiAlt()
{
if (await _service.TryStopAntiAlt(ctx.Guild.Id))
{
await ReplyConfirmLocalizedAsync("prot_disable", "Anti-Alt");
return;
}
await ReplyErrorLocalizedAsync("protection_not_running", "Anti-Alt");
}
[NadekoCommand, Usage, Description, 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;
if (minAgeMinutes < 1 || punishTimeMinutes < 0)
return;
await _service.StartAntiAltAsync(ctx.Guild.Id, minAgeMinutes, action, (int?)punishTime?.Time.TotalMinutes ?? 0);
await ctx.OkAsync();
}
[NadekoCommand, Usage, Description, 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;
await _service.StartAntiAltAsync(ctx.Guild.Id, minAgeMinutes, action, roleId: role.Id);
await ctx.OkAsync();
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public Task AntiRaid()
{
if (_service.TryStopAntiRaid(ctx.Guild.Id))
{
return ReplyConfirmLocalizedAsync("prot_disable", "Anti-Raid");
}
else
{
return ReplyErrorLocalizedAsync("protection_not_running", "Anti-Raid");
}
}
[NadekoCommand, Usage, Description, 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, Usage, Description, 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("punishment_unsupported", action);
return;
}
if (userThreshold < 2 || userThreshold > 30)
{
await ReplyErrorLocalizedAsync("raid_cnt", 2, 30).ConfigureAwait(false);
return;
}
if (seconds < 2 || seconds > 300)
{
await ReplyErrorLocalizedAsync("raid_time", 2, 300).ConfigureAwait(false);
return;
}
if (!(punishTime is null))
{
if (!_service.IsDurationAllowed(action))
{
await ReplyErrorLocalizedAsync("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 == null)
{
return;
}
await ctx.Channel.SendConfirmAsync(GetText("prot_enable", "Anti-Raid"),
$"{ctx.User.Mention} {GetAntiRaidString(stats)}")
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public Task AntiSpam()
{
if (_service.TryStopAntiSpam(ctx.Guild.Id))
{
return ReplyConfirmLocalizedAsync("prot_disable", "Anti-Spam");
}
else
{
return ReplyErrorLocalizedAsync("protection_not_running", "Anti-Spam");
}
}
[NadekoCommand, Usage, Description, 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, Usage, Description, 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, Usage, Description, 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 null))
{
if (!_service.IsDurationAllowed(action))
{
await ReplyErrorLocalizedAsync("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 ctx.Channel.SendConfirmAsync(GetText("prot_enable", "Anti-Spam"),
$"{ctx.User.Mention} {GetAntiSpamString(stats)}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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)
{
await ReplyErrorLocalizedAsync("protection_not_running", "Anti-Spam").ConfigureAwait(false);
return;
}
await ReplyConfirmLocalizedAsync(added.Value ? "spam_ignore" : "spam_not_ignore", "Anti-Spam").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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)
{
await ReplyConfirmLocalizedAsync("prot_none").ConfigureAwait(false);
return;
}
var embed = new EmbedBuilder().WithOkColor()
.WithTitle(GetText("prot_active"));
if (spam != null)
embed.AddField(efb => efb.WithName("Anti-Spam")
.WithValue(GetAntiSpamString(spam).TrimTo(1024))
.WithIsInline(true));
if (raid != null)
embed.AddField(efb => efb.WithName("Anti-Raid")
.WithValue(GetAntiRaidString(raid).TrimTo(1024))
.WithIsInline(true));
if (!(alt is null))
embed.AddField("Anti-Alt", GetAntiAltString(alt), true);
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
private string GetAntiAltString(AntiAltStats alt)
=> GetText("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)
{
add = $" ({TimeSpan.FromMinutes(settings.MuteTime):hh\\hmm\\m})";
}
return GetText("spam_stats",
Format.Bold(settings.MessageThreshold.ToString()),
Format.Bold(settings.Action.ToString() + add),
ignoredString);
}
private string GetAntiRaidString(AntiRaidStats stats)
{
var actionString = Format.Bold(stats.AntiRaidSettings.Action.ToString());
if (stats.AntiRaidSettings.PunishDuration > 0)
{
actionString += $" **({TimeSpan.FromMinutes(stats.AntiRaidSettings.PunishDuration):hh\\hmm\\m})**";
}
return GetText("raid_stats",
Format.Bold(stats.AntiRaidSettings.UserThreshold.ToString()),
Format.Bold(stats.AntiRaidSettings.Seconds.ToString()),
actionString);
}
}
}
}

View File

@@ -0,0 +1,85 @@
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
{
public partial class Administration
{
[Group]
public class PruneCommands : NadekoSubmodule<PruneService>
{
private static readonly TimeSpan twoWeeks = TimeSpan.FromDays(14);
//delets her own messages, no perm required
[NadekoCommand, Usage, Description, 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, Usage, Description, 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, count, (x) => !x.IsPinned).ConfigureAwait(false);
else
await _service.PruneWhere((ITextChannel)ctx.Channel, count, x => true).ConfigureAwait(false);
}
//prune @user [x]
[NadekoCommand, Usage, Description, 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, Usage, Description, 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);
}
}
}
}

View File

@@ -0,0 +1,345 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common;
using NadekoBot.Common.Attributes;
using NadekoBot.Core.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
{
public partial class Administration
{
public class RoleCommands : NadekoSubmodule<RoleCommandsService>
{
private IServiceProvider _services;
public enum Exclude { Excl }
public RoleCommands(IServiceProvider services)
{
_services = services;
}
public async Task InternalReactionRoles(bool exclusive, params string[] input)
{
var msgs = await ((SocketTextChannel)ctx.Channel).GetMessagesAsync().FlattenAsync().ConfigureAwait(false);
var prev = (IUserMessage)msgs.FirstOrDefault(x => x is IUserMessage && x.Id != ctx.Message.Id);
if (prev == null)
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)
{
try
{
await prev.AddReactionAsync(x.emote, new RequestOptions()
{
RetryMode = RetryMode.Retry502 | RetryMode.RetryRatelimit
}).ConfigureAwait(false);
}
catch (Discord.Net.HttpException ex) when(ex.HttpCode == HttpStatusCode.BadRequest)
{
await ReplyErrorLocalizedAsync("reaction_cant_access", Format.Code(x.emote.ToString()));
return;
}
await Task.Delay(500).ConfigureAwait(false);
}
if (_service.Add(ctx.Guild.Id, new ReactionRoleMessage()
{
Exclusive = exclusive,
MessageId = prev.Id,
ChannelId = prev.Channel.Id,
ReactionRoles = all.Select(x =>
{
return new ReactionRole()
{
EmoteName = x.emote.ToString(),
RoleId = x.role.Id,
};
}).ToList(),
}))
{
await ctx.OkAsync();
}
else
{
await ReplyErrorLocalizedAsync("reaction_roles_full").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[NoPublicBot]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public Task ReactionRoles(params string[] input) =>
InternalReactionRoles(false, input);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[NoPublicBot]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(1)]
public Task ReactionRoles(Exclude _, params string[] input) =>
InternalReactionRoles(true, input);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[NoPublicBot]
[UserPerm(GuildPerm.ManageRoles)]
public async Task ReactionRolesList()
{
var embed = new EmbedBuilder()
.WithOkColor();
if (!_service.Get(ctx.Guild.Id, out var rrs) ||
!rrs.Any())
{
embed.WithDescription(GetText("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 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("reaction_roles_message", rr.ReactionRoles?.Count ?? 0, content));
}
}
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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)
{
return;
}
index--;
var rr = rrs[index];
_service.Remove(ctx.Guild.Id, index);
await ReplyConfirmLocalizedAsync("reaction_role_removed", index + 1).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("setrole", Format.Bold(roleToAdd.Name), Format.Bold(targetUser.ToString()))
.ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex, "Error in setrole command");
await ReplyErrorLocalizedAsync("setrole_err").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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("remrole", Format.Bold(roleToRemove.Name), Format.Bold(targetUser.ToString())).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalizedAsync("remrole_err").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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("renrole_perms").ConfigureAwait(false);
return;
}
await roleToEdit.ModifyAsync(g => g.Name = newname).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("renrole").ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalizedAsync("renrole_err").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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();
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("rar", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalizedAsync("rar_err").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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("cr", Format.Bold(r.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("dr", Format.Bold(role.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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)
{
await ReplyConfirmLocalizedAsync("rolehoist_enabled", Format.Bold(role.Name)).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync("rolehoist_disabled", Format.Bold(role.Name)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async Task RoleColor([Leftover] IRole role)
{
await ctx.Channel.SendConfirmAsync("Role Color", role.Color.RawValue.ToString("x6")).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task RoleColor(SixLabors.ImageSharp.Color color, [Leftover]IRole role)
{
try
{
var rgba32 = color.ToPixel<Rgba32>();
await role.ModifyAsync(r => r.Color = new Color(rgba32.R, rgba32.G, rgba32.B)).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("rc", Format.Bold(role.Name)).ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalizedAsync("rc_perms").ConfigureAwait(false);
}
}
}
}
}

View File

@@ -0,0 +1,272 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Core.Modules.Administration.Services;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class SelfAssignedRolesCommands : NadekoSubmodule<SelfAssignedRolesService>
{
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[BotPerm(GuildPerm.ManageMessages)]
public async Task AdSarm()
{
var newVal = _service.ToggleAdSarm(ctx.Guild.Id);
if (newVal)
{
await ReplyConfirmLocalizedAsync("adsarm_enable", Prefix).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync("adsarm_disable", Prefix).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(1)]
public Task Asar([Leftover] IRole role) =>
Asar(0, role);
[NadekoCommand, Usage, Description, 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)
{
await ReplyConfirmLocalizedAsync("role_added", Format.Bold(role.Name), Format.Bold(group.ToString())).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync("role_in_list", Format.Bold(role.Name)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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)
{
await ReplyConfirmLocalizedAsync("group_name_added", Format.Bold(group.ToString()), Format.Bold(name.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync("group_name_removed", Format.Bold(group.ToString())).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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;
bool success = _service.RemoveSar(role.Guild.Id, role.Id);
if (!success)
{
await ReplyErrorLocalizedAsync("self_assign_not").ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync("self_assign_rem", Format.Bold(role.Name)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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 groupNameText = "";
if (!groups.TryGetValue(kvp.Key, out var name))
{
groupNameText = Format.Bold(GetText("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 == null)
{
continue;
}
else
{
// first character is invisible space
if (Model.LevelRequirement == 0)
rolesStr.AppendLine(" " + Role.Name);
else
rolesStr.AppendLine(" " + Role.Name + $" (lvl {Model.LevelRequirement}+)");
}
}
rolesStr.AppendLine();
}
return new EmbedBuilder().WithOkColor()
.WithTitle(Format.Bold(GetText("self_assign_list", roles.Count())))
.WithDescription(rolesStr.ToString())
.WithFooter(exclusive
? GetText("self_assign_are_exclusive")
: GetText("self_assign_are_not_exclusive"));
}, roles.Count(), 20).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("self_assign_excl").ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("self_assign_no_excl").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("self_assign_not").ConfigureAwait(false);
return;
}
await ReplyConfirmLocalizedAsync("self_assign_level_req",
Format.Bold(role.Name),
Format.Bold(level.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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)
{
msg = await ReplyErrorLocalizedAsync("self_assign_not").ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.AssignResult.Err_Lvl_Req)
{
msg = await ReplyErrorLocalizedAsync("self_assign_not_level", Format.Bold(extra.ToString())).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.AssignResult.Err_Already_Have)
{
msg = await ReplyErrorLocalizedAsync("self_assign_already", Format.Bold(role.Name)).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.AssignResult.Err_Not_Perms)
{
msg = await ReplyErrorLocalizedAsync("self_assign_perms").ConfigureAwait(false);
}
else
{
msg = await ReplyConfirmLocalizedAsync("self_assign_success", Format.Bold(role.Name)).ConfigureAwait(false);
}
if (autoDelete)
{
msg.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
}
[NadekoCommand, Usage, Description, 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("self_assign_not").ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Have)
{
msg = await ReplyErrorLocalizedAsync("self_assign_not_have", Format.Bold(role.Name)).ConfigureAwait(false);
}
else if (result == SelfAssignedRolesService.RemoveResult.Err_Not_Perms)
{
msg = await ReplyErrorLocalizedAsync("self_assign_perms").ConfigureAwait(false);
}
else
{
msg = await ReplyConfirmLocalizedAsync("self_assign_remove", Format.Bold(role.Name)).ConfigureAwait(false);
}
if (autoDelete)
{
msg.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
}
}
}
}

View File

@@ -0,0 +1,546 @@
using Discord;
using Discord.Commands;
using Discord.Net;
using Discord.WebSocket;
using NadekoBot.Common;
using NadekoBot.Common.Attributes;
using NadekoBot.Common.Replacements;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Core.Services;
using Serilog;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class SelfCommands : NadekoSubmodule<SelfService>
{
private readonly DiscordSocketClient _client;
private readonly NadekoBot _bot;
private readonly IBotStrings _strings;
public SelfCommands(DiscordSocketClient client, NadekoBot bot, IBotStrings strings)
{
_client = client;
_bot = bot;
_strings = strings;
}
[NadekoCommand, Usage, Description, 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()
{
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 ctx.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("scadd"))
.AddField(efb => efb.WithName(GetText("server"))
.WithValue(cmd.GuildId == null ? $"-" : $"{cmd.GuildName}/{cmd.GuildId}").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("channel"))
.WithValue($"{cmd.ChannelName}/{cmd.ChannelId}").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("command_text"))
.WithValue(cmdText).WithIsInline(false))).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("autocmd_add", Format.Code(Format.Sanitize(cmdText)), cmd.Interval).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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();
if (scmds.Count == 0)
{
await ReplyErrorLocalizedAsync("startcmdlist_none").ConfigureAwait(false);
}
else
{
var i = 0;
await ctx.Channel.SendConfirmAsync(
text: string.Join("\n", scmds
.Select(x => $@"```css
#{++i + page * 5}
[{GetText("server")}]: {(x.GuildId.HasValue ? $"{x.GuildName} #{x.GuildId}" : "-")}
[{GetText("channel")}]: {x.ChannelName} #{x.ChannelId}
[{GetText("command_text")}]: {x.CommandText}```")),
title: string.Empty,
footer: GetText("page", page + 1))
.ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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())
{
await ReplyErrorLocalizedAsync("autocmdlist_none").ConfigureAwait(false);
}
else
{
var i = 0;
await ctx.Channel.SendConfirmAsync(
text: string.Join("\n", scmds
.Select(x => $@"```css
#{++i + page * 5}
[{GetText("server")}]: {(x.GuildId.HasValue ? $"{x.GuildName} #{x.GuildId}" : "-")}
[{GetText("channel")}]: {x.ChannelName} #{x.ChannelId}
{GetIntervalText(x.Interval)}
[{GetText("command_text")}]: {x.CommandText}```")),
title: string.Empty,
footer: GetText("page", page + 1))
.ConfigureAwait(false);
}
}
private string GetIntervalText(int interval)
{
return $"[{GetText("interval")}]: {interval}";
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task Wait(int miliseconds)
{
if (miliseconds <= 0)
return;
ctx.Message.DeleteAfter(0);
try
{
var msg = await ctx.Channel.SendConfirmAsync($"⏲ {miliseconds}ms")
.ConfigureAwait(false);
msg.DeleteAfter(miliseconds / 1000);
}
catch { }
await Task.Delay(miliseconds).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task AutoCommandRemove([Leftover] int index)
{
if (!_service.RemoveAutoCommand(--index, out _))
{
await ReplyErrorLocalizedAsync("acrm_fail").ConfigureAwait(false);
return;
}
await ctx.OkAsync();
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommandRemove([Leftover] int index)
{
if (!_service.RemoveStartupCommand(--index, out _))
await ReplyErrorLocalizedAsync("scrm_fail").ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("scrm").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task StartupCommandsClear()
{
_service.ClearStartupCommands();
await ReplyConfirmLocalizedAsync("startcmds_cleared").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardMessages()
{
var enabled = _service.ForwardMessages();
if (enabled)
await ReplyConfirmLocalizedAsync("fwdm_start").ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("fwdm_stop").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardToAll()
{
var enabled = _service.ForwardToAll();
if (enabled)
await ReplyConfirmLocalizedAsync("fwall_start").ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("fwall_stop").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ShardStats(int page = 1)
{
if (--page < 0)
return;
var statuses = _service.GetAllShardStatuses();
var status = string.Join(", ", statuses
.GroupBy(x => x.ConnectionState)
.Select(x => $"{x.Count()} {x.Key}")
.ToArray());
var allShardStrings = statuses
.Select(x =>
{
var timeDiff = DateTime.UtcNow - x.Time;
if (timeDiff >= TimeSpan.FromSeconds(30))
return $"Shard #{Format.Bold(x.ShardId.ToString())} **UNRESPONSIVE** for {timeDiff.ToString(@"hh\:mm\:ss")}";
return GetText("shard_stats_txt", x.ShardId.ToString(),
Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.ToString()), timeDiff.ToString(@"hh\:mm\:ss"));
})
.ToArray();
await ctx.SendPaginatedConfirmAsync(page, (curPage) =>
{
var str = string.Join("\n", allShardStrings.Skip(25 * curPage).Take(25));
if (string.IsNullOrWhiteSpace(str))
str = GetText("no_shards_on_page");
return new EmbedBuilder()
.WithAuthor(a => a.WithName(GetText("shard_stats")))
.WithTitle(status)
.WithOkColor()
.WithDescription(str);
}, allShardStrings.Length, 25).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task RestartShard(int shardId)
{
var success = _service.RestartShard(shardId);
if (success)
{
await ReplyConfirmLocalizedAsync("shard_reconnecting", Format.Bold("#" + shardId)).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync("no_shard_id").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public Task Leave([Leftover] string guildStr)
{
return _service.LeaveGuild(guildStr);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task Die()
{
try
{
await ReplyConfirmLocalizedAsync("shutting_down").ConfigureAwait(false);
}
catch
{
// ignored
}
await Task.Delay(2000).ConfigureAwait(false);
_service.Die();
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task Restart()
{
bool success = _service.RestartBot();
if (!success)
{
await ReplyErrorLocalizedAsync("restart_fail").ConfigureAwait(false);
return;
}
try { await ReplyConfirmLocalizedAsync("restarting").ConfigureAwait(false); } catch { }
}
[NadekoCommand, Usage, Description, 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("bot_name", Format.Bold(newName)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("bot_nick", Format.Bold(newNick) ?? "-").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[BotPerm(GuildPerm.ManageNicknames)]
[UserPerm(GuildPerm.ManageNicknames)]
[Priority(1)]
public async Task SetNick(IGuildUser gu, [Leftover] string newNick = null)
{
var sg = (SocketGuild) Context.Guild;
if (sg.OwnerId == gu.Id ||
gu.GetRoles().Max(r => r.Position) >= sg.CurrentUser.GetRoles().Max(r => r.Position))
{
await ReplyErrorLocalizedAsync("insuf_perms_i");
return;
}
await gu.ModifyAsync(u => u.Nickname = newNick).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("user_nick", Format.Bold(gu.ToString()), Format.Bold(newNick) ?? "-").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task SetStatus([Leftover] SettableUserStatus status)
{
await _client.SetStatusAsync(SettableUserStatusToUserStatus(status)).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("bot_status", Format.Bold(status.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task SetAvatar([Leftover] string img = null)
{
var success = await _service.SetAvatar(img);
if (success)
{
await ReplyConfirmLocalizedAsync("set_avatar").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task SetGame(ActivityType type, [Leftover] string game = null)
{
var rep = new ReplacementBuilder()
.WithDefault(Context)
.Build();
await _bot.SetGameAsync(game == null ? game : rep.Replace(game), type).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("set_game").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task SetStream(string url, [Leftover] string name = null)
{
name = name ?? "";
await _client.SetGameAsync(name, url, ActivityType.Streaming).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("set_stream").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task Send(string where, [Leftover] string msg = null)
{
if (string.IsNullOrWhiteSpace(msg))
return;
var ids = where.Split('|');
if (ids.Length != 2)
return;
var sid = ulong.Parse(ids[0]);
var server = _client.Guilds.FirstOrDefault(s => s.Id == sid);
if (server == null)
return;
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 == null)
{
return;
}
if (CREmbed.TryParse(msg, out var crembed))
{
rep.Replace(crembed);
await ch.EmbedAsync(crembed).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("message_sent").ConfigureAwait(false);
return;
}
await ch.SendMessageAsync(rep.Replace(msg).SanitizeMentions()).ConfigureAwait(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 == null)
{
return;
}
if (CREmbed.TryParse(msg, out var crembed))
{
rep.Replace(crembed);
await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).EmbedAsync(crembed)
.ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("message_sent").ConfigureAwait(false);
return;
}
await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(rep.Replace(msg).SanitizeMentions()).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync("invalid_format").ConfigureAwait(false);
return;
}
await ReplyConfirmLocalizedAsync("message_sent").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ImagesReload()
{
_service.ReloadImages();
await ReplyConfirmLocalizedAsync("images_loading", 0).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task StringsReload()
{
_strings.Reload();
await ReplyConfirmLocalizedAsync("bot_strings_reloaded").ConfigureAwait(false);
}
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;
}
public enum SettableUserStatus
{
Online,
Invisible,
Idle,
Dnd
}
}
}
}

View File

@@ -0,0 +1,218 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Core.Common.Attributes;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class ServerGreetCommands : NadekoSubmodule<GreetSettingsService>
{
[NadekoCommand, Usage, Description, 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("greetdel_on", timer).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("greetdel_off").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("greet_on").ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("greet_off").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetMsg()
{
string greetMsg = _service.GetGreetMsg(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync("greetmsg_cur", greetMsg?.SanitizeMentions());
}
[NadekoCommand, Usage, Description, 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("greetmsg_new").ConfigureAwait(false);
if (!sendGreetEnabled)
await ReplyConfirmLocalizedAsync("greetmsg_enable", $"`{Prefix}greet`").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("greetdm_on").ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("greetdm_off").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDmMsg()
{
var dmGreetMsg = _service.GetDmGreetMsg(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync("greetdmmsg_cur", dmGreetMsg?.SanitizeMentions());
}
[NadekoCommand, Usage, Description, 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("greetdmmsg_new").ConfigureAwait(false);
if (!sendGreetEnabled)
await ReplyConfirmLocalizedAsync("greetdmmsg_enable", $"`{Prefix}greetdm`").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("bye_on").ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("bye_off").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeMsg()
{
var byeMsg = _service.GetByeMessage(ctx.Guild.Id);
return ReplyConfirmLocalizedAsync("byemsg_cur", byeMsg?.SanitizeMentions());
}
[NadekoCommand, Usage, Description, 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("byemsg_new").ConfigureAwait(false);
if (!sendByeEnabled)
await ReplyConfirmLocalizedAsync("byemsg_enable", $"`{Prefix}bye`").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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("byedel_on", timer).ConfigureAwait(false);
else
await ReplyConfirmLocalizedAsync("byedel_off").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task ByeTest([Leftover] IGuildUser user = null)
{
user = user ?? (IGuildUser) Context.User;
await _service.ByeTest((ITextChannel)Context.Channel, user);
var enabled = _service.GetByeEnabled(Context.Guild.Id);
if (!enabled)
{
await ReplyConfirmLocalizedAsync("byemsg_enable", $"`{Prefix}bye`").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task GreetTest([Leftover] IGuildUser user = null)
{
user = user ?? (IGuildUser) Context.User;
await _service.GreetTest((ITextChannel)Context.Channel, user);
var enabled = _service.GetGreetEnabled(Context.Guild.Id);
if (!enabled)
{
await ReplyConfirmLocalizedAsync("greetmsg_enable", $"`{Prefix}greet`").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task GreetDmTest([Leftover] IGuildUser user = null)
{
user = user ?? (IGuildUser) Context.User;
var channel = await user.GetOrCreateDMChannelAsync();
var success = await _service.GreetDmTest(channel, user);
if (success)
await Context.OkAsync();
else
await Context.WarningAsync();
var enabled = _service.GetGreetDmEnabled(Context.Guild.Id);
if (!enabled)
await ReplyConfirmLocalizedAsync("greetdmmsg_enable", $"`{Prefix}greetdm`").ConfigureAwait(false);
}
}
}
}

View File

@@ -0,0 +1,185 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common;
using NadekoBot.Common.Collections;
using NadekoBot.Common.Replacements;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration.Services
{
public class AdministrationService : INService
{
public ConcurrentHashSet<ulong> DeleteMessagesOnCommand { get; }
public ConcurrentDictionary<ulong, bool> DeleteMessagesOnCommandChannels { get; }
private readonly DbService _db;
private readonly LogCommandService _logService;
public AdministrationService(NadekoBot bot, CommandHandler cmdHandler, DbService db,
LogCommandService logService)
{
_db = db;
_logService = logService;
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;
}
public (bool DelMsgOnCmd, IEnumerable<DelMsgOnCmdChannel> channels) GetDelMsgOnCmdData(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
var conf = uow.GuildConfigs.ForId(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")
{
_logService.AddDeleteIgnore(msg.Id);
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { }
}
});
return Task.CompletedTask;
}
public bool ToggleDeleteMessageOnCommand(ulong guildId)
{
bool enabled;
using (var uow = _db.GetDbContext())
{
var conf = uow.GuildConfigs.ForId(guildId, set => set);
enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand;
uow.SaveChanges();
}
return enabled;
}
public async Task SetDelMsgOnCmdState(ulong guildId, ulong chId, Administration.State newState)
{
using (var uow = _db.GetDbContext())
{
var conf = uow.GuildConfigs.ForId(guildId,
set => set.Include(x => x.DelMsgOnCmdChannels));
var old = conf.DelMsgOnCmdChannels.FirstOrDefault(x => x.ChannelId == chId);
if (newState == Administration.State.Inherit)
{
if (!(old is null))
{
conf.DelMsgOnCmdChannels.Remove(old);
uow._context.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 _);
}
}
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 text)
{
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();
if (CREmbed.TryParse(text, out var crembed))
{
rep.Replace(crembed);
await umsg.ModifyAsync(x =>
{
x.Embed = crembed.ToEmbed().Build();
x.Content = crembed.PlainText?.SanitizeMentions() ?? "";
}).ConfigureAwait(false);
}
else
{
await umsg.ModifyAsync(x =>
{
x.Content = text.SanitizeMentions();
x.Embed = null;
}).ConfigureAwait(false);
}
}
}
}

View File

@@ -0,0 +1,170 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Core.Services;
using System.Collections.Generic;
using System.Threading.Channels;
using LinqToDB;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
{
public sealed class AutoAssignRoleService : INService
{
private readonly DiscordSocketClient _client;
private readonly DbService _db;
//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, NadekoBot bot, DbService db)
{
_client = client;
_db = db;
_autoAssignableRoles = bot.AllGuildConfigs
.Where(x => !string.IsNullOrWhiteSpace(x.AutoAssignRoleIds))
.ToDictionary<GuildConfig, ulong, IReadOnlyList<ulong>>(k => k.GuildId, v => v.GetAutoAssignableRoles())
.ToConcurrent();
_ = Task.Run(async () =>
{
while (true)
{
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 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)
{
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");
}
}
});
_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);
}
}
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.GuildConfigs.ForId(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._context
.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.GuildConfigs.ForId(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

@@ -0,0 +1,145 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services;
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Core.Services.Database.Models;
namespace NadekoBot.Core.Modules.Administration.Services
{
public class DangerousCommandsService : INService
{
public const string WaifusDeleteSql = @"DELETE FROM WaifuUpdates;
DELETE FROM WaifuItem;
DELETE FROM WaifuInfo;";
public const string WaifuDeleteSql = @"DELETE FROM WaifuUpdates WHERE UserId=(SELECT Id FROM DiscordUser WHERE UserId={0});
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;
UPDATE DiscordUser
SET ClubId=NULL,
IsClubAdmin=0,
TotalXp=0;
DELETE FROM ClubApplicants;
DELETE FROM ClubBans;
DELETE FROM Clubs;";
// public const string DeleteUnusedCustomReactionsAndQuotes = @"DELETE FROM CustomReactions
//WHERE UseCount=0 AND (DateAdded < date('now', '-7 day') OR DateAdded is null);
//DELETE FROM Quotes
//WHERE UseCount=0 AND (DateAdded < date('now', '-7 day') OR DateAdded is null);";
private readonly DbService _db;
public DangerousCommandsService(DbService db)
{
_db = db;
}
public async Task<int> ExecuteSql(string sql)
{
int res;
using (var uow = _db.GetDbContext())
{
res = await uow._context.Database.ExecuteSqlRawAsync(sql);
}
return res;
}
public class SelectResult
{
public List<string> ColumnNames { get; set; }
public List<string[]> Results { get; set; }
}
public SelectResult SelectSql(string sql)
{
var result = new SelectResult()
{
ColumnNames = new List<string>(),
Results = new List<string[]>(),
};
using (var uow = _db.GetDbContext())
{
var conn = uow._context.Database.GetDbConnection();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
if (reader.HasRows)
{
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());
}
}
}
}
}
return result;
}
public async Task PurgeUserAsync(ulong userId)
{
using var uow = _db.GetDbContext();
// get waifu info
var wi = await uow._context.Set<WaifuInfo>()
.FirstOrDefaultAsyncEF(x => x.Waifu.UserId == userId);
// if it exists, delete waifu related things
if (!(wi is null))
{
// remove updates which have new or old as this waifu
await uow._context
.WaifuUpdates
.DeleteAsync(wu => wu.New.UserId == userId || wu.Old.UserId == userId);
// delete all items this waifu owns
await uow._context
.Set<WaifuItem>()
.DeleteAsync(x => x.WaifuInfoId == wi.Id);
// all waifus this waifu claims are released
await uow._context
.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._context
.Set<WaifuInfo>()
.AsQueryable()
.Where(x => x.Affinity.UserId == userId)
.UpdateAsync(x => new WaifuInfo() {AffinityId = null});
}
// delete guild xp
await uow._context
.UserXpStats
.DeleteAsync(x => x.UserId == userId);
// delete currency transactions
await uow._context.Set<CurrencyTransaction>()
.DeleteAsync(x => x.UserId == userId);
// delete user, currency, and clubs go away with it
await uow._context.DiscordUser
.DeleteAsync(u => u.UserId == userId);
}
}
}

View File

@@ -0,0 +1,159 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Administration.Services
{
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)
{
_db = db;
_services = services;
using var uow = _db.GetDbContext();
_overrides = uow._context.DiscordPermOverrides
.AsNoTracking()
.AsEnumerable()
.ToDictionary(o => (o.GuildId ?? 0, o.Command), o => o)
.ToConcurrent();
}
public bool TryGetOverrides(ulong guildId, string commandName, out GuildPerm? perm)
{
commandName = commandName.ToLowerInvariant();
if (_overrides.TryGetValue((guildId, commandName), out var dpo))
{
perm = dpo.Perm;
return true;
}
perm = null;
return false;
}
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())
{
var over = await uow._context
.Set<DiscordPermOverride>()
.AsQueryable()
.FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command);
if (over is null)
{
uow._context.Set<DiscordPermOverride>()
.Add(over = new DiscordPermOverride()
{
Command = commandName,
Perm = perm,
GuildId = guildId,
});
}
else
{
over.Perm = perm;
}
_overrides[(guildId, commandName)] = over;
await uow.SaveChangesAsync();
}
}
public async Task ClearAllOverrides(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
var overrides = await uow._context
.Set<DiscordPermOverride>()
.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.ToListAsync();
uow._context.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._context
.Set<DiscordPermOverride>()
.AsQueryable()
.AsNoTracking()
.FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName);
if (over is null)
return;
uow._context.Remove(over);
await uow.SaveChangesAsync();
_overrides.TryRemove((guildId, commandName), out _);
}
}
public Task<List<DiscordPermOverride>> GetAllOverrides(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
return uow._context
.Set<DiscordPermOverride>()
.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.ToListAsync();
}
}
public async Task<bool> TryBlockLate(DiscordSocketClient client, ICommandContext context, string moduleName,
CommandInfo command)
{
if (TryGetOverrides(context.Guild?.Id ?? 0, command.Name, out var perm) && !(perm is null))
{
var result = await new RequireUserPermissionAttribute((GuildPermission) perm)
.CheckPermissionsAsync(context, command, _services);
return !result.IsSuccess;
}
return false;
}
}
}

View File

@@ -0,0 +1,132 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Common.Collections;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
{
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, NadekoBot 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 == 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.GuildConfigs.ForId(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 == 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 == null)
return;
await Task.Delay(1000).ConfigureAwait(false);
await gUser.ModifyAsync(gu => gu.Channel = vch).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration.Services
{
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, NadekoBot 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;
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 == 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.GuildConfigs.ForId(guildId, set => set);
gc.TimeZoneId = tz?.Id;
uow.SaveChanges();
if (tz == null)
_timezones.TryRemove(guildId, out tz);
else
_timezones.AddOrUpdate(guildId, tz, (key, old) => tz);
}
}
public TimeZoneInfo GetTimeZoneOrUtc(ulong guildId)
=> GetTimeZoneOrDefault(guildId) ?? TimeZoneInfo.Utc;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,477 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.Collections;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
{
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;
public MuteService(DiscordSocketClient client, DbService db)
{
_client = client;
_db = db;
using (var uow = db.GetDbContext())
{
var guildIds = client.Guilds.Select(x => x.Id).ToList();
var configs = uow._context.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)
{
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);
}
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);
}
}
_client.UserJoined += Client_UserJoined;
}
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: new EmbedBuilder()
.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;
var _ = Task.Run(() => user.SendMessageAsync(embed: new EmbedBuilder()
.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)
{
try
{
MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted);
if (muted == 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.GuildConfigs.ForId(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.GuildConfigs.ForId(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);
}
}
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.GuildConfigs.ForId(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._context.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 == 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 == 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 == 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 == 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.GuildConfigs.ForId(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.GuildConfigs.ForId(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.GuildConfigs.ForId(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.GuildConfigs.ForId(guildId, set => set.Include(x => x.UnmuteTimers));
toDelete = config.UnmuteTimers.FirstOrDefault(x => x.UserId == userId);
}
else
{
var config = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.UnbanTimer));
toDelete = config.UnbanTimer.FirstOrDefault(x => x.UserId == userId);
}
if (toDelete != null)
{
uow._context.Remove(toDelete);
}
uow.SaveChanges();
}
}
}
}

View File

@@ -0,0 +1,31 @@
//using NadekoBot.Core.Services;
//using System;
//using System.Collections.Generic;
//using System.IO;
//using System.Linq;
//using System.Text.RegularExpressions;
//namespace NadekoBot.Modules.Administration.Services
//{
// public class PackagesService : INService
// {
// public IEnumerable<string> Packages { get; private set; }
// public PackagesService()
// {
// ReloadAvailablePackages();
// }
// public void ReloadAvailablePackages()
// {
// Packages = Directory.GetDirectories(Path.Combine(Appctx.BaseDirectory, "modules\\"), "NadekoBot.Modules.*", SearchOption.AllDirectories)
// .SelectMany(x => Directory.GetFiles(x, "NadekoBot.Modules.*.dll"))
// .Select(x => Path.GetFileNameWithoutExtension(x))
// .Select(x =>
// {
// var m = Regex.Match(x, @"NadekoBot\.Modules\.(?<name>.*)");
// return m.Groups["name"].Value;
// });
// }
// }
//}

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Discord.WebSocket;
using NadekoBot.Common.Replacements;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using Discord;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Common;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
{
public sealed class PlayingRotateService : INService
{
private readonly Timer _t;
private readonly BotConfigService _bss;
private readonly Replacer _rep;
private readonly DbService _db;
private readonly NadekoBot _bot;
private class TimerState
{
public int Index { get; set; }
}
public PlayingRotateService(DiscordSocketClient client, DbService db, NadekoBot bot,
BotConfigService bss, IEnumerable<IPlaceholderProvider> phProviders)
{
_db = db;
_bot = bot;
_bss = bss;
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._context.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 _bot.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._context.RotatingStatus
.AsQueryable()
.AsNoTracking()
.Skip(index)
.FirstOrDefaultAsync();
if (toRemove is null)
return null;
uow._context.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._context.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._context.RotatingStatus.AsNoTracking().ToList();
}
}
}

View File

@@ -0,0 +1,487 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Channels;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using NadekoBot.Modules.Administration.Common;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Common.TypeReaders.Models;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
{
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, NadekoBot 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._context.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)
{
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);
}
}
}
private Task _client_LeftGuild(SocketGuild guild)
{
var _ = Task.Run(async () =>
{
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.GuildConfigs.ForId(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 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)
{
var diff = DateTime.UtcNow - user.CreatedAt.UtcDateTime;
if (diff < alts.MinAge)
{
alts.Increment();
await PunishUsers(
alts.Action,
ProtectionType.Alting,
alts.ActionDurationMinutes,
alts.RoleId,
user);
return;
}
}
}
try
{
if (!(maybeStats is AntiRaidStats stats) || !stats.RaidUsers.Add(user))
return;
++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
{
// ignored
}
});
return Task.CompletedTask;
}
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
});
}
_ = OnAntiProtectionTriggered(action, pt, gus);
}
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;
var stats = new AntiRaidStats()
{
AntiRaidSettings = new AntiRaidSetting()
{
Action = action,
Seconds = seconds,
UserThreshold = userThreshold,
PunishDuration = minutesDuration
}
};
_antiRaidGuilds.AddOrUpdate(guildId, stats, (key, old) => stats);
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigs.ForId(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.GuildConfigs.ForId(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.GuildConfigs.ForId(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.GuildConfigs.ForId(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.GuildConfigs.ForId(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._context.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.GuildConfigs.ForId(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.GuildConfigs.ForId(guildId, set => set.Include(x => x.AntiAltSetting));
gc.AntiAltSetting = null;
await uow.SaveChangesAsync();
return true;
}
}
}

View File

@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using NadekoBot.Common.Collections;
using NadekoBot.Extensions;
using NadekoBot.Core.Services;
namespace NadekoBot.Modules.Administration.Services
{
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 LogCommandService _logService;
public PruneService(LogCommandService logService)
{
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())
{
lastMessage = msgs[msgs.Length - 1];
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();
}
}
catch
{
//ignore
}
finally
{
_pruningGuilds.TryRemove(channel.GuildId);
}
}
}
}

View File

@@ -0,0 +1,190 @@
using Discord;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.Collections;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
{
public class RoleCommandsService : INService
{
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly ConcurrentDictionary<ulong, IndexedCollection<ReactionRoleMessage>> _models;
public RoleCommandsService(DiscordSocketClient client, DbService db,
NadekoBot bot)
{
_db = db;
_client = client;
#if !GLOBAL_NADEKO
_models = bot.AllGuildConfigs.ToDictionary(x => x.GuildId,
x => x.ReactionRoleMessages)
.ToConcurrent();
_client.ReactionAdded += _client_ReactionAdded;
_client.ReactionRemoved += _client_ReactionRemoved;
#endif
}
private Task _client_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
{
var _ = Task.Run(async () =>
{
try
{
if (!reaction.User.IsSpecified ||
reaction.User.Value.IsBot ||
!(reaction.User.Value is SocketGuildUser gusr))
return;
if (!(chan is SocketGuildChannel gch))
return;
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
return;
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
if (conf == 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)
{
var roleIds = conf.ReactionRoles.Select(x => x.RoleId)
.Where(x => x != reactionRole.RoleId)
.Select(x => gusr.Guild.GetRole(x))
.Where(x => x != null);
var __ = Task.Run(async () =>
{
try
{
//if the role is exclusive,
// remove all other reactions user added to the message
var dl = await msg.GetOrDownloadAsync().ConfigureAwait(false);
foreach (var r in dl.Reactions)
{
if (r.Key.Name == reaction.Emote.Name)
continue;
try { await dl.RemoveReactionAsync(r.Key, gusr).ConfigureAwait(false); } catch { }
await Task.Delay(100).ConfigureAwait(false);
}
}
catch { }
});
await gusr.RemoveRolesAsync(roleIds).ConfigureAwait(false);
}
var toAdd = gusr.Guild.GetRole(reactionRole.RoleId);
if (toAdd != null && !gusr.Roles.Contains(toAdd))
{
await gusr.AddRolesAsync(new[] { toAdd }).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;
}
private Task _client_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
{
var _ = Task.Run(async () =>
{
try
{
if (!reaction.User.IsSpecified ||
reaction.User.Value.IsBot ||
!(reaction.User.Value is SocketGuildUser gusr))
return;
if (!(chan is SocketGuildChannel gch))
return;
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
return;
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
if (conf == 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 == 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 gc = uow.GuildConfigs.ForId(id, set => set
.Include(x => x.ReactionRoleMessages)
.ThenInclude(x => x.ReactionRoles));
if (gc.ReactionRoleMessages.Count >= 10)
return false;
gc.ReactionRoleMessages.Add(rrm);
_models.AddOrUpdate(id,
gc.ReactionRoleMessages,
delegate { return gc.ReactionRoleMessages; });
uow.SaveChanges();
}
return true;
}
public void Remove(ulong id, int index)
{
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigs.ForId(id,
set => set.Include(x => x.ReactionRoleMessages)
.ThenInclude(x => x.ReactionRoles));
uow._context.Set<ReactionRole>()
.RemoveRange(gc.ReactionRoleMessages[index].ReactionRoles);
gc.ReactionRoleMessages.RemoveAt(index);
_models.AddOrUpdate(id,
gc.ReactionRoleMessages,
delegate { return gc.ReactionRoleMessages; });
uow.SaveChanges();
}
}
}
}

View File

@@ -0,0 +1,270 @@
using Discord;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Modules.Xp;
namespace NadekoBot.Core.Modules.Administration.Services
{
public class SelfAssignedRolesService : INService
{
private readonly DbService _db;
public enum RemoveResult
{
Removed, // successfully removed
Err_Not_Assignable, // not assignable (error)
Err_Not_Have, // you don't have a role you want to remove (error)
Err_Not_Perms, // bot doesn't have perms (error)
}
public enum AssignResult
{
Assigned, // successfully removed
Err_Not_Assignable, // not assignable (error)
Err_Already_Have, // you already have that role (error)
Err_Not_Perms, // bot doesn't have perms (error)
Err_Lvl_Req, // you are not required level (error)
}
public SelfAssignedRolesService(DbService db)
{
_db = db;
}
public bool AddNew(ulong guildId, IRole role, int group)
{
using (var uow = _db.GetDbContext())
{
var roles = uow.SelfAssignedRoles.GetFromGuild(guildId);
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
{
return false;
}
uow.SelfAssignedRoles.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.GuildConfigs.ForId(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.Xp.GetOrCreateUser(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 == 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
{
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);
}
public async Task<bool> SetNameAsync(ulong guildId, int group, string name)
{
bool set = false;
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigs.ForId(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 == null)
{
gc.SelfAssignableRoleGroupNames.Add(new GroupName
{
Name = name,
Number = group,
});
set = true;
}
else
{
toUpdate.Name = name;
set = true;
}
await uow.SaveChangesAsync();
}
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) == 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);
}
public bool RemoveSar(ulong guildId, ulong roleId)
{
bool success;
using (var uow = _db.GetDbContext())
{
success = uow.SelfAssignedRoles.DeleteByGuildAndRoleId(guildId, roleId);
uow.SaveChanges();
}
return success;
}
public (bool AutoDelete, bool Exclusive, IEnumerable<SelfAssignedRole>) GetAdAndRoles(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigs.ForId(guildId, set => set);
var autoDelete = gc.AutoDeleteSelfAssignedRoleMessages;
var exclusive = gc.ExclusiveSelfAssignedRoles;
var roles = uow.SelfAssignedRoles.GetFromGuild(guildId);
return (autoDelete, exclusive, roles);
}
}
public bool SetLevelReq(ulong guildId, IRole role, int level)
{
using (var uow = _db.GetDbContext())
{
var roles = uow.SelfAssignedRoles.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.GuildConfigs.ForId(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.GuildConfigs.ForId(guild.Id, set => set.Include(x => x.SelfAssignableRoleGroupNames));
exclusive = gc.ExclusiveSelfAssignedRoles;
groupNames = gc.SelfAssignableRoleGroupNames.ToDictionary(x => x.Number, x => x.Name);
var roleModels = uow.SelfAssignedRoles.GetFromGuild(guild.Id);
roles = roleModels
.Select(x => (Model: x, Role: guild.GetRole(x.RoleId)));
uow.SelfAssignedRoles.RemoveRange(roles.Where(x => x.Role == null).Select(x => x.Model).ToArray());
uow.SaveChanges();
}
return (exclusive, roles.Where(x => x.Role != null), groupNames);
}
}
}

View File

@@ -0,0 +1,436 @@
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.Core.Services;
using StackExchange.Redis;
using System.Collections.Generic;
using System.Diagnostics;
using Newtonsoft.Json;
using NadekoBot.Common.ShardCom;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services.Database.Models;
using System.Threading;
using System.Collections.Concurrent;
using System;
using System.Net.Http;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
{
public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
{
private readonly ConnectionMultiplexer _redis;
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 IDataCache _cache;
private readonly IImageCache _imgs;
private readonly IHttpClientFactory _httpFactory;
private readonly BotConfigService _bss;
public SelfService(DiscordSocketClient client, CommandHandler cmdHandler, DbService db,
IBotStrings strings, IBotCredentials creds, IDataCache cache, IHttpClientFactory factory,
BotConfigService bss)
{
_redis = cache.Redis;
_cmdHandler = cmdHandler;
_db = db;
_strings = strings;
_client = client;
_creds = creds;
_cache = cache;
_imgs = cache.LocalImages;
_httpFactory = factory;
_bss = bss;
var sub = _redis.GetSubscriber();
if (_client.ShardId == 0)
{
sub.Subscribe(_creds.RedisKey() + "_reload_images",
delegate { _imgs.Reload(); }, CommandFlags.FireAndForget);
}
sub.Subscribe(_creds.RedisKey() + "_leave_guild", async (ch, v) =>
{
try
{
var guildStr = v.ToString()?.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(guildStr))
return;
var server = _client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr) ??
_client.Guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr);
if (server == 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}]");
}
}
catch
{
}
}, CommandFlags.FireAndForget);
}
public async Task OnReadyAsync()
{
using var uow = _db.GetDbContext();
_autoCommands = uow._context
.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._context.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);
}
}
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._context.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._context
.AutoCommands
.AsNoTracking()
.Where(x => x.Interval == 0)
.OrderBy(x => x.Id)
.ToList();
}
public IEnumerable<AutoCommand> GetAutoCommands()
{
using var uow = _db.GetDbContext();
return uow._context
.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 == 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 credentials.json file and invited the bot to a Discord server.");
else
Log.Information($"Created {ownerChannels.Count} out of {_creds.OwnerIds.Length} owner message channels.");
}
public Task LeaveGuild(string guildStr)
{
var sub = _cache.Redis.GetSubscriber();
return sub.PublishAsync(_creds.RedisKey() + "_leave_guild", guildStr);
}
// forwards dms
public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg)
{
var bs = _bss.Data;
if (msg.Channel is IDMChannel && _bss.Data.ForwardMessages && ownerChannels.Any())
{
var title = _strings.GetText("dm_from") +
$" [{msg.Author}]({msg.Author.Id})";
var attachamentsTxt = _strings.GetText("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(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(title, toSend).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
}
}
public bool RestartBot()
{
var cmd = _creds.RestartCommand;
if (string.IsNullOrWhiteSpace(cmd?.Cmd))
{
return false;
}
Restart();
return true;
}
public bool RemoveStartupCommand(int index, out AutoCommand cmd)
{
using (var uow = _db.GetDbContext())
{
cmd = uow._context.AutoCommands
.AsNoTracking()
.Where(x => x.Interval == 0)
.Skip(index)
.FirstOrDefault();
if (cmd != null)
{
uow._context.Remove(cmd);
uow.SaveChanges();
return true;
}
}
return false;
}
public bool RemoveAutoCommand(int index, out AutoCommand cmd)
{
using (var uow = _db.GetDbContext())
{
cmd = uow._context.AutoCommands
.AsNoTracking()
.Where(x => x.Interval >= 5)
.Skip(index)
.FirstOrDefault();
if (cmd != null)
{
uow._context.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._context
.AutoCommands
.AsNoTracking()
.Where(x => x.Interval == 0);
uow._context.AutoCommands.RemoveRange(toRemove);
uow.SaveChanges();
}
}
public void ReloadImages()
{
var sub = _cache.Redis.GetSubscriber();
sub.Publish(_creds.RedisKey() + "_reload_images", "");
}
public void Die()
{
var sub = _cache.Redis.GetSubscriber();
sub.Publish(_creds.RedisKey() + "_die", "", CommandFlags.FireAndForget);
}
public void Restart()
{
Process.Start(_creds.RestartCommand.Cmd, _creds.RestartCommand.Args);
var sub = _cache.Redis.GetSubscriber();
sub.Publish(_creds.RedisKey() + "_die", "", CommandFlags.FireAndForget);
}
public bool RestartShard(int shardId)
{
if (shardId < 0 || shardId >= _creds.TotalShards)
return false;
var pub = _cache.Redis.GetSubscriber();
pub.Publish(_creds.RedisKey() + "_shardcoord_stop",
JsonConvert.SerializeObject(shardId),
CommandFlags.FireAndForget);
return 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;
}
public IEnumerable<ShardComMessage> GetAllShardStatuses()
{
var db = _cache.Redis.GetDatabase();
return db.ListRange(_creds.RedisKey() + "_shardstats")
.Select(x => JsonConvert.DeserializeObject<ShardComMessage>(x));
}
}
}

View File

@@ -0,0 +1,488 @@
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.Core.Common.TypeReaders.Models;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Permissions.Services;
using Newtonsoft.Json;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
{
public class UserPunishService : INService
{
private readonly MuteService _mute;
private readonly DbService _db;
private readonly BlacklistService _blacklistService;
private readonly Timer _warnExpiryTimer;
public UserPunishService(MuteService mute, DbService db, BlacklistService blacklistService)
{
_mute = mute;
_db = db;
_blacklistService = blacklistService;
_warnExpiryTimer = new Timer(async _ =>
{
await CheckAllWarnExpiresAsync();
}, null, TimeSpan.FromSeconds(0), TimeSpan.FromHours(12));
}
public async Task<WarningPunishment> Warn(IGuild guild, ulong userId, IUser mod, string reason)
{
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,
};
int warnings = 1;
List<WarningPunishment> ps;
using (var uow = _db.GetDbContext())
{
ps = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.WarnPunishments))
.WarnPunishments;
warnings += uow.Warnings
.ForId(guildId, userId)
.Where(w => !w.Forgiven && w.UserId == userId)
.Count();
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 == null)
return null;
await ApplyPunishment(guild, user, mod, p.Punishment, p.Time, p.RoleId, "Warned too many times.");
return p;
}
return null;
}
public async Task ApplyPunishment(IGuild guild, IGuildUser user, IUser mod, PunishmentAction p, int minutes,
ulong? roleId, string reason)
{
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).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 null))
{
if (minutes == 0)
await user.AddRoleAsync(role).ConfigureAwait(false);
else
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;
}
}
public async Task CheckAllWarnExpiresAsync()
{
using (var uow = _db.GetDbContext())
{
var cleared = await uow._context.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._context.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.");
}
}
}
public async Task CheckWarnExpiresAsync(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigs.ForId(guildId, inc => inc);
if (config.WarnExpireHours == 0)
return;
var hours = $"{-config.WarnExpireHours} hours";
if (config.WarnExpireAction == WarnExpireAction.Clear)
{
await uow._context.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._context.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.GuildConfigs.ForId(guildId);
return Task.FromResult(config.WarnExpireHours / 24);
}
public async Task WarnExpireAsync(ulong guildId, int days, bool delete)
{
using (var uow = _db.GetDbContext())
{
var config = uow.GuildConfigs.ForId(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.GuildConfigs.ForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
var toDelete = ps.Where(x => x.Count == number);
uow._context.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.GuildConfigs.ForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
var p = ps.FirstOrDefault(x => x.Count == number);
if (p != null)
{
uow._context.Remove(p);
uow.SaveChanges();
}
}
return true;
}
public WarningPunishment[] WarnPunishList(ulong guildId)
{
using (var uow = _db.GetDbContext())
{
return uow.GuildConfigs.ForId(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._context.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._context.BanTemplates
.AsQueryable()
.FirstOrDefault(x => x.GuildId == guildId);
if (text == null)
{
if (template is null)
return;
uow._context.Remove(template);
}
else if (template == null)
{
uow._context.BanTemplates.Add(new BanTemplate()
{
GuildId = guildId,
Text = text,
});
}
else
{
template.Text = text;
}
uow.SaveChanges();
}
}
public CREmbed 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 CREmbed 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();
CREmbed crEmbed = null;
// if template isn't set, use the old message style
if (string.IsNullOrWhiteSpace(template))
{
template = JsonConvert.SerializeObject(new
{
color = NadekoBot.ErrorColor.RawValue,
description = defaultMessage
});
CREmbed.TryParse(template, out crEmbed);
}
// 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
else if (CREmbed.TryParse(template, out crEmbed))
{
replacer.Replace(crEmbed);
}
// otherwise, treat template as a regular string with replacements
else
{
template = JsonConvert.SerializeObject(new
{
color = NadekoBot.ErrorColor.RawValue,
description = replacer.Replace(template)
});
CREmbed.TryParse(template, out crEmbed);
}
return crEmbed;
}
}
}

View File

@@ -0,0 +1,230 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Database.Models;
using Serilog;
namespace NadekoBot.Modules.Administration.Services
{
public class VcRoleService : INService
{
private readonly DbService _db;
private readonly DiscordSocketClient _client;
public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; }
public ConcurrentDictionary<ulong, ConcurrentQueue<(bool, IGuildUser, IRole)>> ToAssign { get; }
public VcRoleService(DiscordSocketClient client, NadekoBot bot, DbService db)
{
_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._context.Set<GuildConfig>()
.AsQueryable()
.Include(x => x.VcRoleInfos)
.Where(x => guildIds.Contains(x.GuildId))
.ToList();
Task.WhenAll(configs.Select(InitializeVcRole));
}
Task.Run(async () =>
{
while (true)
{
var tasks = ToAssign.Values.Select(queue => Task.Run(async () =>
{
while (queue.TryDequeue(out var item))
{
var (add, user, role) = item;
if (add)
{
if (!user.RoleIds.Contains(role.Id))
{
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.GuildConfigs.ForId(
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 == 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 == 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._context.RemoveRange(missingRoles);
await uow.SaveChangesAsync();
}
}
}
public void AddVcRole(ulong guildId, IRole role, ulong vcId)
{
if (role == 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.GuildConfigs.ForId(guildId, set => set.Include(x => x.VcRoleInfos));
var toDelete = conf.VcRoleInfos.FirstOrDefault(x => x.VoiceChannelId == vcId); // remove old one
if(toDelete != null)
{
uow._context.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.GuildConfigs.ForId(guildId, set => set.Include(x => x.VcRoleInfos));
var toRemove = conf.VcRoleInfos.Where(x => x.VoiceChannelId == vcId).ToList();
uow._context.RemoveRange(toRemove);
uow.SaveChanges();
}
return true;
}
private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
SocketVoiceState newState)
{
var gusr = usr as SocketGuildUser;
if (gusr == 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

@@ -0,0 +1,90 @@
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
{
public partial class Administration
{
[Group]
public class TimeZoneCommands : NadekoSubmodule<GuildTimezoneService>
{
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Timezones(int page = 1)
{
page--;
if (page < 0 || page > 20)
return;
var timezones = TimeZoneInfo.GetSystemTimeZones()
.OrderBy(x => x.BaseUtcOffset)
.ToArray();
var timezonesPerPage = 20;
var curTime = DateTimeOffset.UtcNow;
var i = 0;
var timezoneStrings = timezones
.Select(x => (x, ++i % 2 == 0))
.Select(data =>
{
var (tzInfo, flip) = data;
var nameStr = $"{tzInfo.Id,-30}";
var offset = curTime.ToOffset(tzInfo.GetUtcOffset(curTime)).ToString("zzz");
if (flip)
{
return $"{offset} {Format.Code(nameStr)}";
}
else
{
return $"{Format.Code(offset)} {nameStr}";
}
});
await ctx.SendPaginatedConfirmAsync(page,
(curPage) => new EmbedBuilder()
.WithOkColor()
.WithTitle(GetText("timezones_available"))
.WithDescription(string.Join("\n", timezoneStrings
.Skip(curPage * timezonesPerPage)
.Take(timezonesPerPage))),
timezones.Length, timezonesPerPage).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Timezone()
{
await ReplyConfirmLocalizedAsync("timezone_guild", _service.GetTimeZoneOrUtc(ctx.Guild.Id)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, 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 == null)
{
await ReplyErrorLocalizedAsync("timezone_not_found").ConfigureAwait(false);
return;
}
_service.SetTimeZone(ctx.Guild.Id, tz);
await ctx.Channel.SendConfirmAsync(tz.ToString()).ConfigureAwait(false);
}
}
}
}

View File

@@ -0,0 +1,802 @@
using CommandLine;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Core.Common;
using NadekoBot.Core.Common.TypeReaders.Models;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Modules.Permissions.Services;
using Serilog;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class UserPunishCommands : NadekoSubmodule<UserPunishService>
{
private readonly MuteService _mute;
private readonly BlacklistService _blacklistService;
public UserPunishCommands(MuteService mute, BlacklistService blacklistService)
{
_mute = mute;
_blacklistService = blacklistService;
}
private async Task<bool> CheckRoleHierarchy(IGuildUser target)
{
var curUser = ((SocketGuild) ctx.Guild).CurrentUser;
var ownerId = Context.Guild.OwnerId;
var modMaxRole = ((IGuildUser) ctx.User).GetRoles().Max(r => r.Position);
var targetMaxRole = target.GetRoles().Max(r => r.Position);
var botMaxRole = curUser.GetRoles().Max(r => r.Position);
// bot can't punish a user who is higher in the hierarchy. Discord will return 403
// moderator can be owner, in which case role hierarchy doesn't matter
// otherwise, moderator has to have a higher role
if ((botMaxRole <= targetMaxRole || (Context.User.Id != ownerId && targetMaxRole >= modMaxRole)) || target.Id == ownerId)
{
await ReplyErrorLocalizedAsync("hierarchy");
return false;
}
return true;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
public async Task Warn(IGuildUser user, [Leftover] string reason = null)
{
if (!await CheckRoleHierarchy(user))
return;
var dmFailed = false;
try
{
await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).EmbedAsync(new EmbedBuilder().WithErrorColor()
.WithDescription(GetText("warned_on", ctx.Guild.ToString()))
.AddField(efb => efb.WithName(GetText("moderator")).WithValue(ctx.User.ToString()))
.AddField(efb => efb.WithName(GetText("reason")).WithValue(reason ?? "-")))
.ConfigureAwait(false);
}
catch
{
dmFailed = true;
}
WarningPunishment punishment;
try
{
punishment = await _service.Warn(ctx.Guild, user.Id, ctx.User, reason).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(ex.Message);
var errorEmbed = new EmbedBuilder()
.WithErrorColor()
.WithDescription(GetText("cant_apply_punishment"));
if (dmFailed)
{
errorEmbed.WithFooter("⚠️ " + GetText("unable_to_dm_user"));
}
await ctx.Channel.EmbedAsync(errorEmbed);
return;
}
var embed = new EmbedBuilder()
.WithOkColor();
if (punishment == null)
{
embed.WithDescription(GetText("user_warned",
Format.Bold(user.ToString())));
}
else
{
embed.WithDescription(GetText("user_warned_and_punished", Format.Bold(user.ToString()),
Format.Bold(punishment.Punishment.ToString())));
}
if (dmFailed)
{
embed.WithFooter("⚠️ " + GetText("unable_to_dm_user"));
}
await ctx.Channel.EmbedAsync(embed);
}
public class WarnExpireOptions : INadekoCommandOptions
{
[Option('d', "delete", Default = false, HelpText = "Delete warnings instead of clearing them.")]
public bool Delete { get; set; } = false;
public void NormalizeOptions()
{
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[NadekoOptions(typeof(WarnExpireOptions))]
[Priority(1)]
public async Task WarnExpire()
{
var expireDays = await _service.GetWarnExpire(ctx.Guild.Id);
if (expireDays == 0)
await ReplyConfirmLocalizedAsync("warns_dont_expire");
else
await ReplyConfirmLocalizedAsync("warns_expire_in", expireDays);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[NadekoOptions(typeof(WarnExpireOptions))]
[Priority(2)]
public async Task WarnExpire(int days, params string[] args)
{
if (days < 0 || days > 366)
return;
var opts = OptionsParser.ParseFrom<WarnExpireOptions>(args);
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
await _service.WarnExpireAsync(ctx.Guild.Id, days, opts.Delete).ConfigureAwait(false);
if(days == 0)
{
await ReplyConfirmLocalizedAsync("warn_expire_reset").ConfigureAwait(false);
return;
}
if (opts.Delete)
{
await ReplyConfirmLocalizedAsync("warn_expire_set_delete", Format.Bold(days.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync("warn_expire_set_clear", Format.Bold(days.ToString())).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[Priority(2)]
public Task Warnlog(int page, [Leftover] IGuildUser user = null)
{
user ??= (IGuildUser) ctx.User;
return Warnlog(page, user.Id);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(3)]
public Task Warnlog(IGuildUser user = null)
{
user ??= (IGuildUser) ctx.User;
return ctx.User.Id == user.Id || ((IGuildUser)ctx.User).GuildPermissions.BanMembers ? Warnlog(user.Id) : Task.CompletedTask;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[Priority(0)]
public Task Warnlog(int page, ulong userId)
=> InternalWarnlog(userId, page - 1);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[Priority(1)]
public Task Warnlog(ulong userId)
=> InternalWarnlog(userId, 0);
private async Task InternalWarnlog(ulong userId, int inputPage)
{
if (inputPage < 0)
return;
var allWarnings = _service.UserWarnings(ctx.Guild.Id, userId);
await ctx.SendPaginatedConfirmAsync(inputPage, page =>
{
var warnings = allWarnings
.Skip(page * 9)
.Take(9)
.ToArray();
var user = (ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString();
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle(GetText("warnlog_for", user));
if (!warnings.Any())
{
embed.WithDescription(GetText("warnings_none"));
}
else
{
var i = page * 9;
foreach (var w in warnings)
{
i++;
var name = GetText("warned_on_by",
w.DateAdded.Value.ToString("dd.MM.yyy"),
w.DateAdded.Value.ToString("HH:mm"),
w.Moderator);
if (w.Forgiven)
name = $"{Format.Strikethrough(name)} {GetText("warn_cleared_by", w.ForgivenBy)}";
embed.AddField($"#`{i}` " + name, w.Reason.TrimTo(1020));
}
}
return embed;
}, allWarnings.Length, 9);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
public async Task WarnlogAll(int page = 1)
{
if (--page < 0)
return;
var warnings = _service.WarnlogAll(ctx.Guild.Id);
await ctx.SendPaginatedConfirmAsync(page, (curPage) =>
{
var ws = warnings.Skip(curPage * 15)
.Take(15)
.ToArray()
.Select(x =>
{
var all = x.Count();
var forgiven = x.Count(y => y.Forgiven);
var total = all - forgiven;
var usr = ((SocketGuild)ctx.Guild).GetUser(x.Key);
return (usr?.ToString() ?? x.Key.ToString()) + $" | {total} ({all} - {forgiven})";
});
return new EmbedBuilder().WithOkColor()
.WithTitle(GetText("warnings_list"))
.WithDescription(string.Join("\n", ws));
}, warnings.Length, 15).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
public Task Warnclear(IGuildUser user, int index = 0)
=> Warnclear(user.Id, index);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
public async Task Warnclear(ulong userId, int index = 0)
{
if (index < 0)
return;
var success = await _service.WarnClearAsync(ctx.Guild.Id, userId, index, ctx.User.ToString());
var userStr = Format.Bold((ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString());
if (index == 0)
{
await ReplyConfirmLocalizedAsync("warnings_cleared", userStr).ConfigureAwait(false);
}
else
{
if (success)
{
await ReplyConfirmLocalizedAsync("warning_cleared", Format.Bold(index.ToString()), userStr)
.ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync("warning_clear_fail").ConfigureAwait(false);
}
}
}
public enum AddRole
{
AddRole
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[Priority(1)]
public async Task WarnPunish(int number, AddRole _, IRole role, StoopidTime time = null)
{
var punish = PunishmentAction.AddRole;
var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time, role);
if (!success)
return;
if (time is null)
{
await ReplyConfirmLocalizedAsync("warn_punish_set",
Format.Bold(punish.ToString()),
Format.Bold(number.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync("warn_punish_set_timed",
Format.Bold(punish.ToString()),
Format.Bold(number.ToString()),
Format.Bold(time.Input)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
public async Task WarnPunish(int number, PunishmentAction punish, StoopidTime time = null)
{
// this should never happen. Addrole has its own method with higher priority
if (punish == PunishmentAction.AddRole)
return;
var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time);
if (!success)
return;
if (time is null)
{
await ReplyConfirmLocalizedAsync("warn_punish_set",
Format.Bold(punish.ToString()),
Format.Bold(number.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync("warn_punish_set_timed",
Format.Bold(punish.ToString()),
Format.Bold(number.ToString()),
Format.Bold(time.Input)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
public async Task WarnPunish(int number)
{
if (!_service.WarnPunishRemove(ctx.Guild.Id, number))
{
return;
}
await ReplyConfirmLocalizedAsync("warn_punish_rem",
Format.Bold(number.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WarnPunishList()
{
var ps = _service.WarnPunishList(ctx.Guild.Id);
string list;
if (ps.Any())
{
list = string.Join("\n", ps.Select(x => $"{x.Count} -> {x.Punishment} {(x.Punishment == PunishmentAction.AddRole ? $"<@&{x.RoleId}>" : "")} {(x.Time <= 0 ? "" : x.Time.ToString() + "m")} "));
}
else
{
list = GetText("warnpl_none");
}
await ctx.Channel.SendConfirmAsync(
GetText("warn_punish_list"),
list).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[Priority(1)]
public async Task Ban(StoopidTime time, IUser user, [Leftover] string msg = null)
{
if (time.Time > TimeSpan.FromDays(49))
return;
var guildUser = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(Context.Guild.Id, user.Id);
if (guildUser != null && !await CheckRoleHierarchy(guildUser))
return;
var dmFailed = false;
if (guildUser != null)
{
try
{
var defaultMessage = GetText("bandm", Format.Bold(ctx.Guild.Name), msg);
var embed = _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, time.Time);
if (!(embed is null))
{
var userChannel = await guildUser.GetOrCreateDMChannelAsync();
await userChannel.EmbedAsync(embed);
}
}
catch
{
dmFailed = true;
}
}
await _mute.TimedBan(Context.Guild, user, time.Time, ctx.User.ToString() + " | " + msg).ConfigureAwait(false);
var toSend = new EmbedBuilder().WithOkColor()
.WithTitle("⛔️ " + GetText("banned_user"))
.AddField(efb => efb.WithName(GetText("username")).WithValue(user.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("ID").WithValue(user.Id.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("duration")).WithValue($"{time.Time.Days}d {time.Time.Hours}h {time.Time.Minutes}m").WithIsInline(true));
if (dmFailed)
{
toSend.WithFooter("⚠️ " + GetText("unable_to_dm_user"));
}
await ctx.Channel.EmbedAsync(toSend)
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[Priority(0)]
public async Task Ban(ulong userId, [Leftover] string msg = null)
{
var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(Context.Guild.Id, userId);
if (user is null)
{
await ctx.Guild.AddBanAsync(userId, 7, ctx.User.ToString() + " | " + msg);
await ctx.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("⛔️ " + GetText("banned_user"))
.AddField(efb => efb.WithName("ID").WithValue(userId.ToString()).WithIsInline(true)))
.ConfigureAwait(false);
}
else
{
await Ban(user, msg);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[Priority(2)]
public async Task Ban(IGuildUser user, [Leftover] string msg = null)
{
if (!await CheckRoleHierarchy(user))
return;
var dmFailed = false;
try
{
var defaultMessage = GetText("bandm", Format.Bold(ctx.Guild.Name), msg);
var embed = _service.GetBanUserDmEmbed(Context, user, defaultMessage, msg, null);
if (!(embed is null))
{
var userChannel = await user.GetOrCreateDMChannelAsync();
await userChannel.EmbedAsync(embed);
}
}
catch
{
dmFailed = true;
}
await ctx.Guild.AddBanAsync(user, 7, ctx.User.ToString() + " | " + msg).ConfigureAwait(false);
var toSend = new EmbedBuilder().WithOkColor()
.WithTitle("⛔️ " + GetText("banned_user"))
.AddField(efb => efb.WithName(GetText("username")).WithValue(user.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("ID").WithValue(user.Id.ToString()).WithIsInline(true));
if (dmFailed)
{
toSend.WithFooter("⚠️ " + GetText("unable_to_dm_user"));
}
await ctx.Channel.EmbedAsync(toSend)
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
public async Task BanMessage([Leftover] string message = null)
{
if (message is null)
{
var template = _service.GetBanTemplate(Context.Guild.Id);
if (template is null)
{
await ReplyConfirmLocalizedAsync("banmsg_default");
return;
}
await Context.Channel.SendConfirmAsync(template);
return;
}
_service.SetBanTemplate(Context.Guild.Id, message);
await ctx.OkAsync();
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
public async Task BanMsgReset()
{
_service.SetBanTemplate(Context.Guild.Id, null);
await ctx.OkAsync();
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[Priority(0)]
public Task BanMessageTest([Leftover] string reason = null)
=> InternalBanMessageTest(reason, null);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[Priority(1)]
public Task BanMessageTest(StoopidTime duration, [Leftover] string reason = null)
=> InternalBanMessageTest(reason, duration.Time);
private async Task InternalBanMessageTest(string reason, TimeSpan? duration)
{
var dmChannel = await ctx.User.GetOrCreateDMChannelAsync();
var defaultMessage = GetText("bandm", Format.Bold(ctx.Guild.Name), reason);
var crEmbed = _service.GetBanUserDmEmbed(Context,
(IGuildUser)Context.User,
defaultMessage,
reason,
duration);
if (crEmbed is null)
{
await ConfirmLocalizedAsync("bandm_disabled");
}
else
{
try
{
await dmChannel.EmbedAsync(crEmbed);
}
catch (Exception)
{
await ReplyErrorLocalizedAsync("unable_to_dm_user");
return;
}
await Context.OkAsync();
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
public async Task Unban([Leftover] string user)
{
var bans = await ctx.Guild.GetBansAsync().ConfigureAwait(false);
var bun = bans.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == user.ToLowerInvariant());
if (bun == null)
{
await ReplyErrorLocalizedAsync("user_not_found").ConfigureAwait(false);
return;
}
await UnbanInternal(bun.User).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
public async Task Unban(ulong userId)
{
var bans = await ctx.Guild.GetBansAsync().ConfigureAwait(false);
var bun = bans.FirstOrDefault(x => x.User.Id == userId);
if (bun == null)
{
await ReplyErrorLocalizedAsync("user_not_found").ConfigureAwait(false);
return;
}
await UnbanInternal(bun.User).ConfigureAwait(false);
}
private async Task UnbanInternal(IUser user)
{
await ctx.Guild.RemoveBanAsync(user).ConfigureAwait(false);
await ReplyConfirmLocalizedAsync("unbanned_user", Format.Bold(user.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.KickMembers | GuildPerm.ManageMessages)]
[BotPerm(GuildPerm.BanMembers)]
public Task Softban(IGuildUser user, [Leftover] string msg = null)
=> SoftbanInternal(user, msg);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.KickMembers | GuildPerm.ManageMessages)]
[BotPerm(GuildPerm.BanMembers)]
public async Task Softban(ulong userId, [Leftover] string msg = null)
{
var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(Context.Guild.Id, userId);
if (user is null)
return;
await SoftbanInternal(user, msg);
}
private async Task SoftbanInternal(IGuildUser user, [Leftover] string msg = null)
{
if (!await CheckRoleHierarchy(user))
return;
var dmFailed = false;
try
{
await user.SendErrorAsync(GetText("sbdm", Format.Bold(ctx.Guild.Name), msg)).ConfigureAwait(false);
}
catch
{
dmFailed = true;
}
await ctx.Guild.AddBanAsync(user, 7, "Softban | " + ctx.User.ToString() + " | " + msg).ConfigureAwait(false);
try { await ctx.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
catch { await ctx.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
var toSend = new EmbedBuilder().WithOkColor()
.WithTitle("☣ " + GetText("sb_user"))
.AddField(efb => efb.WithName(GetText("username")).WithValue(user.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("ID").WithValue(user.Id.ToString()).WithIsInline(true));
if (dmFailed)
{
toSend.WithFooter("⚠️ " + GetText("unable_to_dm_user"));
}
await ctx.Channel.EmbedAsync(toSend)
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.KickMembers)]
[BotPerm(GuildPerm.KickMembers)]
[Priority(1)]
public Task Kick(IGuildUser user, [Leftover] string msg = null)
=> KickInternal(user, msg);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.KickMembers)]
[BotPerm(GuildPerm.KickMembers)]
[Priority(0)]
public async Task Kick(ulong userId, [Leftover] string msg = null)
{
var user = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(Context.Guild.Id, userId);
if (user is null)
return;
await KickInternal(user, msg);
}
public async Task KickInternal(IGuildUser user, string msg = null)
{
if (!await CheckRoleHierarchy(user))
return;
var dmFailed = false;
try
{
await user.SendErrorAsync(GetText("kickdm", Format.Bold(ctx.Guild.Name), msg))
.ConfigureAwait(false);
}
catch
{
dmFailed = true;
}
await user.KickAsync(ctx.User.ToString() + " | " + msg).ConfigureAwait(false);
var toSend = new EmbedBuilder().WithOkColor()
.WithTitle(GetText("kicked_user"))
.AddField(efb => efb.WithName(GetText("username")).WithValue(user.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("ID").WithValue(user.Id.ToString()).WithIsInline(true));
if (dmFailed)
{
toSend.WithFooter("⚠️ " + GetText("unable_to_dm_user"));
}
await ctx.Channel.EmbedAsync(toSend)
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[OwnerOnly]
public async Task MassKill([Leftover] string people)
{
if (string.IsNullOrWhiteSpace(people))
return;
var (bans, missing) = _service.MassKill((SocketGuild)ctx.Guild, people);
var missStr = string.Join("\n", missing);
if (string.IsNullOrWhiteSpace(missStr))
missStr = "-";
//send a message but don't wait for it
var banningMessageTask = ctx.Channel.EmbedAsync(new EmbedBuilder()
.WithDescription(GetText("mass_kill_in_progress", bans.Count()))
.AddField(GetText("invalid", missing), missStr)
.WithOkColor());
//do the banning
await Task.WhenAll(bans
.Where(x => x.Id.HasValue)
.Select(x => ctx.Guild.AddBanAsync(x.Id.Value, 7, x.Reason, new RequestOptions()
{
RetryMode = RetryMode.AlwaysRetry,
})))
.ConfigureAwait(false);
//wait for the message and edit it
var banningMessage = await banningMessageTask.ConfigureAwait(false);
await banningMessage.ModifyAsync(x => x.Embed = new EmbedBuilder()
.WithDescription(GetText("mass_kill_completed", bans.Count()))
.AddField(GetText("invalid", missing), missStr)
.WithOkColor()
.Build()).ConfigureAwait(false);
}
}
}
}

View File

@@ -0,0 +1,93 @@
using System.Collections.Concurrent;
using System.Linq;
using Discord;
using Discord.Commands;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Services;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class VcRoleCommands : NadekoSubmodule<VcRoleService>
{
[NadekoCommand, Usage, Description, Aliases]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireContext(ContextType.Guild)]
public async Task VcRoleRm(ulong vcId)
{
if (_service.RemoveVcRole(ctx.Guild.Id, vcId))
{
await ReplyConfirmLocalizedAsync("vcrole_removed", Format.Bold(vcId.ToString())).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalizedAsync("vcrole_not_found").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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 == null || vc.GuildId != user.GuildId)
{
await ReplyErrorLocalizedAsync("must_be_in_voice").ConfigureAwait(false);
return;
}
if (role == null)
{
if (_service.RemoveVcRole(ctx.Guild.Id, vc.Id))
{
await ReplyConfirmLocalizedAsync("vcrole_removed", Format.Bold(vc.Name)).ConfigureAwait(false);
}
}
else
{
_service.AddVcRole(ctx.Guild.Id, role, vc.Id);
await ReplyConfirmLocalizedAsync("vcrole_added", Format.Bold(vc.Name), Format.Bold(role.Name)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, 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("no_vcroles");
}
else
{
text = string.Join("\n", roles.Select(x =>
$"{Format.Bold(guild.GetVoiceChannel(x.Key)?.Name ?? x.Key.ToString())} => {x.Value}"));
}
}
else
{
text = GetText("no_vcroles");
}
await ctx.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("vc_role_list"))
.WithDescription(text))
.ConfigureAwait(false);
}
}
}
}