Merge remote-tracking branch 'Kwoth/dev' into dev

# Conflicts:
#	src/NadekoBot/Resources/CommandStrings.resx
This commit is contained in:
Shikhir Arora 2017-02-15 07:10:31 -05:00
commit 26e769c6dd
73 changed files with 7286 additions and 1299 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class guildtimezoneandlocale : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Locale",
table: "GuildConfigs",
nullable: true,
defaultValue: null);
migrationBuilder.AddColumn<string>(
name: "TimeZoneId",
table: "GuildConfigs",
nullable: true,
defaultValue: null);
migrationBuilder.AddColumn<string>(
name: "Locale",
table: "BotConfig",
nullable: true,
defaultValue: null);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Locale",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "TimeZoneId",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "Locale",
table: "BotConfig");
}
}
}

View File

@ -128,6 +128,8 @@ namespace NadekoBot.Migrations
b.Property<string>("HelpString");
b.Property<string>("Locale");
b.Property<int>("MigrationVersion");
b.Property<int>("MinimumBetAmount");
@ -469,6 +471,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("GuildId");
b.Property<string>("Locale");
b.Property<int?>("LogSettingId");
b.Property<string>("MuteRoleName");
@ -483,6 +487,8 @@ namespace NadekoBot.Migrations
b.Property<bool>("SendDmGreetMessage");
b.Property<string>("TimeZoneId");
b.Property<bool>("VerbosePermissions");
b.Property<bool>("VoicePlusTextEnabled");

View File

@ -1,18 +1,14 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NadekoBot.Services;
using NadekoBot.Attributes;
using Discord.WebSocket;
using NadekoBot.Services.Database.Models;
using System.Net.Http;
using System.IO;
using static NadekoBot.Modules.Permissions.Permissions;
using System.Collections.Concurrent;
using NLog;
@ -20,21 +16,18 @@ using NLog;
namespace NadekoBot.Modules.Administration
{
[NadekoModule("Administration", ".")]
public partial class Administration : DiscordModule
public partial class Administration : NadekoModule
{
private static ConcurrentHashSet<ulong> deleteMessagesOnCommand { get; }
private static ConcurrentDictionary<ulong, string> GuildMuteRoles { get; } = new ConcurrentDictionary<ulong, string>();
private static ConcurrentHashSet<ulong> DeleteMessagesOnCommand { get; } = new ConcurrentHashSet<ulong>();
private new static Logger _log { get; }
private new static readonly Logger _log;
static Administration()
{
_log = LogManager.GetCurrentClassLogger();
NadekoBot.CommandHandler.CommandExecuted += DelMsgOnCmd_Handler;
DeleteMessagesOnCommand = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
deleteMessagesOnCommand = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
}
@ -47,7 +40,7 @@ namespace NadekoBot.Modules.Administration
var channel = msg.Channel as SocketTextChannel;
if (channel == null)
return;
if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune")
if (deleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune")
await msg.DeleteAsync().ConfigureAwait(false);
}
catch (Exception ex)
@ -74,17 +67,17 @@ namespace NadekoBot.Modules.Administration
PermRole = config.PermissionRole,
Verbose = config.VerbosePermissions,
};
Permissions.Permissions.Cache.AddOrUpdate(channel.Guild.Id,
Cache.AddOrUpdate(channel.Guild.Id,
toAdd, (id, old) => toAdd);
await uow.CompleteAsync();
}
await channel.SendConfirmAsync($"{Context.Message.Author.Mention} 🆗 **Permissions for this server are reset.**");
await ReplyConfirmLocalized("perms_reset").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
[RequireBotPermission(GuildPermission.ManageMessages)]
public async Task Delmsgoncmd()
{
bool enabled;
@ -97,29 +90,31 @@ namespace NadekoBot.Modules.Administration
}
if (enabled)
{
DeleteMessagesOnCommand.Add(Context.Guild.Id);
await Context.Channel.SendConfirmAsync("✅ **Now automatically deleting successful command invokations.**").ConfigureAwait(false);
deleteMessagesOnCommand.Add(Context.Guild.Id);
await ReplyConfirmLocalized("delmsg_on").ConfigureAwait(false);
}
else
{
DeleteMessagesOnCommand.TryRemove(Context.Guild.Id);
await Context.Channel.SendConfirmAsync("❗**Stopped automatic deletion of successful command invokations.**").ConfigureAwait(false);
deleteMessagesOnCommand.TryRemove(Context.Guild.Id);
await ReplyConfirmLocalized("delmsg_off").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task Setrole(IGuildUser usr, [Remainder] IRole role)
{
try
{
await usr.AddRolesAsync(role).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($" Successfully added role **{role.Name}** to user **{usr.Username}**").ConfigureAwait(false);
await ReplyConfirmLocalized("setrole", Format.Bold(role.Name), Format.Bold(usr.ToString()))
.ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync("⚠️ Failed to add role. **Bot has insufficient permissions.**\n").ConfigureAwait(false);
await ReplyErrorLocalized("setrole_err").ConfigureAwait(false);
Console.WriteLine(ex.ToString());
}
}
@ -127,95 +122,94 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task Removerole(IGuildUser usr, [Remainder] IRole role)
{
try
{
await usr.RemoveRolesAsync(role).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($" Successfully removed role **{role.Name}** from user **{usr.Username}**").ConfigureAwait(false);
await ReplyConfirmLocalized("remrole", Format.Bold(role.Name), Format.Bold(usr.ToString())).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ Failed to remove role. Most likely reason: **Insufficient permissions.**").ConfigureAwait(false);
await ReplyErrorLocalized("remrole_err").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task RenameRole(IRole roleToEdit, string newname)
{
try
{
if (roleToEdit.Position > (await Context.Guild.GetCurrentUserAsync().ConfigureAwait(false)).GetRoles().Max(r => r.Position))
{
await Context.Channel.SendErrorAsync("🚫 You can't edit roles higher than your highest role.").ConfigureAwait(false);
await ReplyErrorLocalized("renrole_perms").ConfigureAwait(false);
return;
}
await roleToEdit.ModifyAsync(g => g.Name = newname).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("✅ Role renamed.").ConfigureAwait(false);
await ReplyConfirmLocalized("renrole").ConfigureAwait(false);
}
catch (Exception)
{
await Context.Channel.SendErrorAsync("⚠️ Failed to rename role. Probably **insufficient permissions.**").ConfigureAwait(false);
await ReplyErrorLocalized("renrole_err").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task RemoveAllRoles([Remainder] IGuildUser user)
{
try
{
await user.RemoveRolesAsync(user.GetRoles()).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"🗑 Successfully removed **all** roles from user **{user.Username}**").ConfigureAwait(false);
await ReplyConfirmLocalized("rar", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ Failed to remove roles. Most likely reason: **Insufficient permissions.**").ConfigureAwait(false);
await ReplyErrorLocalized("rar_err").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task CreateRole([Remainder] string roleName = null)
{
if (string.IsNullOrWhiteSpace(roleName))
return;
try
{
var r = await Context.Guild.CreateRoleAsync(roleName).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"✅ Successfully created role **{r.Name}**.").ConfigureAwait(false);
}
catch (Exception)
{
await Context.Channel.SendErrorAsync("⚠️ Unspecified error.").ConfigureAwait(false);
}
await ReplyConfirmLocalized("cr", Format.Bold(r.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireBotPermission(GuildPermission.ManageRoles)]
public async Task RoleColor(params string[] args)
{
if (args.Count() != 2 && args.Count() != 4)
if (args.Length != 2 && args.Length != 4)
{
await Context.Channel.SendErrorAsync("❌ The parameters specified are **invalid.**").ConfigureAwait(false);
await ReplyErrorLocalized("rc_params").ConfigureAwait(false);
return;
}
var roleName = args[0].ToUpperInvariant();
var role = Context.Guild.Roles.Where(r => r.Name.ToUpperInvariant() == roleName).FirstOrDefault();
var role = Context.Guild.Roles.FirstOrDefault(r => r.Name.ToUpperInvariant() == roleName);
if (role == null)
{
await Context.Channel.SendErrorAsync("🚫 That role **does not exist.**").ConfigureAwait(false);
await ReplyErrorLocalized("rc_not_exist").ConfigureAwait(false);
return;
}
try
{
var rgb = args.Count() == 4;
var rgb = args.Length == 4;
var arg1 = args[1].Replace("#", "");
var red = Convert.ToByte(rgb ? int.Parse(arg1) : Convert.ToInt32(arg1.Substring(0, 2), 16));
@ -223,64 +217,57 @@ namespace NadekoBot.Modules.Administration
var blue = Convert.ToByte(rgb ? int.Parse(args[3]) : Convert.ToInt32(arg1.Substring(4, 2), 16));
await role.ModifyAsync(r => r.Color = new Color(red, green, blue)).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"☑️ Role **{role.Name}'s** color has been changed.").ConfigureAwait(false);
await ReplyConfirmLocalized("rc", Format.Bold(role.Name)).ConfigureAwait(false);
}
catch (Exception)
{
await Context.Channel.SendErrorAsync("⚠️ Error occured, most likely **invalid parameters** or **insufficient permissions.**").ConfigureAwait(false);
await ReplyErrorLocalized("rc_perms").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task Ban(IGuildUser user, [Remainder] string msg = null)
{
if (string.IsNullOrWhiteSpace(msg))
{
msg = "❗No reason provided.";
}
if (Context.User.Id != user.Guild.OwnerId && (user.GetRoles().Select(r => r.Position).Max() >= ((IGuildUser)Context.User).GetRoles().Select(r => r.Position).Max()))
{
await Context.Channel.SendErrorAsync("⚠️ You can't use this command on users with a role higher or equal to yours in the role hierarchy.").ConfigureAwait(false);
await ReplyErrorLocalized("hierarchy").ConfigureAwait(false);
return;
}
if (!string.IsNullOrWhiteSpace(msg))
{
try
{
await (await user.CreateDMChannelAsync()).SendErrorAsync($"⛔️ **You have been BANNED from `{Context.Guild.Name}` server.**\n" +
$"⚖ *Reason:* {msg}").ConfigureAwait(false);
await user.SendErrorAsync(GetText("bandm", Format.Bold(Context.Guild.Name), msg));
await Task.Delay(2000).ConfigureAwait(false);
}
catch { }
}
try
{
await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("⛔️ **Banned** user **" + user.Username + "** ID: `" + user.Id + "`").ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ **Error.** Most likely I don't have sufficient permissions.").ConfigureAwait(false);
// ignored
}
}
await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false);
await Context.Channel.EmbedAsync(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)))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.KickMembers)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task Softban(IGuildUser user, [Remainder] string msg = null)
{
if (string.IsNullOrWhiteSpace(msg))
{
msg = "❗No reason provided.";
}
if (Context.User.Id != user.Guild.OwnerId && user.GetRoles().Select(r => r.Position).Max() >= ((IGuildUser)Context.User).GetRoles().Select(r => r.Position).Max())
{
await Context.Channel.SendErrorAsync("⚠️ You can't use this command on users with a role higher or equal to yours in the role hierarchy.");
await ReplyErrorLocalized("hierarchy").ConfigureAwait(false);
return;
}
@ -288,161 +275,162 @@ namespace NadekoBot.Modules.Administration
{
try
{
await user.SendErrorAsync($"☣ **You have been SOFT-BANNED from `{Context.Guild.Name}` server.**\n" +
$"⚖ *Reason:* {msg}").ConfigureAwait(false);
await user.SendErrorAsync(GetText("sbdm", Format.Bold(Context.Guild.Name), msg));
await Task.Delay(2000).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
try
{
await Context.Guild.AddBanAsync(user, 7).ConfigureAwait(false);
try { await Context.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
catch { await Context.Guild.RemoveBanAsync(user).ConfigureAwait(false); }
await Context.Channel.SendConfirmAsync("☣ **Soft-Banned** user **" + user.Username + "** ID: `" + user.Id + "`").ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ Error. Most likely I don't have sufficient permissions.").ConfigureAwait(false);
}
await Context.Channel.EmbedAsync(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)))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.KickMembers)]
[RequireBotPermission(GuildPermission.KickMembers)]
public async Task Kick(IGuildUser user, [Remainder] string msg = null)
{
if (user == null)
{
await Context.Channel.SendErrorAsync("❗User not found.").ConfigureAwait(false);
return;
}
if (Context.Message.Author.Id != user.Guild.OwnerId && user.GetRoles().Select(r => r.Position).Max() >= ((IGuildUser)Context.User).GetRoles().Select(r => r.Position).Max())
{
await Context.Channel.SendErrorAsync("⚠️ You can't use this command on users with a role higher or equal to yours in the role hierarchy.");
await ReplyErrorLocalized("hierarchy").ConfigureAwait(false);
return;
}
if (!string.IsNullOrWhiteSpace(msg))
{
try
{
await user.SendErrorAsync($"‼️**You have been KICKED from `{Context.Guild.Name}` server.**\n" +
$"⚖ *Reason:* {msg}").ConfigureAwait(false);
await user.SendErrorAsync(GetText("kickdm", Format.Bold(Context.Guild.Name), msg));
await Task.Delay(2000).ConfigureAwait(false);
}
catch { }
}
try
{
await user.KickAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("‼️**Kicked** user **" + user.Username + "** ID: `" + user.Id + "`").ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ Error. Most likely I don't have sufficient permissions.").ConfigureAwait(false);
}
await Context.Channel.EmbedAsync(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)))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.DeafenMembers)]
[RequireBotPermission(GuildPermission.DeafenMembers)]
public async Task Deafen(params IGuildUser[] users)
{
if (!users.Any())
return;
try
{
foreach (var u in users)
{
try
{
await u.ModifyAsync(usr => usr.Deaf = true).ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("🔇 **Deafen** successful.").ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false);
// ignored
}
}
await ReplyConfirmLocalized("deafen").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.DeafenMembers)]
[RequireBotPermission(GuildPermission.DeafenMembers)]
public async Task UnDeafen(params IGuildUser[] users)
{
if (!users.Any())
return;
try
{
foreach (var u in users)
{
try
{
await u.ModifyAsync(usr => usr.Deaf = false).ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("🔊 **Undeafen** successful.").ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false);
// ignored
}
}
await ReplyConfirmLocalized("undeafen").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task DelVoiChanl([Remainder] IVoiceChannel voiceChannel)
{
await voiceChannel.DeleteAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"🗑 Removed voice channel **{voiceChannel.Name}** successfully.").ConfigureAwait(false);
await ReplyConfirmLocalized("delvoich", Format.Bold(voiceChannel.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task CreatVoiChanl([Remainder] string channelName)
{
var ch = await Context.Guild.CreateVoiceChannelAsync(channelName).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"✅ Created voice channel **{ch.Name}**. ID: `{ch.Id}`").ConfigureAwait(false);
await ReplyConfirmLocalized("createvoich",Format.Bold(ch.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task DelTxtChanl([Remainder] ITextChannel toDelete)
{
await toDelete.DeleteAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"🗑 Removed text channel **{toDelete.Name}**. ID: `{toDelete.Id}`").ConfigureAwait(false);
await ReplyConfirmLocalized("deltextchan", Format.Bold(toDelete.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task CreaTxtChanl([Remainder] string channelName)
{
var txtCh = await Context.Guild.CreateTextChannelAsync(channelName).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"✅ Added text channel **{txtCh.Name}**. ID: `{txtCh.Id}`").ConfigureAwait(false);
await ReplyConfirmLocalized("createtextchan", Format.Bold(txtCh.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task SetTopic([Remainder] string topic = null)
{
var channel = (ITextChannel)Context.Channel;
topic = topic ?? "";
await channel.ModifyAsync(c => c.Topic = topic);
await channel.SendConfirmAsync("🆗 **New channel topic set.**").ConfigureAwait(false);
await ReplyConfirmLocalized("set_topic").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageChannels)]
[RequireBotPermission(GuildPermission.ManageChannels)]
public async Task SetChanlName([Remainder] string name)
{
var channel = (ITextChannel)Context.Channel;
await channel.ModifyAsync(c => c.Name = name).ConfigureAwait(false);
await channel.SendConfirmAsync("🆗 **New channel name set.**").ConfigureAwait(false);
await ReplyConfirmLocalized("set_channel_name").ConfigureAwait(false);
}
@ -462,6 +450,7 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(ChannelPermission.ManageMessages)]
[RequireBotPermission(GuildPermission.ManageMessages)]
public async Task Prune(int count)
{
if (count < 1)
@ -477,6 +466,7 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(ChannelPermission.ManageMessages)]
[RequireBotPermission(GuildPermission.ManageMessages)]
public async Task Prune(IGuildUser user, int count = 100)
{
if (count < 1)
@ -495,11 +485,11 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.MentionEveryone)]
public async Task MentionRole(params IRole[] roles)
{
string send = $"❕{Context.User.Mention} has invoked a mention on the following roles ❕";
string send = "❕" +GetText("menrole",Context.User.Mention);
foreach (var role in roles)
{
send += $"\n**{role.Name}**\n";
send += string.Join(", ", (await Context.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).Distinct().Select(u => u.Mention));
send += string.Join(", ", (await Context.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).Take(50).Select(u => u.Mention));
}
while (send.Length > 2000)
@ -513,7 +503,7 @@ namespace NadekoBot.Modules.Administration
await Context.Channel.SendMessageAsync(send).ConfigureAwait(false);
}
IGuild nadekoSupportServer;
private IGuild _nadekoSupportServer;
[NadekoCommand, Usage, Description, Aliases]
public async Task Donators()
{
@ -523,18 +513,15 @@ namespace NadekoBot.Modules.Administration
{
donatorsOrdered = uow.Donators.GetDonatorsOrdered();
}
await Context.Channel.SendConfirmAsync("Thanks to the people listed below for making this project happen!", string.Join("⭐", donatorsOrdered.Select(d => d.Name))).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(GetText("donators"), string.Join("⭐", donatorsOrdered.Select(d => d.Name))).ConfigureAwait(false);
nadekoSupportServer = nadekoSupportServer ?? NadekoBot.Client.GetGuild(117523346618318850);
_nadekoSupportServer = _nadekoSupportServer ?? NadekoBot.Client.GetGuild(117523346618318850);
if (nadekoSupportServer == null)
return;
var patreonRole = nadekoSupportServer.GetRole(236667642088259585);
var patreonRole = _nadekoSupportServer?.GetRole(236667642088259585);
if (patreonRole == null)
return;
var usrs = (await nadekoSupportServer.GetUsersAsync()).Where(u => u.RoleIds.Contains(236667642088259585u));
var usrs = (await _nadekoSupportServer.GetUsersAsync()).Where(u => u.RoleIds.Contains(236667642088259585u));
await Context.Channel.SendConfirmAsync("Patreon supporters", string.Join("⭐", usrs.Select(d => d.Username))).ConfigureAwait(false);
}
@ -549,8 +536,7 @@ namespace NadekoBot.Modules.Administration
don = uow.Donators.AddOrUpdateDonator(donator.Id, donator.Username, amount);
await uow.CompleteAsync();
}
await Context.Channel.SendConfirmAsync($"Successfuly added a new donator. Total donated amount from this user: {don.Amount} 👑").ConfigureAwait(false);
await ReplyConfirmLocalized("donadd", don.Amount).ConfigureAwait(false);
}
//[NadekoCommand, Usage, Description, Aliases]

View File

@ -1,13 +1,10 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
@ -16,24 +13,23 @@ namespace NadekoBot.Modules.Administration
public partial class Administration
{
[Group]
public class AutoAssignRoleCommands : ModuleBase
public class AutoAssignRoleCommands : NadekoSubmodule
{
private static Logger _log { get; }
//guildid/roleid
private static ConcurrentDictionary<ulong, ulong> AutoAssignedRoles { get; }
private static ConcurrentDictionary<ulong, ulong> autoAssignedRoles { get; }
static AutoAssignRoleCommands()
{
_log = LogManager.GetCurrentClassLogger();
var log = LogManager.GetCurrentClassLogger();
AutoAssignedRoles = new ConcurrentDictionary<ulong, ulong>(NadekoBot.AllGuildConfigs.Where(x => x.AutoAssignRoleId != 0)
autoAssignedRoles = new ConcurrentDictionary<ulong, ulong>(NadekoBot.AllGuildConfigs.Where(x => x.AutoAssignRoleId != 0)
.ToDictionary(k => k.GuildId, v => v.AutoAssignRoleId));
NadekoBot.Client.UserJoined += async (user) =>
{
try
{
ulong roleId = 0;
AutoAssignedRoles.TryGetValue(user.Guild.Id, out roleId);
ulong roleId;
autoAssignedRoles.TryGetValue(user.Guild.Id, out roleId);
if (roleId == 0)
return;
@ -43,7 +39,7 @@ namespace NadekoBot.Modules.Administration
if (role != null)
await user.AddRolesAsync(role).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
catch (Exception ex) { log.Warn(ex); }
};
}
@ -52,20 +48,19 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task AutoAssignRole([Remainder] IRole role = null)
{
GuildConfig conf;
using (var uow = DbHandler.UnitOfWork())
{
conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
if (role == null)
{
conf.AutoAssignRoleId = 0;
ulong throwaway;
AutoAssignedRoles.TryRemove(Context.Guild.Id, out throwaway);
autoAssignedRoles.TryRemove(Context.Guild.Id, out throwaway);
}
else
{
conf.AutoAssignRoleId = role.Id;
AutoAssignedRoles.AddOrUpdate(Context.Guild.Id, role.Id, (key, val) => role.Id);
autoAssignedRoles.AddOrUpdate(Context.Guild.Id, role.Id, (key, val) => role.Id);
}
await uow.CompleteAsync().ConfigureAwait(false);
@ -73,11 +68,11 @@ namespace NadekoBot.Modules.Administration
if (role == null)
{
await Context.Channel.SendConfirmAsync("🆗 **Auto assign role** on user join is now **disabled**.").ConfigureAwait(false);
await ReplyConfirmLocalized("aar_disabled").ConfigureAwait(false);
return;
}
await Context.Channel.SendConfirmAsync("✅ **Auto assign role** on user join is now **enabled**.").ConfigureAwait(false);
await ReplyConfirmLocalized("aar_enabled").ConfigureAwait(false);
}
}
}

View File

@ -1,90 +0,0 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NLog;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class DMForwardCommands : ModuleBase
{
private static bool ForwardDMs { get; set; }
private static bool ForwardDMsToAllOwners { get; set; }
private static readonly Logger _log;
static DMForwardCommands()
{
_log = LogManager.GetCurrentClassLogger();
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMs = config.ForwardMessages;
ForwardDMsToAllOwners = config.ForwardToAllOwners;
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardMessages()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMs = config.ForwardMessages = !config.ForwardMessages;
uow.Complete();
}
if (ForwardDMs)
await Context.Channel.SendConfirmAsync("✅ **I will forward DMs from now on.**").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync("🆗 **I will stop forwarding DMs from now on.**").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardToAll()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners;
uow.Complete();
}
if (ForwardDMsToAllOwners)
await Context.Channel.SendConfirmAsync(" **I will forward DMs to all owners.**").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync(" **I will forward DMs only to the first owner.**").ConfigureAwait(false);
}
public static async Task HandleDMForwarding(SocketMessage msg, List<IDMChannel> ownerChannels)
{
if (ForwardDMs && ownerChannels.Any())
{
var title = $"DM from [{msg.Author}]({msg.Author.Id})";
if (ForwardDMsToAllOwners)
{
var msgs = await Task.WhenAll(ownerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id)
.Select(ch => ch.SendConfirmAsync(title, msg.Content))).ConfigureAwait(false);
}
else
{
var firstOwnerChannel = ownerChannels.First();
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
try { await firstOwnerChannel.SendConfirmAsync(title, msg.Content).ConfigureAwait(false); } catch { }
}
}
}
}
}
}

View File

@ -0,0 +1,234 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class LocalizationCommands : NadekoSubmodule
{
private ImmutableDictionary<string, string> supportedLocales { get; } = new Dictionary<string, string>()
{
{"en-US", "English, United States" },
{"sr-cyrl-rs", "Serbian, Cyrillic" }
}.ToImmutableDictionary();
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task LanguageSet()
{
var cul = NadekoBot.Localization.GetCultureInfo(Context.Guild);
await ReplyConfirmLocalized("lang_set_show", Format.Bold(cul.ToString()), Format.Bold(cul.NativeName))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task LanguageSet(string name)
{
try
{
CultureInfo ci;
if (name.Trim().ToLowerInvariant() == "default")
{
NadekoBot.Localization.RemoveGuildCulture(Context.Guild);
ci = NadekoBot.Localization.DefaultCultureInfo;
}
else
{
ci = new CultureInfo(name);
NadekoBot.Localization.SetGuildCulture(Context.Guild, ci);
}
await ReplyConfirmLocalized("lang_set", Format.Bold(ci.ToString()), Format.Bold(ci.NativeName)).ConfigureAwait(false);
}
catch(Exception)
{
await ReplyErrorLocalized("lang_set_fail").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task LanguageSetDefault()
{
var cul = NadekoBot.Localization.DefaultCultureInfo;
await ReplyConfirmLocalized("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")
{
NadekoBot.Localization.ResetDefaultCulture();
ci = NadekoBot.Localization.DefaultCultureInfo;
}
else
{
ci = new CultureInfo(name);
NadekoBot.Localization.SetDefaultCulture(ci);
}
await ReplyConfirmLocalized("lang_set_bot", Format.Bold(ci.ToString()), Format.Bold(ci.NativeName)).ConfigureAwait(false);
}
catch (Exception)
{
await ReplyErrorLocalized("lang_set_fail").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task LanguagesList()
{
await ReplyConfirmLocalized("lang_list",
string.Join("\n", supportedLocales.Select(x => $"{Format.Code(x.Key)} => {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

@ -1,7 +1,6 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Permissions;
@ -21,45 +20,43 @@ namespace NadekoBot.Modules.Administration
public partial class Administration
{
[Group]
public class LogCommands : ModuleBase
public class LogCommands : NadekoSubmodule
{
private const string clockEmojiUrl = "https://cdn.discordapp.com/attachments/155726317222887425/258309524966866945/clock.png";
private static DiscordShardedClient _client { get; }
private static Logger _log { get; }
private static DiscordShardedClient client { get; }
private new static Logger _log { get; }
private static string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】";
private static string currentTime => $"{DateTime.Now:HH:mm:ss}";
public static ConcurrentDictionary<ulong, LogSetting> GuildLogSettings { get; }
private static ConcurrentDictionary<ITextChannel, List<string>> PresenceUpdates { get; } = new ConcurrentDictionary<ITextChannel, List<string>>();
private static Timer timerReference { get; }
private IGoogleApiService _google { get; }
private static ConcurrentDictionary<ITextChannel, List<string>> presenceUpdates { get; } = new ConcurrentDictionary<ITextChannel, List<string>>();
private static readonly Timer _timerReference;
static LogCommands()
{
_client = NadekoBot.Client;
client = NadekoBot.Client;
_log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
using (var uow = DbHandler.UnitOfWork())
{
GuildLogSettings = new ConcurrentDictionary<ulong, LogSetting>(NadekoBot.AllGuildConfigs
.ToDictionary(g => g.GuildId, g => g.LogSetting));
}
timerReference = new Timer(async (state) =>
_timerReference = new Timer(async (state) =>
{
try
{
var keys = PresenceUpdates.Keys.ToList();
var keys = presenceUpdates.Keys.ToList();
await Task.WhenAll(keys.Select(async key =>
{
List<string> messages;
if (PresenceUpdates.TryRemove(key, out messages))
try { await key.SendConfirmAsync("Presence Updates", string.Join(Environment.NewLine, messages)); } catch { }
if (presenceUpdates.TryRemove(key, out messages))
try { await key.SendConfirmAsync(key.Guild.GetLogText("presence_updates"), string.Join(Environment.NewLine, messages)); }
catch
{
// ignored
}
}));
}
catch (Exception ex)
@ -72,23 +69,22 @@ namespace NadekoBot.Modules.Administration
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
//_client.MessageReceived += _client_MessageReceived;
_client.MessageUpdated += _client_MessageUpdated;
_client.MessageDeleted += _client_MessageDeleted;
_client.UserBanned += _client_UserBanned;
_client.UserUnbanned += _client_UserUnbanned;
_client.UserJoined += _client_UserJoined;
_client.UserLeft += _client_UserLeft;
_client.UserPresenceUpdated += _client_UserPresenceUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS;
_client.GuildMemberUpdated += _client_GuildUserUpdated;
client.MessageUpdated += _client_MessageUpdated;
client.MessageDeleted += _client_MessageDeleted;
client.UserBanned += _client_UserBanned;
client.UserUnbanned += _client_UserUnbanned;
client.UserJoined += _client_UserJoined;
client.UserLeft += _client_UserLeft;
client.UserPresenceUpdated += _client_UserPresenceUpdated;
client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated;
client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS;
client.GuildMemberUpdated += _client_GuildUserUpdated;
#if !GLOBAL_NADEKO
_client.UserUpdated += _client_UserUpdated;
client.UserUpdated += _client_UserUpdated;
#endif
_client.ChannelCreated += _client_ChannelCreated;
_client.ChannelDestroyed += _client_ChannelDestroyed;
_client.ChannelUpdated += _client_ChannelUpdated;
client.ChannelCreated += _client_ChannelCreated;
client.ChannelDestroyed += _client_ChannelDestroyed;
client.ChannelUpdated += _client_ChannelUpdated;
MuteCommands.UserMuted += MuteCommands_UserMuted;
MuteCommands.UserUnmuted += MuteCommands_UserUnmuted;
@ -119,7 +115,7 @@ namespace NadekoBot.Modules.Administration
if (before.Username != after.Username)
{
embed.WithTitle("👥 Username Changed")
embed.WithTitle("👥 " + g.GetLogText("username_changed"))
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.AddField(fb => fb.WithName("Old Name").WithValue($"{before.Username}").WithIsInline(true))
.AddField(fb => fb.WithName("New Name").WithValue($"{after.Username}").WithIsInline(true))
@ -128,7 +124,7 @@ namespace NadekoBot.Modules.Administration
}
else if (before.AvatarUrl != after.AvatarUrl)
{
embed.WithTitle("👥 Avatar Changed")
embed.WithTitle("👥" + g.GetLogText("avatar_changed"))
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithThumbnailUrl(before.AvatarUrl)
@ -159,7 +155,9 @@ namespace NadekoBot.Modules.Administration
//}
}
catch
{ }
{
// ignored
}
}
private static async Task _client_UserVoiceStateUpdated_TTS(SocketUser iusr, SocketVoiceState before, SocketVoiceState after)
@ -188,20 +186,23 @@ namespace NadekoBot.Modules.Administration
var str = "";
if (beforeVch?.Guild == afterVch?.Guild)
{
str = $"{usr.Username} moved from {beforeVch.Name} to {afterVch.Name}";
str = logChannel.Guild.GetLogText("moved", usr.Username, beforeVch?.Name, afterVch?.Name);
}
else if (beforeVch == null)
{
str = $"{usr.Username} has joined {afterVch.Name}";
str = logChannel.Guild.GetLogText("joined", usr.Username, afterVch.Name);
}
else if (afterVch == null)
{
str = $"{usr.Username} has left {beforeVch.Name}";
str = logChannel.Guild.GetLogText("left", usr.Username, beforeVch.Name);
}
var toDelete = await logChannel.SendMessageAsync(str, true).ConfigureAwait(false);
toDelete.DeleteAfter(5);
}
catch { }
catch
{
// ignored
}
}
private static async void MuteCommands_UserMuted(IGuildUser usr, MuteCommands.MuteType muteType)
@ -216,28 +217,32 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserMuted)) == null)
return;
string mutes = "";
var mutes = "";
var mutedLocalized = logChannel.Guild.GetLogText("muted_sn");
switch (muteType)
{
case MuteCommands.MuteType.Voice:
mutes = "voice chat";
mutes = "🔇 " + logChannel.Guild.GetLogText("xmuted_voice", mutedLocalized);
break;
case MuteCommands.MuteType.Chat:
mutes = "text chat";
mutes = "🔇 " + logChannel.Guild.GetLogText("xmuted_text", mutedLocalized);
break;
case MuteCommands.MuteType.All:
mutes = "text and voice chat";
mutes = "🔇 " + logChannel.Guild.GetLogText("xmuted_text_and_voice", mutedLocalized);
break;
}
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName("🔇 User Muted from " + mutes))
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName(mutes))
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter(fb => fb.WithText(currentTime))
.WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
private static async void MuteCommands_UserUnmuted(IGuildUser usr, MuteCommands.MuteType muteType)
@ -253,28 +258,32 @@ namespace NadekoBot.Modules.Administration
if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserMuted)) == null)
return;
string mutes = "";
var mutes = "";
var unmutedLocalized = logChannel.Guild.GetLogText("unmuted_sn");
switch (muteType)
{
case MuteCommands.MuteType.Voice:
mutes = "voice chat";
mutes = "🔊 " + logChannel.Guild.GetLogText("xmuted_voice", unmutedLocalized);
break;
case MuteCommands.MuteType.Chat:
mutes = "text chat";
mutes = "🔊 " + logChannel.Guild.GetLogText("xmuted_text", unmutedLocalized);
break;
case MuteCommands.MuteType.All:
mutes = "text and voice chat";
mutes = "🔊 " + logChannel.Guild.GetLogText("xmuted_text_and_voice", unmutedLocalized);
break;
}
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName("🔊 User Unmuted from " + mutes))
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName(mutes))
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter(fb => fb.WithText($"{currentTime}"))
.WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
public static async Task TriggeredAntiProtection(IGuildUser[] users, PunishmentAction action, ProtectionType protection)
@ -293,28 +302,31 @@ namespace NadekoBot.Modules.Administration
return;
var punishment = "";
if (action == PunishmentAction.Mute)
switch (action)
{
punishment = "🔇 MUTED";
}
else if (action == PunishmentAction.Kick)
{
punishment = "☣ SOFT-BANNED (KICKED)";
}
else if (action == PunishmentAction.Ban)
{
punishment = "⛔️ BANNED";
case PunishmentAction.Mute:
punishment = "🔇 " + logChannel.Guild.GetLogText("muted_pl").ToUpperInvariant();
break;
case PunishmentAction.Kick:
punishment = "☣ " + logChannel.Guild.GetLogText("soft_banned_pl").ToUpperInvariant();
break;
case PunishmentAction.Ban:
punishment = "⛔️ " + logChannel.Guild.GetLogText("banned_pl").ToUpperInvariant();
break;
}
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName($"🛡 Anti-{protection}"))
.WithTitle($"Users " + punishment)
.WithDescription(String.Join("\n", users.Select(u => u.ToString())))
.WithTitle(logChannel.Guild.GetLogText("users") + " " + punishment)
.WithDescription(string.Join("\n", users.Select(u => u.ToString())))
.WithFooter(fb => fb.WithText($"{currentTime}"))
.WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
private static async Task _client_GuildUserUpdated(SocketGuildUser before, SocketGuildUser after)
@ -333,23 +345,23 @@ namespace NadekoBot.Modules.Administration
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}");
if (before.Nickname != after.Nickname)
{
embed.WithAuthor(eab => eab.WithName("👥 Nickname Changed"))
embed.WithAuthor(eab => eab.WithName("👥 " + logChannel.Guild.GetLogText("nick_change")))
.AddField(efb => efb.WithName("Old Nickname").WithValue($"{before.Nickname}#{before.Discriminator}"))
.AddField(efb => efb.WithName("New Nickname").WithValue($"{after.Nickname}#{after.Discriminator}"));
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("old_nick")).WithValue($"{before.Nickname}#{before.Discriminator}"))
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("new_nick")).WithValue($"{after.Nickname}#{after.Discriminator}"));
}
else if (!before.RoleIds.SequenceEqual(after.RoleIds))
{
if (before.RoleIds.Count < after.RoleIds.Count)
{
var diffRoles = after.RoleIds.Where(r => !before.RoleIds.Contains(r)).Select(r => before.Guild.GetRole(r).Name);
embed.WithAuthor(eab => eab.WithName("⚔ User's Role Added"))
embed.WithAuthor(eab => eab.WithName("⚔ " + logChannel.Guild.GetLogText("user_role_add")))
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
}
else if (before.RoleIds.Count > after.RoleIds.Count)
{
var diffRoles = before.RoleIds.Where(r => !after.RoleIds.Contains(r)).Select(r => before.Guild.GetRole(r).Name);
embed.WithAuthor(eab => eab.WithName("⚔ User's Role Removed"))
embed.WithAuthor(eab => eab.WithName("⚔ " + logChannel.Guild.GetLogText("user_role_rem")))
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
}
}
@ -357,7 +369,10 @@ namespace NadekoBot.Modules.Administration
return;
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
private static async Task _client_ChannelUpdated(IChannel cbefore, IChannel cafter)
@ -386,23 +401,26 @@ namespace NadekoBot.Modules.Administration
if (before.Name != after.Name)
{
embed.WithTitle(" Channel Name Changed")
embed.WithTitle(" " + logChannel.Guild.GetLogText("ch_name_change"))
.WithDescription($"{after} | {after.Id}")
.AddField(efb => efb.WithName("Old Name").WithValue(before.Name));
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("ch_old_name")).WithValue(before.Name));
}
else if (beforeTextChannel?.Topic != afterTextChannel?.Topic)
{
embed.WithTitle(" Channel Topic Changed")
embed.WithTitle(" " + logChannel.Guild.GetLogText("ch_topic_change"))
.WithDescription($"{after} | {after.Id}")
.AddField(efb => efb.WithName("Old Topic").WithValue(beforeTextChannel.Topic))
.AddField(efb => efb.WithName("New Topic").WithValue(afterTextChannel.Topic));
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("old_topic")).WithValue(beforeTextChannel?.Topic ?? "-"))
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("new_topic")).WithValue(afterTextChannel?.Topic ?? "-"));
}
else
return;
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
private static async Task _client_ChannelDestroyed(IChannel ich)
@ -422,14 +440,23 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelDestroyed)) == null)
return;
string title;
if (ch is IVoiceChannel)
{
title = logChannel.Guild.GetLogText("voice_chan_destroyed");
}
else
title = logChannel.Guild.GetLogText("text_chan_destroyed");
await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("🆕 " + (ch is IVoiceChannel ? "Voice" : "Text") + " Channel Destroyed")
.WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
private static async Task _client_ChannelCreated(IChannel ich)
@ -448,10 +475,16 @@ namespace NadekoBot.Modules.Administration
ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelCreated)) == null)
return;
string title;
if (ch is IVoiceChannel)
{
title = logChannel.Guild.GetLogText("voice_chan_created");
}
else
title = logChannel.Guild.GetLogText("text_chan_created");
await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("🆕 " + (ch is IVoiceChannel ? "Voice" : "Text") + " Channel Created")
.WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
}
@ -484,20 +517,29 @@ namespace NadekoBot.Modules.Administration
string str = null;
if (beforeVch?.Guild == afterVch?.Guild)
{
str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ moved from **{beforeVch.Name}** to **{afterVch.Name}** voice channel.";
str = "🎙" + Format.Code(prettyCurrentTime) + logChannel.Guild.GetLogText("user_vmoved",
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(beforeVch?.Name ?? ""), Format.Bold(afterVch?.Name ?? ""));
}
else if (beforeVch == null)
{
str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ has joined **{afterVch.Name}** voice channel.";
str = "🎙" + Format.Code(prettyCurrentTime) + logChannel.Guild.GetLogText("user_vjoined",
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(afterVch.Name ?? ""));
}
else if (afterVch == null)
{
str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ has left **{beforeVch.Name}** voice channel.";
str = "🎙" + Format.Code(prettyCurrentTime) + logChannel.Guild.GetLogText("user_vleft",
"👤" + Format.Code(prettyCurrentTime), "👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(beforeVch.Name ?? ""));
}
if (str != null)
PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
presenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
}
catch
{
// ignored
}
catch { }
}
private static async Task _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after)
@ -520,7 +562,10 @@ namespace NadekoBot.Modules.Administration
return;
string str = "";
if (before.Status != after.Status)
str = $"🎭`{prettyCurrentTime}`👤__**{usr.Username}**__ is now **{after.Status}**.";
str = "🎭" + Format.Code(prettyCurrentTime) +
logChannel.Guild.GetLogText("user_status_change",
"👤" + Format.Bold(usr.Username),
Format.Bold(after.Status.ToString()));
//if (before.Game?.Name != after.Game?.Name)
//{
@ -529,9 +574,12 @@ namespace NadekoBot.Modules.Administration
// str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**.";
//}
PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
presenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
}
catch
{
// ignored
}
catch { }
}
private static async Task _client_UserLeft(IGuildUser usr)
@ -549,13 +597,16 @@ namespace NadekoBot.Modules.Administration
await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("❌ User Left")
.WithTitle("❌ " + logChannel.Guild.GetLogText("user_left"))
.WithThumbnailUrl(usr.AvatarUrl)
.WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
.WithFooter(efb => efb.WithText(currentTime))).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
private static async Task _client_UserJoined(IGuildUser usr)
@ -573,7 +624,7 @@ namespace NadekoBot.Modules.Administration
await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("✅ User Joined")
.WithTitle("✅ " + logChannel.Guild.GetLogText("user_joined"))
.WithThumbnailUrl(usr.AvatarUrl)
.WithDescription($"{usr}")
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
@ -597,7 +648,7 @@ namespace NadekoBot.Modules.Administration
await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("♻️ User Unbanned")
.WithTitle("♻️ " + logChannel.Guild.GetLogText("user_unbanned"))
.WithThumbnailUrl(usr.AvatarUrl)
.WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
@ -620,7 +671,7 @@ namespace NadekoBot.Modules.Administration
return;
await logChannel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithTitle("🚫 User Banned")
.WithTitle("🚫 " + logChannel.Guild.GetLogText("user_banned"))
.WithThumbnailUrl(usr.AvatarUrl)
.WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
@ -653,17 +704,20 @@ namespace NadekoBot.Modules.Administration
return;
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle($"🗑 Message Deleted in #{((ITextChannel)msg.Channel).Name}")
.WithTitle("🗑 " + logChannel.Guild.GetLogText("msg_del", ((ITextChannel)msg.Channel).Name))
.WithDescription($"{msg.Author}")
.AddField(efb => efb.WithName("Content").WithValue(msg.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("content")).WithValue(msg.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("Id").WithValue(msg.Id.ToString()).WithIsInline(false))
.WithFooter(efb => efb.WithText(currentTime));
if (msg.Attachments.Any())
embed.AddField(efb => efb.WithName("Attachments").WithValue(string.Join(", ", msg.Attachments.Select(a => a.ProxyUrl))).WithIsInline(false));
embed.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("attachments")).WithValue(string.Join(", ", msg.Attachments.Select(a => a.ProxyUrl))).WithIsInline(false));
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
private static async Task _client_MessageUpdated(Optional<SocketMessage> optmsg, SocketMessage imsg2)
@ -697,16 +751,19 @@ namespace NadekoBot.Modules.Administration
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle($"📝 Message Updated in #{((ITextChannel)after.Channel).Name}")
.WithTitle("📝 " + logChannel.Guild.GetLogText("msg_update", ((ITextChannel)after.Channel).Name))
.WithDescription(after.Author.ToString())
.AddField(efb => efb.WithName("Old Message").WithValue(before.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("New Message").WithValue(after.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("old_msg")).WithValue(before.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("new_msg")).WithValue(after.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("Id").WithValue(after.Id.ToString()).WithIsInline(false))
.WithFooter(efb => efb.WithText(currentTime));
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
public enum LogType
@ -778,8 +835,6 @@ namespace NadekoBot.Modules.Administration
case LogType.UserMuted:
id = logSetting.UserMutedId;
break;
default:
break;
}
if (!id.HasValue)
@ -850,8 +905,6 @@ namespace NadekoBot.Modules.Administration
case LogType.VoicePresenceTTS:
newLogSetting.LogVoicePresenceTTSId = null;
break;
default:
break;
}
GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (gid, old) => newLogSetting);
uow.Complete();
@ -895,9 +948,9 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync().ConfigureAwait(false);
}
if (action.Value)
await channel.SendConfirmAsync("Logging all events in this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("log_all").ConfigureAwait(false);
else
await channel.SendConfirmAsync("Logging disabled.").ConfigureAwait(false);
await ReplyConfirmLocalized("log_disabled").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -924,9 +977,9 @@ namespace NadekoBot.Modules.Administration
}
if (removed == 0)
await channel.SendConfirmAsync($"Logging will IGNORE **{channel.Mention} ({channel.Id})**").ConfigureAwait(false);
await ReplyConfirmLocalized("log_ignore", Format.Bold(channel.Mention + "(" + channel.Id + ")")).ConfigureAwait(false);
else
await channel.SendConfirmAsync($"Logging will NOT IGNORE **{channel.Mention} ({channel.Id})**").ConfigureAwait(false);
await ReplyConfirmLocalized("log_not_ignore", Format.Bold(channel.Mention + "(" + channel.Id + ")")).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -935,7 +988,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task LogEvents()
{
await Context.Channel.SendConfirmAsync("Log events you can subscribe to:", String.Join(", ", Enum.GetNames(typeof(LogType)).Cast<string>()));
await ReplyConfirmLocalized("log_events", string.Join(", ", Enum.GetNames(typeof(LogType)).Cast<string>())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -1003,10 +1056,19 @@ namespace NadekoBot.Modules.Administration
}
if (channelId != null)
await channel.SendConfirmAsync($"Logging **{type}** event in this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("log", Format.Bold(type.ToString())).ConfigureAwait(false);
else
await channel.SendConfirmAsync($"Stopped logging **{type}** event.").ConfigureAwait(false);
await ReplyConfirmLocalized("log_stop", Format.Bold(type.ToString())).ConfigureAwait(false);
}
}
}
public static class GuildExtensions
{
public static string GetLogText(this IGuild guild, string key, params object[] replacements)
=> NadekoModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(guild),
typeof(Administration).Name.ToLowerInvariant(),
replacements);
}
}

View File

@ -20,11 +20,11 @@ namespace NadekoBot.Modules.Administration
public partial class Administration
{
[Group]
public class Migration : ModuleBase
public class Migration : NadekoSubmodule
{
private const int CURRENT_VERSION = 1;
private static Logger _log { get; }
private new static readonly Logger _log;
static Migration()
{
@ -51,12 +51,12 @@ namespace NadekoBot.Modules.Administration
break;
}
}
await Context.Channel.SendMessageAsync("🆙 **Migration done.**").ConfigureAwait(false);
await ReplyConfirmLocalized("migration_done").ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Error(ex);
await Context.Channel.SendMessageAsync("⚠️ **Error while migrating, check `logs` for more informations.**").ConfigureAwait(false);
await ReplyErrorLocalized("migration_error").ConfigureAwait(false);
}
}
@ -103,11 +103,8 @@ namespace NadekoBot.Modules.Administration
var greetChannel = (ulong)(long)reader["GreetChannelId"];
var greetMsg = (string)reader["GreetText"];
var bye = (long)reader["Bye"] == 1;
var byeDM = (long)reader["ByePM"] == 1;
var byeChannel = (ulong)(long)reader["ByeChannelId"];
var byeMsg = (string)reader["ByeText"];
var grdel = false;
var byedel = grdel;
var gc = uow.GuildConfigs.For(gid, set => set);
if (greetDM)
@ -121,7 +118,6 @@ namespace NadekoBot.Modules.Administration
gc.ByeMessageChannelId = byeChannel;
gc.ChannelByeMessageText = byeMsg;
gc.AutoDeleteGreetMessagesTimer = gc.AutoDeleteByeMessagesTimer = grdel ? 30 : 0;
_log.Info(++i);
}
}
@ -129,12 +125,12 @@ namespace NadekoBot.Modules.Administration
_log.Warn("Greet/bye messages won't be migrated");
}
var com2 = db.CreateCommand();
com.CommandText = "SELECT * FROM CurrencyState GROUP BY UserId";
com2.CommandText = "SELECT * FROM CurrencyState GROUP BY UserId";
i = 0;
try
{
var reader2 = com.ExecuteReader();
var reader2 = com2.ExecuteReader();
while (reader2.Read())
{
_log.Info(++i);
@ -203,7 +199,6 @@ namespace NadekoBot.Modules.Administration
guildConfig.ExclusiveSelfAssignedRoles = data.ExclusiveSelfAssignedRoles;
guildConfig.GenerateCurrencyChannelIds = new HashSet<GCChannelId>(data.GenerateCurrencyChannels.Select(gc => new GCChannelId() { ChannelId = gc.Key }));
selfAssRoles.AddRange(data.ListOfSelfAssignableRoles.Select(r => new SelfAssignedRole() { GuildId = guildConfig.GuildId, RoleId = r }).ToArray());
var logSetting = guildConfig.LogSetting;
guildConfig.LogSetting.IgnoredChannels = new HashSet<IgnoredLogChannel>(data.LogserverIgnoreChannels.Select(id => new IgnoredLogChannel() { ChannelId = id }));
guildConfig.LogSetting.LogUserPresenceId = data.LogPresenceChannel;
@ -249,7 +244,7 @@ namespace NadekoBot.Modules.Administration
private void MigratePermissions0_9(IUnitOfWork uow)
{
var PermissionsDict = new ConcurrentDictionary<ulong, ServerPermissions0_9>();
var permissionsDict = new ConcurrentDictionary<ulong, ServerPermissions0_9>();
if (!Directory.Exists("data/permissions/"))
{
_log.Warn("No data from permissions will be migrated.");
@ -263,12 +258,15 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(strippedFileName)) continue;
var id = ulong.Parse(strippedFileName);
var data = JsonConvert.DeserializeObject<ServerPermissions0_9>(File.ReadAllText(file));
PermissionsDict.TryAdd(id, data);
permissionsDict.TryAdd(id, data);
}
catch
{
// ignored
}
catch { }
}
var i = 0;
PermissionsDict
permissionsDict
.Select(p => new { data = p.Value, gconfig = uow.GuildConfigs.For(p.Key) })
.AsParallel()
.ForAll(perms =>

View File

@ -8,8 +8,6 @@ using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
@ -18,11 +16,10 @@ namespace NadekoBot.Modules.Administration
public partial class Administration
{
[Group]
public class MuteCommands : ModuleBase
public class MuteCommands : NadekoSubmodule
{
private static ConcurrentDictionary<ulong, string> GuildMuteRoles { get; } = new ConcurrentDictionary<ulong, string>();
private static ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; } = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>();
private static ConcurrentDictionary<ulong, string> guildMuteRoles { get; }
private static ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> mutedUsers { get; }
public static event Action<IGuildUser, MuteType> UserMuted = delegate { };
public static event Action<IGuildUser, MuteType> UserUnmuted = delegate { };
@ -36,14 +33,12 @@ namespace NadekoBot.Modules.Administration
static MuteCommands()
{
var _log = LogManager.GetCurrentClassLogger();
var configs = NadekoBot.AllGuildConfigs;
GuildMuteRoles = new ConcurrentDictionary<ulong, string>(configs
guildMuteRoles = new ConcurrentDictionary<ulong, string>(configs
.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
.ToDictionary(c => c.GuildId, c => c.MuteRoleName));
MutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(configs.ToDictionary(
mutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(configs.ToDictionary(
k => k.GuildId,
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
));
@ -56,16 +51,15 @@ namespace NadekoBot.Modules.Administration
try
{
ConcurrentHashSet<ulong> muted;
MutedUsers.TryGetValue(usr.Guild.Id, out muted);
mutedUsers.TryGetValue(usr.Guild.Id, out muted);
if (muted == null || !muted.Contains(usr.Id))
return;
else
await MuteUser(usr).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
LogManager.GetCurrentClassLogger().Warn(ex);
}
}
@ -82,7 +76,7 @@ namespace NadekoBot.Modules.Administration
UserId = usr.Id
});
ConcurrentHashSet<ulong> muted;
if (MutedUsers.TryGetValue(usr.Guild.Id, out muted))
if (mutedUsers.TryGetValue(usr.Guild.Id, out muted))
muted.Add(usr.Id);
await uow.CompleteAsync().ConfigureAwait(false);
@ -102,7 +96,7 @@ namespace NadekoBot.Modules.Administration
UserId = usr.Id
});
ConcurrentHashSet<ulong> muted;
if (MutedUsers.TryGetValue(usr.Guild.Id, out muted))
if (mutedUsers.TryGetValue(usr.Guild.Id, out muted))
muted.TryRemove(usr.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
@ -113,7 +107,7 @@ namespace NadekoBot.Modules.Administration
{
const string defaultMuteRoleName = "nadeko-mute";
var muteRoleName = GuildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName);
var muteRoleName = guildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName);
var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName);
if (muteRole == null)
@ -135,7 +129,10 @@ namespace NadekoBot.Modules.Administration
await toOverwrite.AddPermissionOverwriteAsync(muteRole, new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny))
.ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
await Task.Delay(200).ConfigureAwait(false);
}
}
@ -148,7 +145,6 @@ namespace NadekoBot.Modules.Administration
[Priority(1)]
public async Task SetMuteRole([Remainder] string name)
{
//var channel = (ITextChannel)Context.Channel;
name = name.Trim();
if (string.IsNullOrWhiteSpace(name))
return;
@ -157,10 +153,10 @@ namespace NadekoBot.Modules.Administration
{
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set);
config.MuteRoleName = name;
GuildMuteRoles.AddOrUpdate(Context.Guild.Id, name, (id, old) => name);
guildMuteRoles.AddOrUpdate(Context.Guild.Id, name, (id, old) => name);
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("☑️ **New mute role set.**").ConfigureAwait(false);
await ReplyConfirmLocalized("mute_role_set").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -179,11 +175,11 @@ namespace NadekoBot.Modules.Administration
try
{
await MuteUser(user).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"🔇 **{user}** has been **muted** from text and voice chat.").ConfigureAwait(false);
await ReplyConfirmLocalized("user_muted", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false);
await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
}
}
@ -196,11 +192,11 @@ namespace NadekoBot.Modules.Administration
try
{
await UnmuteUser(user).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"🔉 **{user}** has been **unmuted** from text and voice chat.").ConfigureAwait(false);
await ReplyConfirmLocalized("user_unmuted", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false);
await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
}
}
@ -213,11 +209,11 @@ namespace NadekoBot.Modules.Administration
{
await user.AddRolesAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserMuted(user, MuteType.Chat);
await Context.Channel.SendConfirmAsync($"✏️🚫 **{user}** has been **muted** from chatting.").ConfigureAwait(false);
await ReplyConfirmLocalized("user_chat_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false);
await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
}
}
@ -230,11 +226,11 @@ namespace NadekoBot.Modules.Administration
{
await user.RemoveRolesAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserUnmuted(user, MuteType.Chat);
await Context.Channel.SendConfirmAsync($"✏️✅ **{user}** has been **unmuted** from chatting.").ConfigureAwait(false);
await ReplyConfirmLocalized("user_chat_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false);
await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
}
}
@ -247,11 +243,11 @@ namespace NadekoBot.Modules.Administration
{
await user.ModifyAsync(usr => usr.Mute = true).ConfigureAwait(false);
UserMuted(user, MuteType.Voice);
await Context.Channel.SendConfirmAsync($"🎙🚫 **{user}** has been **voice muted**.").ConfigureAwait(false);
await ReplyConfirmLocalized("user_voice_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false);
await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
}
}
@ -264,11 +260,11 @@ namespace NadekoBot.Modules.Administration
{
await user.ModifyAsync(usr => usr.Mute = false).ConfigureAwait(false);
UserUnmuted(user, MuteType.Voice);
await Context.Channel.SendConfirmAsync($"🎙✅ **{user}** has been **voice unmuted**.").ConfigureAwait(false);
await ReplyConfirmLocalized("user_voice_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false);
await ReplyErrorLocalized("mute_error").ConfigureAwait(false);
}
}
}

View File

@ -1,5 +1,4 @@
using Discord;
using Discord.Commands;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
@ -8,7 +7,6 @@ using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -18,16 +16,17 @@ namespace NadekoBot.Modules.Administration
public partial class Administration
{
[Group]
public class PlayingRotateCommands : ModuleBase
public class PlayingRotateCommands : NadekoSubmodule
{
private static Logger _log { get; }
public static List<PlayingStatus> RotatingStatusMessages { get; }
public static bool RotatingStatuses { get; private set; } = false;
private static Timer _t { get; }
public static volatile bool RotatingStatuses;
private readonly object _locker = new object();
private new static Logger _log { get; }
private static readonly Timer _t;
private class TimerState
{
public int Index { get; set; } = 0;
public int Index { get; set; }
}
static PlayingRotateCommands()
@ -37,8 +36,6 @@ namespace NadekoBot.Modules.Administration
RotatingStatusMessages = NadekoBot.BotConfig.RotatingStatusMessages;
RotatingStatuses = NadekoBot.BotConfig.RotatingStatuses;
_t = new Timer(async (objState) =>
{
try
@ -46,8 +43,6 @@ namespace NadekoBot.Modules.Administration
var state = (TimerState)objState;
if (!RotatingStatuses)
return;
else
{
if (state.Index >= RotatingStatusMessages.Count)
state.Index = 0;
@ -60,7 +55,8 @@ namespace NadekoBot.Modules.Administration
var shards = NadekoBot.Client.Shards;
for (int i = 0; i < shards.Count; i++)
{
ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(shards.ElementAt(i))));
var curShard = shards.ElementAt(i);
ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(curShard)));
try { await shards.ElementAt(i).SetGameAsync(status).ConfigureAwait(false); }
catch (Exception ex)
{
@ -68,7 +64,6 @@ namespace NadekoBot.Modules.Administration
}
}
}
}
catch (Exception ex)
{
_log.Warn("Rotating playing status errored.\n" + ex);
@ -106,18 +101,21 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task RotatePlaying()
{
lock (_locker)
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
RotatingStatuses = config.RotatingStatuses = !config.RotatingStatuses;
await uow.CompleteAsync();
uow.Complete();
}
}
if (RotatingStatuses)
await Context.Channel.SendConfirmAsync("🆗 **Rotating playing status enabled.**").ConfigureAwait(false);
await ReplyConfirmLocalized("ropl_enabled").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync(" **Rotating playing status disabled.**").ConfigureAwait(false);
await ReplyConfirmLocalized("ropl_disabled").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -133,7 +131,7 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync();
}
await Context.Channel.SendConfirmAsync("✅ **Added.**").ConfigureAwait(false);
await ReplyConfirmLocalized("ropl_added").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -141,11 +139,13 @@ namespace NadekoBot.Modules.Administration
public async Task ListPlaying()
{
if (!RotatingStatusMessages.Any())
await Context.Channel.SendErrorAsync("❎ **No rotating playing statuses set.**");
await ReplyErrorLocalized("ropl_not_set").ConfigureAwait(false);
else
{
var i = 1;
await Context.Channel.SendConfirmAsync($" {Context.User.Mention} `Here is a list of rotating statuses:`\n\n\t" + string.Join("\n\t", RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}")));
await ReplyConfirmLocalized("ropl_list",
string.Join("\n\t", RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}")))
.ConfigureAwait(false);
}
}
@ -156,7 +156,7 @@ namespace NadekoBot.Modules.Administration
{
index -= 1;
string msg = "";
string msg;
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
@ -168,7 +168,7 @@ namespace NadekoBot.Modules.Administration
RotatingStatusMessages.RemoveAt(index);
await uow.CompleteAsync();
}
await Context.Channel.SendConfirmAsync($"🗑 **Removed the the playing message:** {msg}").ConfigureAwait(false);
await ReplyConfirmLocalized("reprm", msg).ConfigureAwait(false);
}
}
}

View File

@ -4,12 +4,10 @@ using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
@ -26,12 +24,8 @@ namespace NadekoBot.Modules.Administration
public class AntiRaidStats
{
public AntiRaidSetting AntiRaidSettings { get; set; }
public int UsersCount { get; set; } = 0;
public int UsersCount { get; set; }
public ConcurrentHashSet<IGuildUser> RaidUsers { get; set; } = new ConcurrentHashSet<IGuildUser>();
public override string ToString() =>
$"If **{AntiRaidSettings.UserThreshold}** or more users join within **{AntiRaidSettings.Seconds}** seconds," +
$" I will **{AntiRaidSettings.Action}** them.";
}
public class AntiSpamStats
@ -39,16 +33,6 @@ namespace NadekoBot.Modules.Administration
public AntiSpamSetting AntiSpamSettings { get; set; }
public ConcurrentDictionary<ulong, UserSpamStats> UserStats { get; set; }
= new ConcurrentDictionary<ulong, UserSpamStats>();
public override string ToString()
{
var ignoredString = string.Join(", ", AntiSpamSettings.IgnoredChannels.Select(c => $"<#{c.ChannelId}>"));
if (string.IsNullOrWhiteSpace(ignoredString))
ignoredString = "none";
return $"If a user posts **{AntiSpamSettings.MessageThreshold}** same messages in a row, I will **{AntiSpamSettings.Action}** them."
+ $"\n\t__IgnoredChannels__: {ignoredString}";
}
}
public class UserSpamStats
@ -76,15 +60,15 @@ namespace NadekoBot.Modules.Administration
}
[Group]
public class ProtectionCommands : ModuleBase
public class ProtectionCommands : NadekoSubmodule
{
private static ConcurrentDictionary<ulong, AntiRaidStats> antiRaidGuilds =
private static readonly ConcurrentDictionary<ulong, AntiRaidStats> _antiRaidGuilds =
new ConcurrentDictionary<ulong, AntiRaidStats>();
// guildId | (userId|messages)
private static ConcurrentDictionary<ulong, AntiSpamStats> antiSpamGuilds =
private static readonly ConcurrentDictionary<ulong, AntiSpamStats> _antiSpamGuilds =
new ConcurrentDictionary<ulong, AntiSpamStats>();
private static Logger _log { get; }
private new static readonly Logger _log;
static ProtectionCommands()
{
@ -98,11 +82,11 @@ namespace NadekoBot.Modules.Administration
if (raid != null)
{
var raidStats = new AntiRaidStats() { AntiRaidSettings = raid };
antiRaidGuilds.TryAdd(gc.GuildId, raidStats);
_antiRaidGuilds.TryAdd(gc.GuildId, raidStats);
}
if (spam != null)
antiSpamGuilds.TryAdd(gc.GuildId, new AntiSpamStats() { AntiSpamSettings = spam });
_antiSpamGuilds.TryAdd(gc.GuildId, new AntiSpamStats() { AntiSpamSettings = spam });
}
NadekoBot.Client.MessageReceived += (imsg) =>
@ -119,7 +103,7 @@ namespace NadekoBot.Modules.Administration
try
{
AntiSpamStats spamSettings;
if (!antiSpamGuilds.TryGetValue(channel.Guild.Id, out spamSettings) ||
if (!_antiSpamGuilds.TryGetValue(channel.Guild.Id, out spamSettings) ||
spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new AntiSpamIgnore()
{
ChannelId = channel.Id
@ -141,7 +125,10 @@ namespace NadekoBot.Modules.Administration
}
}
}
catch { }
catch
{
// ignored
}
});
return Task.CompletedTask;
};
@ -151,7 +138,7 @@ namespace NadekoBot.Modules.Administration
if (usr.IsBot)
return Task.CompletedTask;
AntiRaidStats settings;
if (!antiRaidGuilds.TryGetValue(usr.Guild.Id, out settings))
if (!_antiRaidGuilds.TryGetValue(usr.Guild.Id, out settings))
return Task.CompletedTask;
if (!settings.RaidUsers.Add(usr))
return Task.CompletedTask;
@ -175,7 +162,10 @@ namespace NadekoBot.Modules.Administration
--settings.UsersCount;
}
catch { }
catch
{
// ignored
}
});
return Task.CompletedTask;
};
@ -219,13 +209,27 @@ namespace NadekoBot.Modules.Administration
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
default:
break;
}
}
await LogCommands.TriggeredAntiProtection(gus, action, pt).ConfigureAwait(false);
}
private string GetAntiSpamString(AntiSpamStats stats)
{
var ignoredString = string.Join(", ", stats.AntiSpamSettings.IgnoredChannels.Select(c => $"<#{c.ChannelId}>"));
if (string.IsNullOrWhiteSpace(ignoredString))
ignoredString = "none";
return GetText("spam_stats",
Format.Bold(stats.AntiSpamSettings.MessageThreshold.ToString()),
Format.Bold(stats.AntiSpamSettings.Action.ToString()),
ignoredString);
}
private string GetAntiRaidString(AntiRaidStats stats) => GetText("raid_stats",
Format.Bold(stats.AntiRaidSettings.UserThreshold.ToString()),
Format.Bold(stats.AntiRaidSettings.Seconds.ToString()),
Format.Bold(stats.AntiRaidSettings.Action.ToString()));
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -234,18 +238,18 @@ namespace NadekoBot.Modules.Administration
{
if (userThreshold < 2 || userThreshold > 30)
{
await Context.Channel.SendErrorAsync("❗User threshold must be between **2** and **30**.").ConfigureAwait(false);
await ReplyErrorLocalized("raid_cnt", 2, 30).ConfigureAwait(false);
return;
}
if (seconds < 2 || seconds > 300)
{
await Context.Channel.SendErrorAsync("❗Time must be between **2** and **300** seconds.").ConfigureAwait(false);
await ReplyErrorLocalized("raid_time", 2, 300).ConfigureAwait(false);
return;
}
AntiRaidStats throwaway;
if (antiRaidGuilds.TryRemove(Context.Guild.Id, out throwaway))
if (_antiRaidGuilds.TryRemove(Context.Guild.Id, out throwaway))
{
using (var uow = DbHandler.UnitOfWork())
{
@ -254,7 +258,7 @@ namespace NadekoBot.Modules.Administration
gc.AntiRaidSetting = null;
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("**Anti-Raid** feature has been **disabled** on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("prot_disable", "Anti-Raid").ConfigureAwait(false);
return;
}
@ -264,10 +268,8 @@ namespace NadekoBot.Modules.Administration
}
catch (Exception ex)
{
await Context.Channel.SendConfirmAsync("⚠️ Failed creating a mute role. Give me ManageRoles permission" +
"or create 'nadeko-mute' role with disabled SendMessages and try again.")
.ConfigureAwait(false);
_log.Warn(ex);
await ReplyErrorLocalized("prot_error").ConfigureAwait(false);
return;
}
@ -281,7 +283,7 @@ namespace NadekoBot.Modules.Administration
}
};
antiRaidGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats);
_antiRaidGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats);
using (var uow = DbHandler.UnitOfWork())
{
@ -291,7 +293,7 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("Anti-Raid Enabled", $"{Context.User.Mention} {stats.ToString()}")
await Context.Channel.SendConfirmAsync(GetText("prot_enable", "Anti-Raid"), $"{Context.User.Mention} {GetAntiRaidString(stats)}")
.ConfigureAwait(false);
}
@ -304,7 +306,7 @@ namespace NadekoBot.Modules.Administration
return;
AntiSpamStats throwaway;
if (antiSpamGuilds.TryRemove(Context.Guild.Id, out throwaway))
if (_antiSpamGuilds.TryRemove(Context.Guild.Id, out throwaway))
{
using (var uow = DbHandler.UnitOfWork())
{
@ -314,7 +316,7 @@ namespace NadekoBot.Modules.Administration
gc.AntiSpamSetting = null;
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("**Anti-Spam** has been **disabled** on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("prot_disable", "Anti-Spam").ConfigureAwait(false);
return;
}
@ -324,10 +326,8 @@ namespace NadekoBot.Modules.Administration
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync("⚠️ Failed creating a mute role. Give me ManageRoles permission" +
"or create 'nadeko-mute' role with disabled SendMessages and try again.")
.ConfigureAwait(false);
_log.Warn(ex);
await ReplyErrorLocalized("prot_error").ConfigureAwait(false);
return;
}
@ -340,7 +340,7 @@ namespace NadekoBot.Modules.Administration
}
};
antiSpamGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats);
_antiSpamGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats);
using (var uow = DbHandler.UnitOfWork())
{
@ -350,7 +350,7 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("Anti-Spam Enabled", $"{Context.User.Mention} {stats.ToString()}").ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(GetText("prot_enable", "Anti-Spam"), $"{Context.User.Mention} {GetAntiSpamString(stats)}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -376,7 +376,7 @@ namespace NadekoBot.Modules.Administration
if (spam.IgnoredChannels.Add(obj))
{
AntiSpamStats temp;
if (antiSpamGuilds.TryGetValue(Context.Guild.Id, out temp))
if (_antiSpamGuilds.TryGetValue(Context.Guild.Id, out temp))
temp.AntiSpamSettings.IgnoredChannels.Add(obj);
added = true;
}
@ -384,7 +384,7 @@ namespace NadekoBot.Modules.Administration
{
spam.IgnoredChannels.Remove(obj);
AntiSpamStats temp;
if (antiSpamGuilds.TryGetValue(Context.Guild.Id, out temp))
if (_antiSpamGuilds.TryGetValue(Context.Guild.Id, out temp))
temp.AntiSpamSettings.IgnoredChannels.Remove(obj);
added = false;
}
@ -392,9 +392,9 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync().ConfigureAwait(false);
}
if (added)
await Context.Channel.SendConfirmAsync("Anti-Spam will ignore this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("spam_ignore", "Anti-Spam").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync("Anti-Spam will no longer ignore this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("spam_not_ignore", "Anti-Spam").ConfigureAwait(false);
}
@ -402,31 +402,29 @@ namespace NadekoBot.Modules.Administration
[RequireContext(ContextType.Guild)]
public async Task AntiList()
{
var channel = (ITextChannel)Context.Channel;
AntiSpamStats spam;
antiSpamGuilds.TryGetValue(Context.Guild.Id, out spam);
_antiSpamGuilds.TryGetValue(Context.Guild.Id, out spam);
AntiRaidStats raid;
antiRaidGuilds.TryGetValue(Context.Guild.Id, out raid);
_antiRaidGuilds.TryGetValue(Context.Guild.Id, out raid);
if (spam == null && raid == null)
{
await Context.Channel.SendConfirmAsync("No protections enabled.");
await ReplyConfirmLocalized("prot_none").ConfigureAwait(false);
return;
}
var embed = new EmbedBuilder().WithOkColor()
.WithTitle("Protections Enabled");
.WithTitle(GetText("prot_active"));
if (spam != null)
embed.AddField(efb => efb.WithName("Anti-Spam")
.WithValue(spam.ToString())
.WithValue(GetAntiSpamString(spam))
.WithIsInline(true));
if (raid != null)
embed.AddField(efb => efb.WithName("Anti-Raid")
.WithValue(raid.ToString())
.WithValue(GetAntiRaidString(raid))
.WithIsInline(true));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);

View File

@ -13,10 +13,10 @@ namespace NadekoBot.Modules.Administration
public partial class Administration
{
[Group]
public class RatelimitCommand : ModuleBase
public class RatelimitCommand : NadekoSubmodule
{
public static ConcurrentDictionary<ulong, Ratelimiter> RatelimitingChannels = new ConcurrentDictionary<ulong, Ratelimiter>();
private static Logger _log { get; }
private new static readonly Logger _log;
public class Ratelimiter
{
@ -37,15 +37,13 @@ namespace NadekoBot.Modules.Administration
public bool CheckUserRatelimit(ulong id)
{
RatelimitedUser usr = Users.GetOrAdd(id, (key) => new RatelimitedUser() { UserId = id });
var usr = Users.GetOrAdd(id, (key) => new RatelimitedUser() { UserId = id });
if (usr.MessageCount == MaxMessages)
{
return true;
}
else
{
usr.MessageCount++;
var t = Task.Run(async () =>
var _ = Task.Run(async () =>
{
try
{
@ -56,8 +54,6 @@ namespace NadekoBot.Modules.Administration
});
return false;
}
}
}
static RatelimitCommand()
@ -69,9 +65,7 @@ namespace NadekoBot.Modules.Administration
try
{
var usrMsg = umsg as IUserMessage;
if (usrMsg == null)
return;
var channel = usrMsg.Channel as ITextChannel;
var channel = usrMsg?.Channel as ITextChannel;
if (channel == null || usrMsg.IsAuthor())
return;
@ -95,8 +89,7 @@ namespace NadekoBot.Modules.Administration
if (RatelimitingChannels.TryRemove(Context.Channel.Id, out throwaway))
{
throwaway.cancelSource.Cancel();
await Context.Channel.SendConfirmAsync(" Slow mode disabled.").ConfigureAwait(false);
return;
await ReplyConfirmLocalized("slowmode_disabled").ConfigureAwait(false);
}
}
@ -109,7 +102,7 @@ namespace NadekoBot.Modules.Administration
if (msg < 1 || perSec < 1 || msg > 100 || perSec > 3600)
{
await Context.Channel.SendErrorAsync("⚠️ Invalid parameters.");
await ReplyErrorLocalized("invalid_params").ConfigureAwait(false);
return;
}
var toAdd = new Ratelimiter()
@ -120,8 +113,8 @@ namespace NadekoBot.Modules.Administration
};
if(RatelimitingChannels.TryAdd(Context.Channel.Id, toAdd))
{
await Context.Channel.SendConfirmAsync("Slow mode initiated",
$"Users can't send more than `{toAdd.MaxMessages} message(s)` every `{toAdd.PerSeconds} second(s)`.")
await Context.Channel.SendConfirmAsync(GetText("slowmode_init"),
GetText("slowmode_desc", Format.Bold(toAdd.MaxMessages.ToString()), Format.Bold(toAdd.PerSeconds.ToString())))
.ConfigureAwait(false);
}
}

View File

@ -16,7 +16,7 @@ namespace NadekoBot.Modules.Administration
public partial class Administration
{
[Group]
public class SelfAssignedRolesCommands : ModuleBase
public class SelfAssignedRolesCommands : NadekoSubmodule
{
[NadekoCommand, Usage, Description, Aliases]
@ -44,25 +44,30 @@ namespace NadekoBot.Modules.Administration
IEnumerable<SelfAssignedRole> roles;
string msg;
var error = false;
using (var uow = DbHandler.UnitOfWork())
{
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id);
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
{
await Context.Channel.SendMessageAsync($"💢 Role **{role.Name}** is already in the list.").ConfigureAwait(false);
return;
msg = GetText("role_in_list", Format.Bold(role.Name));
error = true;
}
else
{
uow.SelfAssignedRoles.Add(new SelfAssignedRole {
uow.SelfAssignedRoles.Add(new SelfAssignedRole
{
RoleId = role.Id,
GuildId = role.Guild.Id
});
await uow.CompleteAsync();
msg = $"🆗 Role **{role.Name}** added to the list.";
msg = GetText("role_added", Format.Bold(role.Name));
}
}
await Context.Channel.SendConfirmAsync(msg.ToString()).ConfigureAwait(false);
if (error)
await Context.Channel.SendErrorAsync(msg).ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync(msg).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -70,8 +75,6 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task Rsar([Remainder] IRole role)
{
//var channel = (ITextChannel)Context.Channel;
bool success;
using (var uow = DbHandler.UnitOfWork())
{
@ -80,18 +83,16 @@ namespace NadekoBot.Modules.Administration
}
if (!success)
{
await Context.Channel.SendErrorAsync("❎ That role is not self-assignable.").ConfigureAwait(false);
await ReplyErrorLocalized("self_assign_not").ConfigureAwait(false);
return;
}
await Context.Channel.SendConfirmAsync($"🗑 **{role.Name}** has been removed from the list of self-assignable roles.").ConfigureAwait(false);
await ReplyConfirmLocalized("self_assign_rem", Format.Bold(role.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Lsar()
{
//var channel = (ITextChannel)Context.Channel;
var toRemove = new ConcurrentHashSet<SelfAssignedRole>();
var removeMsg = new StringBuilder();
var msg = new StringBuilder();
@ -116,11 +117,11 @@ namespace NadekoBot.Modules.Administration
}
foreach (var role in toRemove)
{
removeMsg.AppendLine($"`{role.RoleId} not found. Cleaned up.`");
removeMsg.AppendLine(GetText("role_clean", role.RoleId));
}
await uow.CompleteAsync();
}
await Context.Channel.SendConfirmAsync($" There are `{roleCnt}` self assignable roles:", msg.ToString() + "\n\n" + removeMsg.ToString()).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(GetText("self_assign_list", roleCnt), msg + "\n\n" + removeMsg).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -128,8 +129,6 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task Tesar()
{
//var channel = (ITextChannel)Context.Channel;
bool areExclusive;
using (var uow = DbHandler.UnitOfWork())
{
@ -138,15 +137,16 @@ namespace NadekoBot.Modules.Administration
areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles;
await uow.CompleteAsync();
}
string exl = areExclusive ? "**exclusive**." : "**not exclusive**.";
await Context.Channel.SendConfirmAsync(" Self assigned roles are now " + exl);
if(areExclusive)
await ReplyConfirmLocalized("self_assign_excl").ConfigureAwait(false);
else
await ReplyConfirmLocalized("self_assign_no_excl").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Iam([Remainder] IRole role)
{
//var channel = (ITextChannel)Context.Channel;
var guildUser = (IGuildUser)Context.User;
GuildConfig conf;
@ -156,25 +156,24 @@ namespace NadekoBot.Modules.Administration
conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id);
}
SelfAssignedRole roleModel;
if ((roleModel = roles.FirstOrDefault(r=>r.RoleId == role.Id)) == null)
if (roles.FirstOrDefault(r=>r.RoleId == role.Id) == null)
{
await Context.Channel.SendErrorAsync("That role is not self-assignable.").ConfigureAwait(false);
await ReplyErrorLocalized("self_assign_not").ConfigureAwait(false);
return;
}
if (guildUser.RoleIds.Contains(role.Id))
{
await Context.Channel.SendErrorAsync($"You already have **{role.Name}** role.").ConfigureAwait(false);
await ReplyErrorLocalized("self_assign_already", Format.Bold(role.Name)).ConfigureAwait(false);
return;
}
if (conf.ExclusiveSelfAssignedRoles)
{
var sameRoleId = guildUser.RoleIds.Where(r => roles.Select(sar => sar.RoleId).Contains(r)).FirstOrDefault();
var sameRoleId = guildUser.RoleIds.FirstOrDefault(r => roles.Select(sar => sar.RoleId).Contains(r));
var sameRole = Context.Guild.GetRole(sameRoleId);
if (sameRoleId != default(ulong))
{
await Context.Channel.SendErrorAsync($"You already have **{sameRole?.Name}** `exclusive self-assigned` role.").ConfigureAwait(false);
await ReplyErrorLocalized("self_assign_already_excl", Format.Bold(sameRole?.Name)).ConfigureAwait(false);
return;
}
}
@ -184,11 +183,11 @@ namespace NadekoBot.Modules.Administration
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync($"⚠️ I am unable to add that role to you. `I can't add roles to owners or other roles higher than my role in the role hierarchy.`").ConfigureAwait(false);
await ReplyErrorLocalized("self_assign_perms").ConfigureAwait(false);
Console.WriteLine(ex);
return;
}
var msg = await Context.Channel.SendConfirmAsync($"🆗 You now have **{role.Name}** role.").ConfigureAwait(false);
var msg = await ReplyConfirmLocalized("self_assign_success",Format.Bold(role.Name)).ConfigureAwait(false);
if (conf.AutoDeleteSelfAssignedRoleMessages)
{
@ -210,15 +209,14 @@ namespace NadekoBot.Modules.Administration
autoDeleteSelfAssignedRoleMessages = uow.GuildConfigs.For(Context.Guild.Id, set => set).AutoDeleteSelfAssignedRoleMessages;
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id);
}
SelfAssignedRole roleModel;
if ((roleModel = roles.FirstOrDefault(r => r.RoleId == role.Id)) == null)
if (roles.FirstOrDefault(r => r.RoleId == role.Id) == null)
{
await Context.Channel.SendErrorAsync("💢 That role is not self-assignable.").ConfigureAwait(false);
await ReplyErrorLocalized("self_assign_not").ConfigureAwait(false);
return;
}
if (!guildUser.RoleIds.Contains(role.Id))
{
await Context.Channel.SendErrorAsync($"❎ You don't have **{role.Name}** role.").ConfigureAwait(false);
await ReplyErrorLocalized("self_assign_not_have",Format.Bold(role.Name)).ConfigureAwait(false);
return;
}
try
@ -227,10 +225,10 @@ namespace NadekoBot.Modules.Administration
}
catch (Exception)
{
await Context.Channel.SendErrorAsync($"⚠️ I am unable to add that role to you. `I can't remove roles to owners or other roles higher than my role in the role hierarchy.`").ConfigureAwait(false);
await ReplyErrorLocalized("self_assign_perms").ConfigureAwait(false);
return;
}
var msg = await Context.Channel.SendConfirmAsync($"🆗 You no longer have **{role.Name}** role.").ConfigureAwait(false);
var msg = await ReplyConfirmLocalized("self_assign_remove", Format.Bold(role.Name)).ConfigureAwait(false);
if (autoDeleteSelfAssignedRoleMessages)
{

View File

@ -3,19 +3,97 @@ using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Services;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
class SelfCommands : ModuleBase
public class SelfCommands : NadekoSubmodule
{
private static volatile bool _forwardDMs;
private static volatile bool _forwardDMsToAllOwners;
private static readonly object _locker = new object();
static SelfCommands()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
_forwardDMs = config.ForwardMessages;
_forwardDMsToAllOwners = config.ForwardToAllOwners;
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardMessages()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
lock (_locker)
_forwardDMs = config.ForwardMessages = !config.ForwardMessages;
uow.Complete();
}
if (_forwardDMs)
await ReplyConfirmLocalized("fwdm_start").ConfigureAwait(false);
else
await ReplyConfirmLocalized("fwdm_stop").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ForwardToAll()
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
lock (_locker)
_forwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners;
uow.Complete();
}
if (_forwardDMsToAllOwners)
await ReplyConfirmLocalized("fwall_start").ConfigureAwait(false);
else
await ReplyConfirmLocalized("fwall_stop").ConfigureAwait(false);
}
public static async Task HandleDmForwarding(SocketMessage msg, List<IDMChannel> ownerChannels)
{
if (_forwardDMs && ownerChannels.Any())
{
var title =
GetTextStatic("dm_from", NadekoBot.Localization.DefaultCultureInfo,
typeof(Administration).Name.ToLowerInvariant()) + $" [{msg.Author}]({msg.Author.Id})";
if (_forwardDMsToAllOwners)
{
await Task.WhenAll(ownerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id)
.Select(ch => ch.SendConfirmAsync(title, msg.Content))).ConfigureAwait(false);
}
else
{
var firstOwnerChannel = ownerChannels.First();
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
try { await firstOwnerChannel.SendConfirmAsync(title, msg.Content).ConfigureAwait(false); }
catch
{
// ignored
}
}
}
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ConnectShard(int shardid)
@ -24,14 +102,14 @@ namespace NadekoBot.Modules.Administration
if (shard == null)
{
await Context.Channel.SendErrorAsync("No shard by that id found.").ConfigureAwait(false);
await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false);
return;
}
try
{
await Context.Channel.SendConfirmAsync($"Shard **#{shardid}** reconnecting.").ConfigureAwait(false);
await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false);
await shard.ConnectAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"Shard **#{shardid}** reconnected.").ConfigureAwait(false);
await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false);
}
catch (Exception ex)
{
@ -49,18 +127,18 @@ namespace NadekoBot.Modules.Administration
if (server == null)
{
await Context.Channel.SendErrorAsync("⚠️ Cannot find that server").ConfigureAwait(false);
await ReplyErrorLocalized("no_server").ConfigureAwait(false);
return;
}
if (server.OwnerId != NadekoBot.Client.CurrentUser.Id)
{
await server.LeaveAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("✅ Left server " + server.Name).ConfigureAwait(false);
await ReplyConfirmLocalized("left_server", Format.Bold(server.Name)).ConfigureAwait(false);
}
else
{
await server.DeleteAsync().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("Deleted server " + server.Name).ConfigureAwait(false);
await ReplyConfirmLocalized("deleted_server",Format.Bold(server.Name)).ConfigureAwait(false);
}
}
@ -69,7 +147,14 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task Die()
{
try { await Context.Channel.SendConfirmAsync(" **Shutting down.**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
try
{
await ReplyConfirmLocalized("shutting_down").ConfigureAwait(false);
}
catch
{
// ignored
}
await Task.Delay(2000).ConfigureAwait(false);
Environment.Exit(0);
}
@ -83,7 +168,7 @@ namespace NadekoBot.Modules.Administration
await NadekoBot.Client.CurrentUser.ModifyAsync(u => u.Username = newName).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"Bot name changed to **{newName}**").ConfigureAwait(false);
await ReplyConfirmLocalized("bot_name", Format.Bold(newName)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -92,7 +177,7 @@ namespace NadekoBot.Modules.Administration
{
await NadekoBot.Client.SetStatusAsync(SettableUserStatusToUserStatus(status)).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"Bot status changed to **{status}**").ConfigureAwait(false);
await ReplyConfirmLocalized("bot_status", Format.Bold(status.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -114,7 +199,7 @@ namespace NadekoBot.Modules.Administration
}
}
await Context.Channel.SendConfirmAsync("🆒 **New avatar set.**").ConfigureAwait(false);
await ReplyConfirmLocalized("set_avatar").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -123,7 +208,7 @@ namespace NadekoBot.Modules.Administration
{
await NadekoBot.Client.SetGameAsync(game).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("👾 **New game set.**").ConfigureAwait(false);
await ReplyConfirmLocalized("set_game").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -134,7 +219,7 @@ namespace NadekoBot.Modules.Administration
await NadekoBot.Client.SetGameAsync(name, url, StreamType.Twitch).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(" **New stream set.**").ConfigureAwait(false);
await ReplyConfirmLocalized("set_stream").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -148,7 +233,7 @@ namespace NadekoBot.Modules.Administration
if (ids.Length != 2)
return;
var sid = ulong.Parse(ids[0]);
var server = NadekoBot.Client.GetGuilds().Where(s => s.Id == sid).FirstOrDefault();
var server = NadekoBot.Client.GetGuilds().FirstOrDefault(s => s.Id == sid);
if (server == null)
return;
@ -156,7 +241,7 @@ namespace NadekoBot.Modules.Administration
if (ids[1].ToUpperInvariant().StartsWith("C:"))
{
var cid = ulong.Parse(ids[1].Substring(2));
var ch = server.TextChannels.Where(c => c.Id == cid).FirstOrDefault();
var ch = server.TextChannels.FirstOrDefault(c => c.Id == cid);
if (ch == null)
{
return;
@ -166,7 +251,7 @@ namespace NadekoBot.Modules.Administration
else if (ids[1].ToUpperInvariant().StartsWith("U:"))
{
var uid = ulong.Parse(ids[1].Substring(2));
var user = server.Users.Where(u => u.Id == uid).FirstOrDefault();
var user = server.Users.FirstOrDefault(u => u.Id == uid);
if (user == null)
{
return;
@ -175,8 +260,10 @@ namespace NadekoBot.Modules.Administration
}
else
{
await Context.Channel.SendErrorAsync("⚠️ Invalid format.").ConfigureAwait(false);
await ReplyErrorLocalized("invalid_format").ConfigureAwait(false);
return;
}
await ReplyConfirmLocalized("message_sent").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -186,10 +273,10 @@ namespace NadekoBot.Modules.Administration
var channels = NadekoBot.Client.GetGuilds().Select(g => g.DefaultChannel).ToArray();
if (channels == null)
return;
await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync($"🆕 Message from {Context.User} `[Bot Owner]`:", message)))
await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync(GetText("message_from_bo", Context.User.ToString()), message)))
.ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("🆗").ConfigureAwait(false);
await ReplyConfirmLocalized("message_sent").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -197,7 +284,7 @@ namespace NadekoBot.Modules.Administration
public async Task ReloadImages()
{
var time = await NadekoBot.Images.Reload().ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"Images loaded after {time.TotalSeconds:F3}s!").ConfigureAwait(false);
await ReplyConfirmLocalized("images_loaded", time.TotalSeconds.ToString("F3")).ConfigureAwait(false);
}
private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)

View File

@ -1,11 +1,9 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.DataStructures;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
@ -18,10 +16,10 @@ namespace NadekoBot.Modules.Administration
public partial class Administration
{
[Group]
public class ServerGreetCommands : ModuleBase
public class ServerGreetCommands : NadekoSubmodule
{
//make this to a field in the guildconfig table
class GreetSettings
private class GreetSettings
{
public int AutoDeleteGreetMessagesTimer { get; set; }
public int AutoDeleteByeMessagesTimer { get; set; }
@ -53,9 +51,9 @@ namespace NadekoBot.Modules.Administration
};
}
private static Logger _log { get; }
private new static Logger _log { get; }
private static ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache { get; } = new ConcurrentDictionary<ulong, GreetSettings>();
private static ConcurrentDictionary<ulong, GreetSettings> guildConfigsCache { get; }
static ServerGreetCommands()
{
@ -63,13 +61,13 @@ namespace NadekoBot.Modules.Administration
NadekoBot.Client.UserLeft += UserLeft;
_log = LogManager.GetCurrentClassLogger();
GuildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(NadekoBot.AllGuildConfigs.ToDictionary(g => g.GuildId, (g) => GreetSettings.Create(g)));
guildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(NadekoBot.AllGuildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
}
private static GreetSettings GetOrAddSettingsForGuild(ulong guildId)
{
GreetSettings settings;
GuildConfigsCache.TryGetValue(guildId, out settings);
guildConfigsCache.TryGetValue(guildId, out settings);
if (settings != null)
return settings;
@ -80,7 +78,7 @@ namespace NadekoBot.Modules.Administration
settings = GreetSettings.Create(gc);
}
GuildConfigsCache.TryAdd(guildId, settings);
guildConfigsCache.TryAdd(guildId, settings);
return settings;
}
@ -129,7 +127,10 @@ namespace NadekoBot.Modules.Administration
catch (Exception ex) { _log.Warn(ex); }
}
}
catch { }
catch
{
// ignored
}
});
return Task.CompletedTask;
}
@ -212,7 +213,10 @@ namespace NadekoBot.Modules.Administration
}
}
}
catch { }
catch
{
// ignored
}
});
return Task.CompletedTask;
}
@ -222,16 +226,15 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageGuild)]
public async Task GreetDel(int timer = 30)
{
var channel = (ITextChannel)Context.Channel;
if (timer < 0 || timer > 600)
return;
await ServerGreetCommands.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false);
if (timer > 0)
await Context.Channel.SendConfirmAsync($"🆗 Greet messages **will be deleted** after `{timer} seconds`.").ConfigureAwait(false);
await ReplyConfirmLocalized("greetdel_on", timer).ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync(" Automatic deletion of greet messages has been **disabled**.").ConfigureAwait(false);
await ReplyConfirmLocalized("greetdel_off").ConfigureAwait(false);
}
private static async Task SetGreetDel(ulong id, int timer)
@ -245,7 +248,7 @@ namespace NadekoBot.Modules.Administration
conf.AutoDeleteGreetMessagesTimer = timer;
var toAdd = GreetSettings.Create(conf);
GuildConfigsCache.AddOrUpdate(id, toAdd, (key, old) => toAdd);
guildConfigsCache.AddOrUpdate(id, toAdd, (key, old) => toAdd);
await uow.CompleteAsync().ConfigureAwait(false);
}
@ -259,9 +262,9 @@ namespace NadekoBot.Modules.Administration
var enabled = await ServerGreetCommands.SetGreet(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false);
if (enabled)
await Context.Channel.SendConfirmAsync("✅ Greeting messages **enabled** on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("greet_on").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync(" Greeting messages **disabled**.").ConfigureAwait(false);
await ReplyConfirmLocalized("greet_off").ConfigureAwait(false);
}
private static async Task<bool> SetGreet(ulong guildId, ulong channelId, bool? value = null)
@ -274,7 +277,7 @@ namespace NadekoBot.Modules.Administration
conf.GreetMessageChannelId = channelId;
var toAdd = GreetSettings.Create(conf);
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
await uow.CompleteAsync().ConfigureAwait(false);
}
@ -293,15 +296,15 @@ namespace NadekoBot.Modules.Administration
{
channelGreetMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelGreetMessageText;
}
await Context.Channel.SendConfirmAsync("Current greet message: ", channelGreetMessageText?.SanitizeMentions());
await ReplyConfirmLocalized("greetmsg_cur", channelGreetMessageText?.SanitizeMentions()).ConfigureAwait(false);
return;
}
var sendGreetEnabled = ServerGreetCommands.SetGreetMessage(Context.Guild.Id, ref text);
await Context.Channel.SendConfirmAsync("🆗 New greet message **set**.").ConfigureAwait(false);
await ReplyConfirmLocalized("greetmsg_new").ConfigureAwait(false);
if (!sendGreetEnabled)
await Context.Channel.SendConfirmAsync(" Enable greet messsages by typing `.greet`").ConfigureAwait(false);
await ReplyConfirmLocalized("greetmsg_enable", $"`{Prefix}greet`").ConfigureAwait(false);
}
public static bool SetGreetMessage(ulong guildId, ref string message)
@ -319,7 +322,7 @@ namespace NadekoBot.Modules.Administration
greetMsgEnabled = conf.SendChannelGreetMessage;
var toAdd = GreetSettings.Create(conf);
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
uow.Complete();
}
@ -334,9 +337,9 @@ namespace NadekoBot.Modules.Administration
var enabled = await ServerGreetCommands.SetGreetDm(Context.Guild.Id).ConfigureAwait(false);
if (enabled)
await Context.Channel.SendConfirmAsync("🆗 DM Greet announcements **enabled**.").ConfigureAwait(false);
await ReplyConfirmLocalized("greetdm_on").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync(" Greet announcements **disabled**.").ConfigureAwait(false);
await ReplyConfirmLocalized("greetdm_off").ConfigureAwait(false);
}
private static async Task<bool> SetGreetDm(ulong guildId, bool? value = null)
@ -348,7 +351,7 @@ namespace NadekoBot.Modules.Administration
enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage;
var toAdd = GreetSettings.Create(conf);
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
await uow.CompleteAsync().ConfigureAwait(false);
}
@ -367,15 +370,15 @@ namespace NadekoBot.Modules.Administration
{
config = uow.GuildConfigs.For(Context.Guild.Id);
}
await Context.Channel.SendConfirmAsync(" Current **DM greet** message: `" + config.DmGreetMessageText?.SanitizeMentions() + "`");
await ReplyConfirmLocalized("greetdmmsg_cur", config.DmGreetMessageText?.SanitizeMentions()).ConfigureAwait(false);
return;
}
var sendGreetEnabled = ServerGreetCommands.SetGreetDmMessage(Context.Guild.Id, ref text);
await Context.Channel.SendConfirmAsync("🆗 New DM greet message **set**.").ConfigureAwait(false);
await ReplyConfirmLocalized("greetdmmsg_new").ConfigureAwait(false);
if (!sendGreetEnabled)
await Context.Channel.SendConfirmAsync($" Enable DM greet messsages by typing `{NadekoBot.ModulePrefixes[typeof(Administration).Name]}greetdm`").ConfigureAwait(false);
await ReplyConfirmLocalized("greetdmmsg_enable", $"`{Prefix}greetdm`").ConfigureAwait(false);
}
public static bool SetGreetDmMessage(ulong guildId, ref string message)
@ -393,7 +396,7 @@ namespace NadekoBot.Modules.Administration
greetMsgEnabled = conf.SendDmGreetMessage;
var toAdd = GreetSettings.Create(conf);
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
uow.Complete();
}
@ -408,9 +411,9 @@ namespace NadekoBot.Modules.Administration
var enabled = await ServerGreetCommands.SetBye(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false);
if (enabled)
await Context.Channel.SendConfirmAsync("✅ Bye announcements **enabled** on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("bye_on").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync(" Bye announcements **disabled**.").ConfigureAwait(false);
await ReplyConfirmLocalized("bye_off").ConfigureAwait(false);
}
private static async Task<bool> SetBye(ulong guildId, ulong channelId, bool? value = null)
@ -423,7 +426,7 @@ namespace NadekoBot.Modules.Administration
conf.ByeMessageChannelId = channelId;
var toAdd = GreetSettings.Create(conf);
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
await uow.CompleteAsync();
}
@ -442,15 +445,15 @@ namespace NadekoBot.Modules.Administration
{
byeMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelByeMessageText;
}
await Context.Channel.SendConfirmAsync(" Current **bye** message: `" + byeMessageText?.SanitizeMentions() + "`");
await ReplyConfirmLocalized("byemsg_cur", byeMessageText?.SanitizeMentions()).ConfigureAwait(false);
return;
}
var sendByeEnabled = ServerGreetCommands.SetByeMessage(Context.Guild.Id, ref text);
await Context.Channel.SendConfirmAsync("🆗 New bye message **set**.").ConfigureAwait(false);
await ReplyConfirmLocalized("byemsg_new").ConfigureAwait(false);
if (!sendByeEnabled)
await Context.Channel.SendConfirmAsync($" Enable bye messsages by typing `{NadekoBot.ModulePrefixes[typeof(Administration).Name]}bye`").ConfigureAwait(false);
await ReplyConfirmLocalized("byemsg_enable", $"`{Prefix}bye`").ConfigureAwait(false);
}
public static bool SetByeMessage(ulong guildId, ref string message)
@ -468,7 +471,7 @@ namespace NadekoBot.Modules.Administration
byeMsgEnabled = conf.SendChannelByeMessage;
var toAdd = GreetSettings.Create(conf);
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
uow.Complete();
}
@ -483,9 +486,9 @@ namespace NadekoBot.Modules.Administration
await ServerGreetCommands.SetByeDel(Context.Guild.Id, timer).ConfigureAwait(false);
if (timer > 0)
await Context.Channel.SendConfirmAsync($"🆗 Bye messages **will be deleted** after `{timer} seconds`.").ConfigureAwait(false);
await ReplyConfirmLocalized("byedel_on", timer).ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync(" Automatic deletion of bye messages has been **disabled**.").ConfigureAwait(false);
await ReplyConfirmLocalized("byedel_off").ConfigureAwait(false);
}
private static async Task SetByeDel(ulong guildId, int timer)
@ -499,7 +502,7 @@ namespace NadekoBot.Modules.Administration
conf.AutoDeleteByeMessagesTimer = timer;
var toAdd = GreetSettings.Create(conf);
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
guildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
await uow.CompleteAsync().ConfigureAwait(false);
}

View File

@ -19,21 +19,20 @@ namespace NadekoBot.Modules.Administration
public partial class Administration
{
[Group]
public class VoicePlusTextCommands : ModuleBase
public class VoicePlusTextCommands : NadekoSubmodule
{
private static Regex channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled);
private new static readonly Logger _log;
private static ConcurrentHashSet<ulong> voicePlusTextCache { get; }
private static readonly Regex _channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled);
private static ConcurrentDictionary<ulong, SemaphoreSlim> guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
private static readonly ConcurrentHashSet<ulong> _voicePlusTextCache;
private static readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
static VoicePlusTextCommands()
{
var _log = LogManager.GetCurrentClassLogger();
_log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
using (var uow = DbHandler.UnitOfWork())
{
voicePlusTextCache = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.VoicePlusTextEnabled).Select(g => g.GuildId));
}
_voicePlusTextCache = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.VoicePlusTextEnabled).Select(g => g.GuildId));
NadekoBot.Client.UserVoiceStateUpdated += UserUpdatedEventHandler;
sw.Stop();
@ -53,7 +52,7 @@ namespace NadekoBot.Modules.Administration
if (before.VoiceChannel == after.VoiceChannel)
return Task.CompletedTask;
if (!voicePlusTextCache.Contains(guild.Id))
if (!_voicePlusTextCache.Contains(guild.Id))
return Task.CompletedTask;
var _ = Task.Run(async () =>
@ -66,20 +65,25 @@ namespace NadekoBot.Modules.Administration
try
{
await guild.Owner.SendErrorAsync(
"⚠️ I don't have **manage server** and/or **manage channels** permission," +
$" so I cannot run `voice+text` on **{guild.Name}** server.").ConfigureAwait(false);
GetTextStatic("vt_exit",
NadekoBot.Localization.GetCultureInfo(guild),
typeof(Administration).Name.ToLowerInvariant(),
Format.Bold(guild.Name))).ConfigureAwait(false);
}
catch
{
// ignored
}
catch { }
using (var uow = DbHandler.UnitOfWork())
{
uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false;
voicePlusTextCache.TryRemove(guild.Id);
_voicePlusTextCache.TryRemove(guild.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
return;
}
var semaphore = guildLockObjects.GetOrAdd(guild.Id, (key) => new SemaphoreSlim(1, 1));
var semaphore = _guildLockObjects.GetOrAdd(guild.Id, (key) => new SemaphoreSlim(1, 1));
try
{
@ -106,18 +110,16 @@ namespace NadekoBot.Modules.Administration
if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id)
{
var roleName = GetRoleName(afterVch);
IRole roleToAdd = guild.Roles.FirstOrDefault(x => x.Name == roleName);
if (roleToAdd == null)
roleToAdd = await guild.CreateRoleAsync(roleName, GuildPermissions.None).ConfigureAwait(false);
var roleToAdd = guild.Roles.FirstOrDefault(x => x.Name == roleName) ??
(IRole) await guild.CreateRoleAsync(roleName, GuildPermissions.None).ConfigureAwait(false);
ITextChannel textChannel = guild.TextChannels
.Where(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant())
.FirstOrDefault();
.FirstOrDefault(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant());
if (textChannel == null)
{
var created = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false));
try { await guild.CurrentUser.AddRolesAsync(roleToAdd).ConfigureAwait(false); } catch { }
try { await guild.CurrentUser.AddRolesAsync(roleToAdd).ConfigureAwait(false); } catch {/*ignored*/}
await Task.Delay(50).ConfigureAwait(false);
await created.AddPermissionOverwriteAsync(roleToAdd, new OverwritePermissions(
readMessages: PermValue.Allow,
@ -148,7 +150,7 @@ namespace NadekoBot.Modules.Administration
}
private static string GetChannelName(string voiceName) =>
channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice";
_channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice";
private static string GetRoleName(IVoiceChannel ch) =>
"nvoice-" + ch.Id;
@ -164,7 +166,7 @@ namespace NadekoBot.Modules.Administration
var botUser = await guild.GetCurrentUserAsync().ConfigureAwait(false);
if (!botUser.GuildPermissions.ManageRoles || !botUser.GuildPermissions.ManageChannels)
{
await Context.Channel.SendErrorAsync("I require atleast **manage roles** and **manage channels permissions** to enable this feature. `(preffered Administration permission)`");
await ReplyErrorLocalized("vt_no_perms").ConfigureAwait(false);
return;
}
@ -172,10 +174,12 @@ namespace NadekoBot.Modules.Administration
{
try
{
await Context.Channel.SendErrorAsync("⚠️ You are enabling/disabling this feature and **I do not have ADMINISTRATOR permissions**. " +
"`This may cause some issues, and you will have to clean up text channels yourself afterwards.`");
await ReplyErrorLocalized("vt_no_admin").ConfigureAwait(false);
}
catch
{
// ignored
}
catch { }
}
try
{
@ -188,7 +192,7 @@ namespace NadekoBot.Modules.Administration
}
if (!isEnabled)
{
voicePlusTextCache.TryRemove(guild.Id);
_voicePlusTextCache.TryRemove(guild.Id);
foreach (var textChannel in (await guild.GetTextChannelsAsync().ConfigureAwait(false)).Where(c => c.Name.EndsWith("-voice")))
{
try { await textChannel.DeleteAsync().ConfigureAwait(false); } catch { }
@ -200,11 +204,11 @@ namespace NadekoBot.Modules.Administration
try { await role.DeleteAsync().ConfigureAwait(false); } catch { }
await Task.Delay(500).ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync(" Successfuly **removed** voice + text feature.").ConfigureAwait(false);
await ReplyConfirmLocalized("vt_disabled").ConfigureAwait(false);
return;
}
voicePlusTextCache.Add(guild.Id);
await Context.Channel.SendConfirmAsync("🆗 Successfuly **enabled** voice + text feature.").ConfigureAwait(false);
_voicePlusTextCache.Add(guild.Id);
await ReplyConfirmLocalized("vt_enabled").ConfigureAwait(false);
}
catch (Exception ex)
@ -224,7 +228,7 @@ namespace NadekoBot.Modules.Administration
var botUser = await guild.GetCurrentUserAsync().ConfigureAwait(false);
if (!botUser.GuildPermissions.Administrator)
{
await Context.Channel.SendErrorAsync("I need **Administrator permission** to do that.").ConfigureAwait(false);
await ReplyErrorLocalized("need_admin").ConfigureAwait(false);
return;
}
@ -251,7 +255,7 @@ namespace NadekoBot.Modules.Administration
await Task.Delay(500).ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("Cleaned v+t.").ConfigureAwait(false);
await ReplyConfirmLocalized("cleaned_up").ConfigureAwait(false);
}
}
}

View File

@ -17,18 +17,14 @@ using NLog;
namespace NadekoBot.Modules.ClashOfClans
{
[NadekoModule("ClashOfClans", ",")]
public class ClashOfClans : DiscordModule
public class ClashOfClans : NadekoModule
{
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } = new ConcurrentDictionary<ulong, List<ClashWar>>();
private static Timer checkWarTimer { get; }
private static new readonly Logger _log;
static ClashOfClans()
{
_log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
using (var uow = DbHandler.UnitOfWork())
{
ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(
@ -73,7 +69,11 @@ namespace NadekoBot.Modules.ClashOfClans
try
{
SaveWar(war);
await war.Channel.SendErrorAsync($"❗🔰**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false);
await war.Channel.SendErrorAsync(GetTextStatic("claim_expired",
NadekoBot.Localization.GetCultureInfo(war.Channel.GuildId),
typeof(ClashOfClans).Name.ToLowerInvariant(),
Format.Bold(Bases[i].CallUser),
war.ShortPrint()));
}
catch { }
}
@ -92,7 +92,7 @@ namespace NadekoBot.Modules.ClashOfClans
if (size < 10 || size > 50 || size % 5 != 0)
{
await Context.Channel.SendErrorAsync("🔰 Not a Valid war size").ConfigureAwait(false);
await ReplyErrorLocalized("invalid_size").ConfigureAwait(false);
return;
}
List<ClashWar> wars;
@ -107,7 +107,7 @@ namespace NadekoBot.Modules.ClashOfClans
var cw = await CreateWar(enemyClan, size, Context.Guild.Id, Context.Channel.Id);
wars.Add(cw);
await Context.Channel.SendConfirmAsync($"❗🔰**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false);
await ReplyErrorLocalized("war_created", cw.ShortPrint()).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -120,18 +120,18 @@ namespace NadekoBot.Modules.ClashOfClans
var warsInfo = GetWarInfo(Context.Guild, num);
if (warsInfo == null)
{
await Context.Channel.SendErrorAsync("🔰 **That war does not exist.**").ConfigureAwait(false);
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return;
}
var war = warsInfo.Item1[warsInfo.Item2];
try
{
war.Start();
await Context.Channel.SendConfirmAsync($"🔰**STARTED WAR AGAINST {war.ShortPrint()}**").ConfigureAwait(false);
await ReplyConfirmLocalized("war_started", war.ShortPrint()).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync($"🔰**WAR AGAINST {war.ShortPrint()} HAS ALREADY STARTED**").ConfigureAwait(false);
await ReplyErrorLocalized("war_already_started", war.ShortPrint()).ConfigureAwait(false);
}
SaveWar(war);
}
@ -149,22 +149,20 @@ namespace NadekoBot.Modules.ClashOfClans
ClashWars.TryGetValue(Context.Guild.Id, out wars);
if (wars == null || wars.Count == 0)
{
await Context.Channel.SendErrorAsync("🔰 **No active wars.**").ConfigureAwait(false);
await ReplyErrorLocalized("no_active_wars").ConfigureAwait(false);
return;
}
var sb = new StringBuilder();
sb.AppendLine("🔰 **LIST OF ACTIVE WARS**");
sb.AppendLine("**-------------------------**");
for (var i = 0; i < wars.Count; i++)
{
sb.AppendLine($"**#{i + 1}.** `Enemy:` **{wars[i].EnemyClan}**");
sb.AppendLine($"\t\t`Size:` **{wars[i].Size} v {wars[i].Size}**");
sb.AppendLine($"**#{i + 1}.** `{GetText("enemy")}:` **{wars[i].EnemyClan}**");
sb.AppendLine($"\t\t`{GetText("size")}:` **{wars[i].Size} v {wars[i].Size}**");
sb.AppendLine("**-------------------------**");
}
await Context.Channel.SendConfirmAsync(sb.ToString()).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(GetText("list_active_wars"), sb.ToString()).ConfigureAwait(false);
return;
}
var num = 0;
int.TryParse(number, out num);
@ -172,10 +170,11 @@ namespace NadekoBot.Modules.ClashOfClans
var warsInfo = GetWarInfo(Context.Guild, num);
if (warsInfo == null)
{
await Context.Channel.SendErrorAsync("🔰 **That war does not exist.**").ConfigureAwait(false);
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return;
}
await Context.Channel.SendConfirmAsync(warsInfo.Item1[warsInfo.Item2].ToPrettyString()).ConfigureAwait(false);
var war = warsInfo.Item1[warsInfo.Item2];
await Context.Channel.SendConfirmAsync(war.Localize("info_about_war", $"`{war.EnemyClan}` ({war.Size} v {war.Size})"), war.ToPrettyString()).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -185,7 +184,7 @@ namespace NadekoBot.Modules.ClashOfClans
var warsInfo = GetWarInfo(Context.Guild, number);
if (warsInfo == null || warsInfo.Item1.Count == 0)
{
await Context.Channel.SendErrorAsync("🔰 **That war does not exist.**").ConfigureAwait(false);
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return;
}
var usr =
@ -197,11 +196,11 @@ namespace NadekoBot.Modules.ClashOfClans
var war = warsInfo.Item1[warsInfo.Item2];
war.Call(usr, baseNumber - 1);
SaveWar(war);
await Context.Channel.SendConfirmAsync($"🔰**{usr}** claimed a base #{baseNumber} for a war against {war.ShortPrint()}").ConfigureAwait(false);
await ConfirmLocalized("claimed_base", Format.Bold(usr.ToString()), baseNumber, war.ShortPrint()).ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync($"🔰 {ex.Message}").ConfigureAwait(false);
await Context.Channel.SendErrorAsync(ex.Message).ConfigureAwait(false);
}
}
@ -233,15 +232,14 @@ namespace NadekoBot.Modules.ClashOfClans
var warsInfo = GetWarInfo(Context.Guild, number);
if (warsInfo == null)
{
await Context.Channel.SendErrorAsync("🔰 That war does not exist.").ConfigureAwait(false);
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return;
}
var war = warsInfo.Item1[warsInfo.Item2];
war.End();
SaveWar(war);
await Context.Channel.SendConfirmAsync($"❗🔰**War against {warsInfo.Item1[warsInfo.Item2].ShortPrint()} ended.**").ConfigureAwait(false);
await ReplyConfirmLocalized("war_ended", warsInfo.Item1[warsInfo.Item2].ShortPrint()).ConfigureAwait(false);
var size = warsInfo.Item1[warsInfo.Item2].Size;
warsInfo.Item1.RemoveAt(warsInfo.Item2);
}
@ -252,7 +250,7 @@ namespace NadekoBot.Modules.ClashOfClans
var warsInfo = GetWarInfo(Context.Guild, number);
if (warsInfo == null || warsInfo.Item1.Count == 0)
{
await Context.Channel.SendErrorAsync("🔰 **That war does not exist.**").ConfigureAwait(false);
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return;
}
var usr =
@ -264,11 +262,11 @@ namespace NadekoBot.Modules.ClashOfClans
var war = warsInfo.Item1[warsInfo.Item2];
var baseNumber = war.Uncall(usr);
SaveWar(war);
await Context.Channel.SendConfirmAsync($"🔰 @{usr} has **UNCLAIMED** a base #{baseNumber + 1} from a war against {war.ShortPrint()}").ConfigureAwait(false);
await ReplyConfirmLocalized("base_unclaimed", usr, baseNumber + 1, war.ShortPrint()).ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync($"🔰 {ex.Message}").ConfigureAwait(false);
await Context.Channel.SendErrorAsync(ex.Message).ConfigureAwait(false);
}
}
@ -277,7 +275,7 @@ namespace NadekoBot.Modules.ClashOfClans
var warInfo = GetWarInfo(Context.Guild, number);
if (warInfo == null || warInfo.Item1.Count == 0)
{
await Context.Channel.SendErrorAsync("🔰 **That war does not exist.**").ConfigureAwait(false);
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return;
}
var war = warInfo.Item1[warInfo.Item2];
@ -292,7 +290,7 @@ namespace NadekoBot.Modules.ClashOfClans
{
war.FinishClaim(baseNumber, stars);
}
await Context.Channel.SendConfirmAsync($"❗🔰{Context.User.Mention} **DESTROYED** a base #{baseNumber + 1} in a war against {war.ShortPrint()}").ConfigureAwait(false);
await ReplyConfirmLocalized("base_destroyed", baseNumber +1, war.ShortPrint()).ConfigureAwait(false);
}
catch (Exception ex)
{

View File

@ -27,13 +27,13 @@ namespace NadekoBot.Modules.ClashOfClans
public static void Call(this ClashWar cw, string u, int baseNumber)
{
if (baseNumber < 0 || baseNumber >= cw.Bases.Count)
throw new ArgumentException("Invalid base number");
throw new ArgumentException(cw.Localize("invalid_base_number"));
if (cw.Bases[baseNumber].CallUser != null && cw.Bases[baseNumber].Stars == 3)
throw new ArgumentException("That base is already destroyed.");
throw new ArgumentException(cw.Localize("base_already_claimed"));
for (var i = 0; i < cw.Bases.Count; i++)
{
if (cw.Bases[i]?.BaseDestroyed == false && cw.Bases[i]?.CallUser == u)
throw new ArgumentException($"@{u} You already claimed base #{i + 1}. You can't claim a new one.");
throw new ArgumentException(cw.Localize("claimed_other", u, i + 1));
}
var cc = cw.Bases[baseNumber];
@ -45,7 +45,7 @@ namespace NadekoBot.Modules.ClashOfClans
public static void Start(this ClashWar cw)
{
if (cw.WarState == StateOfWar.Started)
throw new InvalidOperationException("War already started");
throw new InvalidOperationException("war_already_started");
//if (Started)
// throw new InvalidOperationException();
//Started = true;
@ -66,7 +66,7 @@ namespace NadekoBot.Modules.ClashOfClans
cw.Bases[i].CallUser = null;
return i;
}
throw new InvalidOperationException("You are not participating in that war.");
throw new InvalidOperationException(cw.Localize("not_partic"));
}
public static string ShortPrint(this ClashWar cw) =>
@ -76,7 +76,6 @@ namespace NadekoBot.Modules.ClashOfClans
{
var sb = new StringBuilder();
sb.AppendLine($"🔰**WAR AGAINST `{cw.EnemyClan}` ({cw.Size} v {cw.Size}) INFO:**");
if (cw.WarState == StateOfWar.Created)
sb.AppendLine("`not started`");
var twoHours = new TimeSpan(2, 0, 0);
@ -84,7 +83,7 @@ namespace NadekoBot.Modules.ClashOfClans
{
if (cw.Bases[i].CallUser == null)
{
sb.AppendLine($"`{i + 1}.` ❌*unclaimed*");
sb.AppendLine($"`{i + 1}.` ❌*{cw.Localize("not_claimed")}*");
}
else
{
@ -120,7 +119,7 @@ namespace NadekoBot.Modules.ClashOfClans
cw.Bases[i].Stars = stars;
return i;
}
throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base.");
throw new InvalidOperationException(cw.Localize("not_partic_or_destroyed", user));
}
public static void FinishClaim(this ClashWar cw, int index, int stars = 3)
@ -128,10 +127,22 @@ namespace NadekoBot.Modules.ClashOfClans
if (index < 0 || index > cw.Bases.Count)
throw new ArgumentOutOfRangeException(nameof(index));
var toFinish = cw.Bases[index];
if (toFinish.BaseDestroyed != false) throw new InvalidOperationException("That base is already destroyed.");
if (toFinish.CallUser == null) throw new InvalidOperationException("That base is unclaimed.");
if (toFinish.BaseDestroyed != false) throw new InvalidOperationException(cw.Localize("base_already_destroyed"));
if (toFinish.CallUser == null) throw new InvalidOperationException(cw.Localize("base_already_unclaimed"));
toFinish.BaseDestroyed = true;
toFinish.Stars = stars;
}
public static string Localize(this ClashWar cw, string key)
{
return NadekoModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(cw.Channel?.GuildId),
typeof(ClashOfClans).Name.ToLowerInvariant());
}
public static string Localize(this ClashWar cw, string key, params object[] replacements)
{
return string.Format(cw.Localize(key), replacements);
}
}
}

View File

@ -17,7 +17,7 @@ using NadekoBot.DataStructures;
namespace NadekoBot.Modules.CustomReactions
{
[NadekoModule("CustomReactions", ".")]
public class CustomReactions : DiscordModule
public class CustomReactions : NadekoModule
{
private static CustomReaction[] _globalReactions = new CustomReaction[] { };
public static CustomReaction[] GlobalReactions => _globalReactions;
@ -139,7 +139,7 @@ namespace NadekoBot.Modules.CustomReactions
if ((channel == null && !NadekoBot.Credentials.IsOwner(Context.User)) || (channel != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
{
try { await Context.Channel.SendErrorAsync("Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for guild custom reactions."); } catch { }
await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
return;
}
@ -165,8 +165,8 @@ namespace NadekoBot.Modules.CustomReactions
}
else
{
var reactions = GuildReactions.AddOrUpdate(Context.Guild.Id,
Array.Empty<CustomReaction>(),
GuildReactions.AddOrUpdate(Context.Guild.Id,
new CustomReaction[] { cr },
(k, old) =>
{
Array.Resize(ref old, old.Length + 1);
@ -176,10 +176,10 @@ namespace NadekoBot.Modules.CustomReactions
}
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("New Custom Reaction")
.WithTitle(GetText("new_cust_react"))
.WithDescription($"#{cr.Id}")
.AddField(efb => efb.WithName("Trigger").WithValue(key))
.AddField(efb => efb.WithName("Response").WithValue(message))
.AddField(efb => efb.WithName(GetText("trigger")).WithValue(key))
.AddField(efb => efb.WithName(GetText("response")).WithValue(message))
).ConfigureAwait(false);
}
@ -196,20 +196,21 @@ namespace NadekoBot.Modules.CustomReactions
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, Array.Empty<CustomReaction>()).Where(cr => cr != null).ToArray();
if (customReactions == null || !customReactions.Any())
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false);
else
{
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
return;
}
var lastPage = customReactions.Length / 20;
await Context.Channel.SendPaginatedConfirmAsync(page, curPage =>
new EmbedBuilder().WithOkColor()
.WithTitle("Custom reactions")
.WithTitle(GetText("name"))
.WithDescription(string.Join("\n", customReactions.OrderBy(cr => cr.Trigger)
.Skip((curPage - 1) * 20)
.Take(20)
.Select(cr => $"`#{cr.Id}` `Trigger:` {cr.Trigger}"))), lastPage)
.Select(cr => $"`#{cr.Id}` `{GetText("trigger")}:` {cr.Trigger}"))), lastPage)
.ConfigureAwait(false);
}
}
public enum All
{
@ -227,20 +228,22 @@ namespace NadekoBot.Modules.CustomReactions
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray();
if (customReactions == null || !customReactions.Any())
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false);
else
{
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
return;
}
var txtStream = await customReactions.GroupBy(cr => cr.Trigger)
.OrderBy(cr => cr.Key)
.Select(cr => new { Trigger = cr.Key, Responses = cr.Select(y => new { id = y.Id, text = y.Response }).ToList() })
.ToJson()
.ToStream()
.ConfigureAwait(false);
if (Context.Guild == null) // its a private one, just send back
await Context.Channel.SendFileAsync(txtStream, "customreactions.txt", "List of all custom reactions").ConfigureAwait(false);
await Context.Channel.SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false);
else
await ((IGuildUser)Context.User).SendFileAsync(txtStream, "customreactions.txt", "List of all custom reactions").ConfigureAwait(false);
}
await ((IGuildUser)Context.User).SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -255,7 +258,9 @@ namespace NadekoBot.Modules.CustomReactions
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray();
if (customReactions == null || !customReactions.Any())
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false);
{
await ReplyErrorLocalized("no_found").ConfigureAwait(false);
}
else
{
var ordered = customReactions
@ -266,7 +271,7 @@ namespace NadekoBot.Modules.CustomReactions
var lastPage = ordered.Count / 20;
await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) =>
new EmbedBuilder().WithOkColor()
.WithTitle($"Custom Reactions (grouped)")
.WithTitle(GetText("name"))
.WithDescription(string.Join("\r\n", ordered
.Skip((curPage - 1) * 20)
.Take(20)
@ -287,13 +292,16 @@ namespace NadekoBot.Modules.CustomReactions
var found = customReactions.FirstOrDefault(cr => cr?.Id == id);
if (found == null)
await Context.Channel.SendErrorAsync("No custom reaction found with that id.").ConfigureAwait(false);
{
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
return;
}
else
{
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription($"#{id}")
.AddField(efb => efb.WithName("Trigger").WithValue(found.Trigger))
.AddField(efb => efb.WithName("Response").WithValue(found.Response + "\n```css\n" + found.Response + "```"))
.AddField(efb => efb.WithName(GetText("trigger")).WithValue(found.Trigger))
.AddField(efb => efb.WithName(GetText("response")).WithValue(found.Response + "\n```css\n" + found.Response + "```"))
).ConfigureAwait(false);
}
}
@ -303,7 +311,7 @@ namespace NadekoBot.Modules.CustomReactions
{
if ((Context.Guild == null && !NadekoBot.Credentials.IsOwner(Context.User)) || (Context.Guild != null && !((IGuildUser)Context.User).GuildPermissions.Administrator))
{
try { await Context.Channel.SendErrorAsync("Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for guild custom reactions."); } catch { }
await ReplyErrorLocalized("insuff_perms").ConfigureAwait(false);
return;
}
@ -313,8 +321,9 @@ namespace NadekoBot.Modules.CustomReactions
{
toDelete = uow.CustomReactions.Get(id);
if (toDelete == null) //not found
return;
success = false;
else
{
if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null)
{
uow.CustomReactions.Remove(toDelete);
@ -334,11 +343,20 @@ namespace NadekoBot.Modules.CustomReactions
if (success)
await uow.CompleteAsync().ConfigureAwait(false);
}
}
if (success)
await Context.Channel.SendConfirmAsync("Deleted custom reaction", toDelete.ToString()).ConfigureAwait(false);
{
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("deleted"))
.WithDescription("#" + toDelete.Id)
.AddField(efb => efb.WithName(GetText("trigger")).WithValue(toDelete.Trigger))
.AddField(efb => efb.WithName(GetText("response")).WithValue(toDelete.Response)));
}
else
await Context.Channel.SendErrorAsync("Failed to find that custom reaction.").ConfigureAwait(false);
{
await ReplyErrorLocalized("no_found_id").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -348,18 +366,18 @@ namespace NadekoBot.Modules.CustomReactions
if (string.IsNullOrWhiteSpace(trigger))
{
ClearStats();
await Context.Channel.SendConfirmAsync($"Custom reaction stats cleared.").ConfigureAwait(false);
await ReplyConfirmLocalized("all_stats_cleared").ConfigureAwait(false);
}
else
{
uint throwaway;
if (ReactionStats.TryRemove(trigger, out throwaway))
{
await Context.Channel.SendConfirmAsync($"Stats cleared for `{trigger}` custom reaction.").ConfigureAwait(false);
await ReplyErrorLocalized("stats_cleared", Format.Bold(trigger)).ConfigureAwait(false);
}
else
{
await Context.Channel.SendErrorAsync("No stats for that trigger found, no action taken.").ConfigureAwait(false);
await ReplyErrorLocalized("stats_not_found").ConfigureAwait(false);
}
}
}
@ -376,7 +394,7 @@ namespace NadekoBot.Modules.CustomReactions
await Context.Channel.SendPaginatedConfirmAsync(page,
(curPage) => ordered.Skip((curPage - 1) * 9)
.Take(9)
.Aggregate(new EmbedBuilder().WithOkColor().WithTitle($"Custom Reaction Stats"),
.Aggregate(new EmbedBuilder().WithOkColor().WithTitle(GetText("stats")),
(agg, cur) => agg.AddField(efb => efb.WithName(cur.Key).WithValue(cur.Value.ToString()).WithIsInline(true))), lastPage)
.ConfigureAwait(false);
}

View File

@ -1,22 +0,0 @@
using Discord.Commands;
using NLog;
namespace NadekoBot.Modules
{
public abstract class DiscordModule : ModuleBase
{
protected Logger _log { get; }
protected string _prefix { get; }
public DiscordModule()
{
string prefix;
if (NadekoBot.ModulePrefixes.TryGetValue(this.GetType().Name, out prefix))
_prefix = prefix;
else
_prefix = "?missing_prefix?";
_log = LogManager.GetCurrentClassLogger();
}
}
}

View File

@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Gambling
public partial class Gambling
{
[Group]
public class AnimalRacing : ModuleBase
public class AnimalRacing : NadekoSubmodule
{
public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
@ -25,7 +25,7 @@ namespace NadekoBot.Modules.Gambling
[RequireContext(ContextType.Guild)]
public async Task Race()
{
var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel);
var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel, Prefix);
if (ar.Fail)
await Context.Channel.SendErrorAsync("🏁 `Failed starting a race. Another race is probably running.`").ConfigureAwait(false);
@ -59,13 +59,16 @@ namespace NadekoBot.Modules.Gambling
public List<Participant> participants = new List<Participant>();
private ulong serverId;
private int messagesSinceGameStarted = 0;
private readonly string _prefix;
private Logger _log { get; }
public ITextChannel raceChannel { get; set; }
public bool Started { get; private set; } = false;
public AnimalRace(ulong serverId, ITextChannel ch)
public AnimalRace(ulong serverId, ITextChannel ch, string prefix)
{
this._prefix = prefix;
this._log = LogManager.GetCurrentClassLogger();
this.serverId = serverId;
this.raceChannel = ch;
@ -75,10 +78,7 @@ namespace NadekoBot.Modules.Gambling
return;
}
using (var uow = DbHandler.UnitOfWork())
{
animals = new ConcurrentQueue<string>(NadekoBot.BotConfig.RaceAnimals.Select(ra => ra.Icon).Shuffle());
}
var cancelSource = new CancellationTokenSource();
@ -91,7 +91,7 @@ namespace NadekoBot.Modules.Gambling
try
{
await raceChannel.SendConfirmAsync("Animal Race", $"Starting in 20 seconds or when the room is full.",
footer: $"Type {NadekoBot.ModulePrefixes[typeof(Gambling).Name]}jr to join the race.");
footer: $"Type {_prefix}jr to join the race.");
}
catch (Exception ex)
{
@ -280,9 +280,7 @@ namespace NadekoBot.Modules.Gambling
public override bool Equals(object obj)
{
var p = obj as Participant;
return p == null ?
false :
p.User == User;
return p != null && p.User == User;
}
public override string ToString()

View File

@ -5,12 +5,9 @@ using NadekoBot.Extensions;
using NadekoBot.Services;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord.WebSocket;
using NadekoBot.Services.Database;
using System.Threading;
using NLog;
@ -19,7 +16,7 @@ namespace NadekoBot.Modules.Gambling
public partial class Gambling
{
[Group]
public class CurrencyEvents : ModuleBase
public class CurrencyEvents : NadekoSubmodule
{
public enum CurrencyEvent
{
@ -27,7 +24,7 @@ namespace NadekoBot.Modules.Gambling
SneakyGameStatus
}
//flower reaction event
public static readonly ConcurrentHashSet<ulong> _sneakyGameAwardedUsers = new ConcurrentHashSet<ulong>();
private static readonly ConcurrentHashSet<ulong> _sneakyGameAwardedUsers = new ConcurrentHashSet<ulong>();
private static readonly char[] _sneakyGameStatusChars = Enumerable.Range(48, 10)
@ -36,15 +33,12 @@ namespace NadekoBot.Modules.Gambling
.Select(x => (char)x)
.ToArray();
private static string _secretCode = String.Empty;
private static string _secretCode = string.Empty;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartEvent(CurrencyEvent e, int arg = -1)
{
var channel = (ITextChannel)Context.Channel;
try
{
switch (e)
{
@ -54,14 +48,10 @@ namespace NadekoBot.Modules.Gambling
case CurrencyEvent.SneakyGameStatus:
await SneakyGameStatusEvent(Context, arg).ConfigureAwait(false);
break;
default:
break;
}
}
catch { }
}
public static async Task SneakyGameStatusEvent(CommandContext Context, int? arg)
public async Task SneakyGameStatusEvent(CommandContext context, int? arg)
{
int num;
if (arg == null || arg < 5)
@ -69,26 +59,27 @@ namespace NadekoBot.Modules.Gambling
else
num = arg.Value;
if (_secretCode != String.Empty)
if (_secretCode != string.Empty)
return;
var rng = new NadekoRandom();
for (int i = 0; i < 5; i++)
for (var i = 0; i < 5; i++)
{
_secretCode += _sneakyGameStatusChars[rng.Next(0, _sneakyGameStatusChars.Length)];
}
var game = NadekoBot.Client.Game?.Name;
await NadekoBot.Client.SetGameAsync($"type {_secretCode} for " + NadekoBot.BotConfig.CurrencyPluralName)
.ConfigureAwait(false);
try
{
await Context.Channel.SendConfirmAsync($"SneakyGameStatus event started",
$"Users must type a secret code to get 100 currency.\n" +
$"Lasts {num} seconds. Don't tell anyone. Shhh.")
.ConfigureAwait(false);
var title = GetText("sneakygamestatus_title");
var desc = GetText("sneakygamestatus_desc", Format.Bold(100.ToString()) + CurrencySign, Format.Bold(num.ToString()));
await context.Channel.SendConfirmAsync(title, desc).ConfigureAwait(false);
}
catch
{
// ignored
}
catch { }
NadekoBot.Client.MessageReceived += SneakyGameMessageReceivedEventHandler;
@ -97,9 +88,9 @@ namespace NadekoBot.Modules.Gambling
var cnt = _sneakyGameAwardedUsers.Count;
_sneakyGameAwardedUsers.Clear();
_secretCode = String.Empty;
_secretCode = string.Empty;
await NadekoBot.Client.SetGameAsync($"SneakyGame event ended. {cnt} users received a reward.")
await NadekoBot.Client.SetGameAsync(GetText("sneakygamestatus_end", cnt))
.ConfigureAwait(false);
}
@ -114,29 +105,41 @@ namespace NadekoBot.Modules.Gambling
.ConfigureAwait(false);
try { await arg.DeleteAsync(new RequestOptions() { RetryMode = RetryMode.AlwaysFail }).ConfigureAwait(false); }
catch { }
catch
{
// ignored
}
});
}
return Task.Delay(0);
}
public static Task FlowerReactionEvent(CommandContext Context) =>
new FlowerReactionEvent().Start(Context);
public async Task FlowerReactionEvent(CommandContext context)
{
var title = GetText("flowerreaction_title");
var desc = GetText("flowerreaction_desc", "🌸", Format.Bold(100.ToString()) + CurrencySign);
var footer = GetText("flowerreaction_footer", 24);
var msg = await context.Channel.SendConfirmAsync(title,
desc, footer: footer)
.ConfigureAwait(false);
await new FlowerReactionEvent().Start(msg, context);
}
}
}
public abstract class CurrencyEvent
{
public abstract Task Start(CommandContext channel);
public abstract Task Start(IUserMessage msg, CommandContext channel);
}
public class FlowerReactionEvent : CurrencyEvent
{
public readonly ConcurrentHashSet<ulong> _flowerReactionAwardedUsers = new ConcurrentHashSet<ulong>();
private readonly ConcurrentHashSet<ulong> _flowerReactionAwardedUsers = new ConcurrentHashSet<ulong>();
private readonly Logger _log;
private IUserMessage msg { get; set; } = null;
private IUserMessage msg { get; set; }
private CancellationTokenSource source { get; }
private CancellationToken cancelToken { get; }
@ -163,19 +166,15 @@ namespace NadekoBot.Modules.Gambling
if (msg?.Id == id)
{
_log.Warn("Stopping flower reaction event because message is deleted.");
Task.Run(() => End());
var __ = Task.Run(End);
}
return Task.CompletedTask;
}
public override async Task Start(CommandContext context)
public override async Task Start(IUserMessage umsg, CommandContext context)
{
msg = await context.Channel.SendConfirmAsync("Flower reaction event started!",
"Add 🌸 reaction to this message to get 100" + NadekoBot.BotConfig.CurrencySign,
footer: "This event is active for up to 24 hours.")
.ConfigureAwait(false);
msg = umsg;
NadekoBot.Client.MessageDeleted += MessageDeletedEventHandler;
try { await msg.AddReactionAsync("🌸").ConfigureAwait(false); }
@ -194,10 +193,14 @@ namespace NadekoBot.Modules.Gambling
{
if (r.Emoji.Name == "🌸" && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _flowerReactionAwardedUsers.Add(r.User.Value.Id))
{
try { await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, false).ConfigureAwait(false); } catch { }
await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, false)
.ConfigureAwait(false);
}
}
catch { }
catch
{
// ignored
}
}))
{
try

View File

@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Gambling
public partial class Gambling
{
[Group]
public class DriceRollCommands : ModuleBase
public class DriceRollCommands : NadekoSubmodule
{
private Regex dndRegex { get; } = new Regex(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
private Regex fudgeRegex { get; } = new Regex(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);

View File

@ -4,8 +4,6 @@ using ImageSharp;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@ -17,17 +15,17 @@ namespace NadekoBot.Modules.Gambling
public partial class Gambling
{
[Group]
public class DrawCommands : ModuleBase
public class DrawCommands : NadekoSubmodule
{
private static readonly ConcurrentDictionary<IGuild, Cards> AllDecks = new ConcurrentDictionary<IGuild, Cards>();
private static readonly ConcurrentDictionary<IGuild, Cards> _allDecks = new ConcurrentDictionary<IGuild, Cards>();
private const string cardsPath = "data/images/cards";
private const string _cardsPath = "data/images/cards";
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Draw(int num = 1)
{
var cards = AllDecks.GetOrAdd(Context.Guild, (s) => new Cards());
var cards = _allDecks.GetOrAdd(Context.Guild, (s) => new Cards());
var images = new List<Image>();
var cardObjects = new List<Cards.Card>();
if (num > 5) num = 5;
@ -35,12 +33,19 @@ namespace NadekoBot.Modules.Gambling
{
if (cards.CardPool.Count == 0 && i != 0)
{
try { await Context.Channel.SendErrorAsync("No more cards in a deck.").ConfigureAwait(false); } catch { }
try
{
await ReplyErrorLocalized("no_more_cards").ConfigureAwait(false);
}
catch
{
// ignored
}
break;
}
var currentCard = cards.DrawACard();
cardObjects.Add(currentCard);
using (var stream = File.OpenRead(Path.Combine(cardsPath, currentCard.ToString().ToLowerInvariant()+ ".jpg").Replace(' ','_')))
using (var stream = File.OpenRead(Path.Combine(_cardsPath, currentCard.ToString().ToLowerInvariant()+ ".jpg").Replace(' ','_')))
images.Add(new Image(stream));
}
MemoryStream bitmapStream = new MemoryStream();
@ -59,7 +64,7 @@ namespace NadekoBot.Modules.Gambling
{
//var channel = (ITextChannel)Context.Channel;
AllDecks.AddOrUpdate(Context.Guild,
_allDecks.AddOrUpdate(Context.Guild,
(g) => new Cards(),
(g, c) =>
{
@ -67,7 +72,7 @@ namespace NadekoBot.Modules.Gambling
return c;
});
await Context.Channel.SendConfirmAsync("Deck reshuffled.").ConfigureAwait(false);
await ReplyConfirmLocalized("deck_reshuffled").ConfigureAwait(false);
}
}
}

View File

@ -1,12 +1,10 @@
using Discord;
using Discord.Commands;
using ImageSharp;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Image = ImageSharp.Image;
@ -15,7 +13,7 @@ namespace NadekoBot.Modules.Gambling
public partial class Gambling
{
[Group]
public class FlipCoinCommands : ModuleBase
public class FlipCoinCommands : NadekoSubmodule
{
private readonly IImagesService _images;
@ -24,7 +22,7 @@ namespace NadekoBot.Modules.Gambling
public FlipCoinCommands()
{
//todo DI in the future, can't atm
this._images = NadekoBot.Images;
_images = NadekoBot.Images;
}
[NadekoCommand, Usage, Description, Aliases]
@ -36,21 +34,21 @@ namespace NadekoBot.Modules.Gambling
{
using (var heads = _images.Heads.ToStream())
{
await Context.Channel.SendFileAsync(heads, "heads.jpg", $"{Context.User.Mention} flipped " + Format.Code("Heads") + ".").ConfigureAwait(false);
await Context.Channel.SendFileAsync(heads, "heads.jpg", Context.User.Mention + " " + GetText("flipped", Format.Bold(GetText("heads"))) + ".").ConfigureAwait(false);
}
}
else
{
using (var tails = _images.Tails.ToStream())
{
await Context.Channel.SendFileAsync(tails, "tails.jpg", $"{Context.User.Mention} flipped " + Format.Code("Tails") + ".").ConfigureAwait(false);
await Context.Channel.SendFileAsync(tails, "tails.jpg", Context.User.Mention + " " + GetText("flipped", Format.Bold(GetText("tails"))) + ".").ConfigureAwait(false);
}
}
return;
}
if (count > 10 || count < 1)
{
await Context.Channel.SendErrorAsync("`Invalid number specified. You can flip 1 to 10 coins.`").ConfigureAwait(false);
await ReplyErrorLocalized("flip_invalid", 10).ConfigureAwait(false);
return;
}
var imgs = new Image[count];
@ -76,14 +74,13 @@ namespace NadekoBot.Modules.Gambling
if (amount < NadekoBot.BotConfig.MinimumBetAmount)
{
await Context.Channel.SendErrorAsync($"You can't bet less than {NadekoBot.BotConfig.MinimumBetAmount}{CurrencySign}.")
.ConfigureAwait(false);
await ReplyErrorLocalized("min_bet_limit", NadekoBot.BotConfig.MinimumBetAmount + CurrencySign).ConfigureAwait(false);
return;
}
var removed = await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Betflip Gamble", amount, false).ConfigureAwait(false);
if (!removed)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You don't have enough {CurrencyPluralName}.").ConfigureAwait(false);
await ReplyErrorLocalized("not_enough", CurrencyPluralName).ConfigureAwait(false);
return;
}
//heads = true
@ -91,7 +88,7 @@ namespace NadekoBot.Modules.Gambling
//todo this seems stinky, no time to look at it right now
var isHeads = guessStr == "HEADS" || guessStr == "H";
bool result = false;
var result = false;
IEnumerable<byte> imageToSend;
if (rng.Next(0, 2) == 1)
{
@ -107,12 +104,12 @@ namespace NadekoBot.Modules.Gambling
if (isHeads == result)
{
var toWin = (int)Math.Round(amount * NadekoBot.BotConfig.BetflipMultiplier);
str = $"{Context.User.Mention}`You guessed it!` You won {toWin}{CurrencySign}";
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betflip Gamble", toWin, false).ConfigureAwait(false);
str = Context.User.Mention + " " + GetText("flip_guess", toWin + CurrencySign);
await CurrencyHandler.AddCurrencyAsync(Context.User, GetText("betflip_gamble"), toWin, false).ConfigureAwait(false);
}
else
{
str = $"{Context.User.Mention}`Better luck next time.`";
str = Context.User.Mention + " " + GetText("better_luck");
}
using (var toSend = imageToSend.ToStream())
{

View File

@ -5,7 +5,6 @@ using NadekoBot.Extensions;
using NadekoBot.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
@ -16,12 +15,12 @@ namespace NadekoBot.Modules.Gambling
public partial class Gambling
{
[Group]
public class Slots : ModuleBase
public class Slots : NadekoSubmodule
{
private static int totalBet = 0;
private static int totalPaidOut = 0;
private static int _totalBet;
private static int _totalPaidOut;
const int alphaCutOut = byte.MaxValue / 3;
private const int _alphaCutOut = byte.MaxValue / 3;
//here is a payout chart
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
@ -31,14 +30,14 @@ namespace NadekoBot.Modules.Gambling
public Slots()
{
this._images = NadekoBot.Images;
_images = NadekoBot.Images;
}
public class SlotMachine
{
public const int MaxValue = 5;
static readonly List<Func<int[], int>> winningCombos = new List<Func<int[], int>>()
static readonly List<Func<int[], int>> _winningCombos = new List<Func<int[], int>>()
{
//three flowers
(arr) => arr.All(a=>a==MaxValue) ? 30 : 0,
@ -53,14 +52,14 @@ namespace NadekoBot.Modules.Gambling
public static SlotResult Pull()
{
var numbers = new int[3];
for (int i = 0; i < numbers.Length; i++)
for (var i = 0; i < numbers.Length; i++)
{
numbers[i] = new NadekoRandom().Next(0, MaxValue + 1);
}
int multi = 0;
for (int i = 0; i < winningCombos.Count; i++)
var multi = 0;
foreach (var t in _winningCombos)
{
multi = winningCombos[i](numbers);
multi = t(numbers);
if (multi != 0)
break;
}
@ -74,8 +73,8 @@ namespace NadekoBot.Modules.Gambling
public int Multiplier { get; }
public SlotResult(int[] nums, int multi)
{
this.Numbers = nums;
this.Multiplier = multi;
Numbers = nums;
Multiplier = multi;
}
}
}
@ -85,8 +84,8 @@ namespace NadekoBot.Modules.Gambling
public async Task SlotStats()
{
//i remembered to not be a moron
var paid = totalPaidOut;
var bet = totalBet;
var paid = _totalPaidOut;
var bet = _totalBet;
if (bet <= 0)
bet = 1;
@ -130,33 +129,34 @@ namespace NadekoBot.Modules.Gambling
footer: $"Total Bet: {tests * bet} | Payout: {payout * bet} | {payout * 1.0f / tests * 100}%");
}
static HashSet<ulong> runningUsers = new HashSet<ulong>();
private static readonly HashSet<ulong> _runningUsers = new HashSet<ulong>();
[NadekoCommand, Usage, Description, Aliases]
public async Task Slot(int amount = 0)
{
if (!runningUsers.Add(Context.User.Id))
if (!_runningUsers.Add(Context.User.Id))
return;
try
{
if (amount < 1)
{
await Context.Channel.SendErrorAsync($"You can't bet less than 1{NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false);
await ReplyErrorLocalized("min_bet_limit", 1 + CurrencySign).ConfigureAwait(false);
return;
}
if (amount > 999)
{
await Context.Channel.SendErrorAsync($"You can't bet more than 999{NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false);
GetText("slot_maxbet", 999 + CurrencySign);
await ReplyErrorLocalized("max_bet_limit", 999 + CurrencySign).ConfigureAwait(false);
return;
}
if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Slot Machine", amount, false))
{
await Context.Channel.SendErrorAsync($"You don't have enough {NadekoBot.BotConfig.CurrencySign}.").ConfigureAwait(false);
await ReplyErrorLocalized("not_enough", CurrencySign).ConfigureAwait(false);
return;
}
Interlocked.Add(ref totalBet, amount);
Interlocked.Add(ref _totalBet, amount);
using (var bgFileStream = NadekoBot.Images.SlotBackground.ToStream())
{
var bgImage = new ImageSharp.Image(bgFileStream);
@ -179,7 +179,7 @@ namespace NadekoBot.Modules.Gambling
var x = 95 + 142 * i + j;
int y = 330 + k;
var toSet = toAdd[j, k];
if (toSet.A < alphaCutOut)
if (toSet.A < _alphaCutOut)
continue;
bgPixels[x, y] = toAdd[j, k];
}
@ -203,7 +203,7 @@ namespace NadekoBot.Modules.Gambling
{
for (int j = 0; j < pixels.Height; j++)
{
if (pixels[i, j].A < alphaCutOut)
if (pixels[i, j].A < _alphaCutOut)
continue;
var x = 230 - n * 16 + i;
bgPixels[x, 462 + j] = pixels[i, j];
@ -228,7 +228,7 @@ namespace NadekoBot.Modules.Gambling
{
for (int j = 0; j < pixels.Height; j++)
{
if (pixels[i, j].A < alphaCutOut)
if (pixels[i, j].A < _alphaCutOut)
continue;
var x = 395 - n * 16 + i;
bgPixels[x, 462 + j] = pixels[i, j];
@ -240,30 +240,30 @@ namespace NadekoBot.Modules.Gambling
} while ((printAmount /= 10) != 0);
}
var msg = "Better luck next time ^_^";
var msg = GetText("better_luck");
if (result.Multiplier != 0)
{
await CurrencyHandler.AddCurrencyAsync(Context.User, $"Slot Machine x{result.Multiplier}", amount * result.Multiplier, false);
Interlocked.Add(ref totalPaidOut, amount * result.Multiplier);
Interlocked.Add(ref _totalPaidOut, amount * result.Multiplier);
if (result.Multiplier == 1)
msg = $"A single {NadekoBot.BotConfig.CurrencySign}, x1 - Try again!";
msg = GetText("slot_single", CurrencySign, 1);
else if (result.Multiplier == 4)
msg = $"Good job! Two {NadekoBot.BotConfig.CurrencySign} - bet x4";
msg = GetText("slot_two", CurrencySign, 4);
else if (result.Multiplier == 10)
msg = "Wow! Lucky! Three of a kind! x10";
msg = GetText("slot_three", 10);
else if (result.Multiplier == 30)
msg = "WOAAHHHHHH!!! Congratulations!!! x30";
msg = GetText("slot_jackpot", 30);
}
await Context.Channel.SendFileAsync(bgImage.ToStream(), "result.png", Context.User.Mention + " " + msg + $"\n`Bet:`{amount} `Won:` {amount * result.Multiplier}{NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false);
await Context.Channel.SendFileAsync(bgImage.ToStream(), "result.png", Context.User.Mention + " " + msg + $"\n`{GetText("slot_bet")}:`{amount} `{GetText("slot_won")}:` {amount * result.Multiplier}{NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false);
}
}
finally
{
var t = Task.Run(async () =>
var _ = Task.Run(async () =>
{
await Task.Delay(2000);
runningUsers.Remove(Context.User.Id);
_runningUsers.Remove(Context.User.Id);
});
}
}

View File

@ -47,7 +47,7 @@ namespace NadekoBot.Modules.Gambling
}
[Group]
public class WaifuClaimCommands : ModuleBase
public class WaifuClaimCommands : NadekoSubmodule
{
private static ConcurrentDictionary<ulong, DateTime> _divorceCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
private static ConcurrentDictionary<ulong, DateTime> _affinityCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
@ -197,8 +197,6 @@ namespace NadekoBot.Modules.Gambling
[RequireContext(ContextType.Guild)]
public async Task Divorce([Remainder]IUser target)
{
var channel = (ITextChannel)Context.Channel;
if (target.Id == Context.User.Id)
return;
@ -423,7 +421,7 @@ namespace NadekoBot.Modules.Gambling
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle("Waifu " + w.Waifu.ToString() + " - \"the " + claimInfo.Title + "\"")
.WithTitle("Waifu " + w.Waifu + " - \"the " + claimInfo.Title + "\"")
.AddField(efb => efb.WithName("Price").WithValue(w.Price.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("Claimed by").WithValue(w.Claimer?.ToString() ?? "No one").WithIsInline(true))
.AddField(efb => efb.WithName("Likes").WithValue(w.Affinity?.ToString() ?? "Nobody").WithIsInline(true))

View File

@ -1,9 +1,9 @@
using Discord;
using System;
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
@ -12,7 +12,7 @@ using System.Collections.Generic;
namespace NadekoBot.Modules.Gambling
{
[NadekoModule("Gambling", "$")]
public partial class Gambling : DiscordModule
public partial class Gambling : NadekoModule
{
public static string CurrencyName { get; set; }
public static string CurrencyPluralName { get; set; }
@ -42,7 +42,7 @@ namespace NadekoBot.Modules.Gambling
var members = role.Members().Where(u => u.Status != UserStatus.Offline && u.Status != UserStatus.Unknown);
var membersArray = members as IUser[] ?? members.ToArray();
var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)];
await Context.Channel.SendConfirmAsync("🎟 Raffled user", $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}").ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("🎟 "+ GetText("raffled_user"), $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -50,15 +50,14 @@ namespace NadekoBot.Modules.Gambling
public async Task Cash([Remainder] IUser user = null)
{
user = user ?? Context.User;
await Context.Channel.SendConfirmAsync($"{user.Username} has {GetCurrency(user.Id)} {CurrencySign}").ConfigureAwait(false);
await ReplyConfirmLocalized("has", Format.Bold(user.ToString()), $"{GetCurrency(user.Id)} {CurrencySign}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public async Task Cash(ulong userId)
{
await Context.Channel.SendConfirmAsync($"`{userId}` has {GetCurrency(userId)} {CurrencySign}").ConfigureAwait(false);
await ReplyConfirmLocalized("has", Format.Code(userId.ToString()), $"{GetCurrency(userId)} {CurrencySign}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -70,11 +69,12 @@ namespace NadekoBot.Modules.Gambling
var success = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount, false).ConfigureAwait(false);
if (!success)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You don't have enough {CurrencyPluralName}.").ConfigureAwait(false);
await ReplyErrorLocalized("not_enough", CurrencyPluralName).ConfigureAwait(false);
return;
}
await CurrencyHandler.AddCurrencyAsync(receiver, $"Gift from {Context.User.Username} ({Context.User.Id}).", amount, true).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} gifted {amount}{CurrencySign} to {Format.Bold(receiver.ToString())}!").ConfigureAwait(false);
await ReplyConfirmLocalized("gifted", amount + CurrencySign, Format.Bold(receiver.ToString()))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -93,8 +93,7 @@ namespace NadekoBot.Modules.Gambling
return;
await CurrencyHandler.AddCurrencyAsync(usrId, $"Awarded by bot owner. ({Context.User.Username}/{Context.User.Id})", amount).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} awarded {amount}{CurrencySign} to <@{usrId}>!").ConfigureAwait(false);
await ReplyConfirmLocalized("awarded", amount + CurrencySign, $"<@{usrId}>").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -103,7 +102,6 @@ namespace NadekoBot.Modules.Gambling
[Priority(0)]
public async Task Award(int amount, [Remainder] IRole role)
{
var channel = (ITextChannel)Context.Channel;
var users = (await Context.Guild.GetUsersAsync())
.Where(u => u.GetRoles().Contains(role))
.ToList();
@ -112,9 +110,10 @@ namespace NadekoBot.Modules.Gambling
amount)))
.ConfigureAwait(false);
await Context.Channel.SendConfirmAsync($"Awarded `{amount}` {CurrencyPluralName} to `{users.Count}` users from `{role.Name}` role.")
.ConfigureAwait(false);
await ReplyConfirmLocalized("mass_award",
amount + CurrencySign,
Format.Bold(users.Count.ToString()),
Format.Bold(role.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -126,9 +125,9 @@ namespace NadekoBot.Modules.Gambling
return;
if (await CurrencyHandler.RemoveCurrencyAsync(user, $"Taken by bot owner.({Context.User.Username}/{Context.User.Id})", amount, true).ConfigureAwait(false))
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} successfully took {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} from {user}!").ConfigureAwait(false);
await ReplyConfirmLocalized("take", amount+CurrencySign, Format.Bold(user.ToString())).ConfigureAwait(false);
else
await Context.Channel.SendErrorAsync($"{Context.User.Mention} was unable to take {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} from {user} because the user doesn't have that much {CurrencyPluralName}!").ConfigureAwait(false);
await ReplyErrorLocalized("take_fail", amount + CurrencySign, Format.Bold(user.ToString()), CurrencyPluralName).ConfigureAwait(false);
}
@ -140,9 +139,9 @@ namespace NadekoBot.Modules.Gambling
return;
if (await CurrencyHandler.RemoveCurrencyAsync(usrId, $"Taken by bot owner.({Context.User.Username}/{Context.User.Id})", amount).ConfigureAwait(false))
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} successfully took {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} from <@{usrId}>!").ConfigureAwait(false);
await ReplyConfirmLocalized("take", amount + CurrencySign, $"<@{usrId}>").ConfigureAwait(false);
else
await Context.Channel.SendErrorAsync($"{Context.User.Mention} was unable to take {amount} {(amount == 1 ? CurrencyName : CurrencyPluralName)} from `{usrId}` because the user doesn't have that much {CurrencyPluralName}!").ConfigureAwait(false);
await ReplyErrorLocalized("take_fail", amount + CurrencySign, Format.Code(usrId.ToString()), CurrencyPluralName).ConfigureAwait(false);
}
//[NadekoCommand, Usage, Description, Aliases]
@ -206,49 +205,48 @@ namespace NadekoBot.Modules.Gambling
if (amount < 1)
return;
long userFlowers;
using (var uow = DbHandler.UnitOfWork())
if (!await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Betroll Gamble", amount, false).ConfigureAwait(false))
{
userFlowers = uow.Currency.GetOrCreate(Context.User.Id).Amount;
}
if (userFlowers < amount)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You don't have enough {CurrencyPluralName}. You only have {userFlowers}{CurrencySign}.").ConfigureAwait(false);
await ReplyErrorLocalized("not_enough", CurrencyPluralName).ConfigureAwait(false);
return;
}
await CurrencyHandler.RemoveCurrencyAsync(Context.User, "Betroll Gamble", amount, false).ConfigureAwait(false);
var rng = new NadekoRandom().Next(0, 101);
var str = $"{Context.User.Mention} `You rolled {rng}.` ";
if (rng < 67)
var rnd = new NadekoRandom().Next(0, 101);
var str = Context.User.Mention + Format.Code(GetText("roll", rnd));
if (rnd < 67)
{
str += "Better luck next time.";
}
else if (rng < 91)
{
str += $"Congratulations! You won {amount * NadekoBot.BotConfig.Betroll67Multiplier}{CurrencySign} for rolling above 66";
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble", (int)(amount * NadekoBot.BotConfig.Betroll67Multiplier), false).ConfigureAwait(false);
}
else if (rng < 100)
{
str += $"Congratulations! You won {amount * NadekoBot.BotConfig.Betroll91Multiplier}{CurrencySign} for rolling above 90.";
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble", (int)(amount * NadekoBot.BotConfig.Betroll91Multiplier), false).ConfigureAwait(false);
str += GetText("better_luck");
}
else
{
str += $"👑 Congratulations! You won {amount * NadekoBot.BotConfig.Betroll100Multiplier}{CurrencySign} for rolling **100**. 👑";
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble", (int)(amount * NadekoBot.BotConfig.Betroll100Multiplier), false).ConfigureAwait(false);
if (rnd < 91)
{
str += GetText("br_win", (amount * NadekoBot.BotConfig.Betroll67Multiplier) + CurrencySign, 66);
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble",
(int) (amount * NadekoBot.BotConfig.Betroll67Multiplier), false).ConfigureAwait(false);
}
else if (rnd < 100)
{
str += GetText("br_win", (amount * NadekoBot.BotConfig.Betroll91Multiplier) + CurrencySign, 90);
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble",
(int) (amount * NadekoBot.BotConfig.Betroll91Multiplier), false).ConfigureAwait(false);
}
else
{
str += GetText("br_win", (amount * NadekoBot.BotConfig.Betroll100Multiplier) + CurrencySign, 100) + " 👑";
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betroll Gamble",
(int) (amount * NadekoBot.BotConfig.Betroll100Multiplier), false).ConfigureAwait(false);
}
}
Console.WriteLine("started sending");
await Context.Channel.SendConfirmAsync(str).ConfigureAwait(false);
Console.WriteLine("done sending");
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Leaderboard()
{
var richest = new List<Currency>();
List<Currency> richest;
using (var uow = DbHandler.UnitOfWork())
{
richest = uow.Currency.GetTopRichest(9).ToList();
@ -256,22 +254,22 @@ namespace NadekoBot.Modules.Gambling
if (!richest.Any())
return;
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle(NadekoBot.BotConfig.CurrencySign + " Leaderboard");
.WithTitle(NadekoBot.BotConfig.CurrencySign + " " + GetText("leaderboard"));
for (var i = 0; i < richest.Count; i++)
{
var x = richest[i];
var usr = await Context.Guild.GetUserAsync(x.UserId).ConfigureAwait(false);
var usrStr = "";
if (usr == null)
usrStr = x.UserId.ToString();
else
usrStr = usr.Username?.TrimTo(20, true);
var usrStr = usr == null
? x.UserId.ToString()
: usr.Username?.TrimTo(20, true);
embed.AddField(efb => efb.WithName("#" + (i + 1) + " " + usrStr).WithValue(x.Amount.ToString() + " " + NadekoBot.BotConfig.CurrencySign).WithIsInline(true));
var j = i;
embed.AddField(efb => efb.WithName("#" + (j + 1) + " " + usrStr)
.WithValue(x.Amount.ToString() + " " + NadekoBot.BotConfig.CurrencySign)
.WithIsInline(true));
}
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);

View File

@ -21,12 +21,6 @@ namespace NadekoBot.Modules.Games
{
private static Logger _log { get; }
class CleverAnswer
{
public string Status { get; set; }
public string Response { get; set; }
}
public static ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> CleverbotGuilds { get; } = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>();
static CleverBotCommands()
@ -34,14 +28,12 @@ namespace NadekoBot.Modules.Games
_log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
using (var uow = DbHandler.UnitOfWork())
{
var bot = ChatterBotFactory.Create(ChatterBotType.CLEVERBOT);
CleverbotGuilds = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>(
NadekoBot.AllGuildConfigs
.Where(gc => gc.CleverbotEnabled)
.ToDictionary(gc => gc.GuildId, gc => new Lazy<ChatterBotSession>(() => bot.CreateSession(), true)));
}
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");

View File

@ -23,7 +23,8 @@ namespace NadekoBot.Modules.Games
static HangmanCommands()
{
_log = LogManager.GetCurrentClassLogger();
typesStr = $"`List of \"{NadekoBot.ModulePrefixes[typeof(Games).Name]}hangman\" term types:`\n" + String.Join(", ", HangmanTermPool.data.Keys);
typesStr =
string.Format("`List of \"{0}hangman\" term types:`\n", NadekoBot.ModulePrefixes[typeof(Games).Name]) + String.Join(", ", HangmanTermPool.data.Keys);
}
[NadekoCommand, Usage, Description, Aliases]

View File

@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Games
/// https://discord.gg/0TYNJfCU4De7YIk8
/// </summary>
[Group]
public class PlantPickCommands : ModuleBase
public class PlantPickCommands : NadekoSubmodule
{
private static ConcurrentHashSet<ulong> generationChannels { get; } = new ConcurrentHashSet<ulong>();
//channelid/message
@ -37,13 +37,8 @@ namespace NadekoBot.Modules.Games
//channelId/last generation
private static ConcurrentDictionary<ulong, DateTime> lastGenerations { get; } = new ConcurrentDictionary<ulong, DateTime>();
private static ConcurrentHashSet<ulong> usersRecentlyPicked { get; } = new ConcurrentHashSet<ulong>();
private static Logger _log { get; }
static PlantPickCommands()
{
_log = LogManager.GetCurrentClassLogger();
#if !GLOBAL_NADEKO
NadekoBot.Client.MessageReceived += PotentialFlowerGeneration;
@ -103,7 +98,8 @@ namespace NadekoBot.Modules.Games
var sent = await channel.SendFileAsync(
fileStream,
file.Key,
$"❗ {firstPart} Pick it up by typing `{NadekoBot.ModulePrefixes[typeof(Games).Name]}pick`")
string.Format("❗ {0} Pick it up by typing `{1}pick`", firstPart,
NadekoBot.ModulePrefixes[typeof(Games).Name]))
.ConfigureAwait(false);
msgs[0] = sent;
@ -115,7 +111,7 @@ namespace NadekoBot.Modules.Games
}
catch (Exception ex)
{
_log.Warn(ex);
LogManager.GetCurrentClassLogger().Warn(ex);
}
});
return Task.CompletedTask;
@ -129,12 +125,6 @@ namespace NadekoBot.Modules.Games
if (!(await channel.Guild.GetCurrentUserAsync()).GetPermissions(channel).ManageMessages)
return;
#if GLOBAL_NADEKO
if (!usersRecentlyPicked.Add(Context.User.Id))
return;
#endif
try
{
List<IUserMessage> msgs;
@ -148,14 +138,6 @@ namespace NadekoBot.Modules.Games
var msg = await channel.SendConfirmAsync($"**{Context.User}** picked {msgs.Count}{NadekoBot.BotConfig.CurrencySign}!").ConfigureAwait(false);
msg.DeleteAfter(10);
}
finally
{
#if GLOBAL_NADEKO
await Task.Delay(60000);
usersRecentlyPicked.TryRemove(Context.User.Id);
#endif
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -174,7 +156,7 @@ namespace NadekoBot.Modules.Games
var imgData = GetRandomCurrencyImage();
var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.BotConfig.CurrencyName[0]);
var msgToSend = $"Oh how Nice! **{Context.User.Username}** planted {(amount == 1 ? (vowelFirst ? "an" : "a") : amount.ToString())} {(amount > 1 ? NadekoBot.BotConfig.CurrencyPluralName : NadekoBot.BotConfig.CurrencyName)}. Pick it using {NadekoBot.ModulePrefixes[typeof(Games).Name]}pick";
var msgToSend = $"Oh how Nice! **{Context.User.Username}** planted {(amount == 1 ? (vowelFirst ? "an" : "a") : amount.ToString())} {(amount > 1 ? NadekoBot.BotConfig.CurrencyPluralName : NadekoBot.BotConfig.CurrencyName)}. Pick it using {Prefix}pick";
IUserMessage msg;
using (var toSend = imgData.Value.ToStream())
@ -236,17 +218,6 @@ namespace NadekoBot.Modules.Games
return images[rng.Next(0, images.Length)];
}
int GetRandomNumber()
{
using (var rg = RandomNumberGenerator.Create())
{
byte[] rno = new byte[4];
rg.GetBytes(rno);
int randomvalue = BitConverter.ToInt32(rno, 0);
return randomvalue;
}
}
}
}
}

View File

@ -307,7 +307,7 @@ namespace NadekoBot.Modules.Games
var del2 = previousMessage?.DeleteAsync();
try { previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); } catch { }
try { await del1; } catch { }
try { await del2; } catch { }
try { if (del2 != null) await del2; } catch { }
});
curUserIndex ^= 1;

View File

@ -11,7 +11,7 @@ using NadekoBot.Extensions;
namespace NadekoBot.Modules.Games
{
[NadekoModule("Games", ">")]
public partial class Games : DiscordModule
public partial class Games : NadekoModule
{
private static string[] _8BallResponses { get; } = NadekoBot.BotConfig.EightBallResponses.Select(ebr => ebr.Text).ToArray();
@ -32,7 +32,6 @@ namespace NadekoBot.Modules.Games
{
if (string.IsNullOrWhiteSpace(question))
return;
var rng = new NadekoRandom();
await Context.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
.AddField(efb => efb.WithName("❓ Question").WithValue(question).WithIsInline(false))

View File

@ -13,19 +13,26 @@ using System.Collections.Generic;
namespace NadekoBot.Modules.Help
{
[NadekoModule("Help", "-")]
public partial class Help : DiscordModule
public class Help : NadekoModule
{
private static string helpString { get; } = NadekoBot.BotConfig.HelpString;
public static string HelpString => String.Format(helpString, NadekoBot.Credentials.ClientId, NadekoBot.ModulePrefixes[typeof(Help).Name]);
public static string DMHelpString { get; } = NadekoBot.BotConfig.DMHelpString;
public const string PatreonUrl = "https://patreon.com/nadekobot";
public const string PaypalUrl = "https://paypal.me/Kwoth";
[NadekoCommand, Usage, Description, Aliases]
public async Task Modules()
{
var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText($" Type `-cmds ModuleName` to get a list of commands in that module. eg `-cmds games`"))
.WithTitle("📜 List Of Modules").WithDescription("\n• " + string.Join("\n• ", NadekoBot.CommandService.Modules.GroupBy(m => m.GetTopLevelModule()).Select(m => m.Key.Name).OrderBy(s => s)));
var embed = new EmbedBuilder().WithOkColor()
.WithFooter(efb => efb.WithText("" + GetText("modules_footer", Prefix)))
.WithTitle(GetText("list_of_modules"))
.WithDescription(string.Join("\n",
NadekoBot.CommandService.Modules.GroupBy(m => m.GetTopLevelModule())
.Select(m => "• " + m.Key.Name)
.OrderBy(s => s)));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
@ -45,18 +52,13 @@ namespace NadekoBot.Modules.Help
var cmdsArray = cmds as CommandInfo[] ?? cmds.ToArray();
if (!cmdsArray.Any())
{
await channel.SendErrorAsync("That module does not exist.").ConfigureAwait(false);
await ReplyErrorLocalized("module_not_found").ConfigureAwait(false);
return;
}
if (module != "customreactions" && module != "conversations")
{
await channel.SendTableAsync("📃 **List Of Commands:**\n", cmdsArray, el => $"{el.Aliases.First(),-15} {"["+el.Aliases.Skip(1).FirstOrDefault()+"]",-8}").ConfigureAwait(false);
}
else
{
await channel.SendMessageAsync("📃 **List Of Commands:**\n• " + string.Join("\n• ", cmdsArray.Select(c => $"{c.Aliases.First()}")));
}
await channel.SendConfirmAsync($" **Type** `\"{NadekoBot.ModulePrefixes[typeof(Help).Name]}h CommandName\"` **to see the help for that specified command.** ***e.g.*** `-h >8ball`").ConfigureAwait(false);
await channel.SendTableAsync($"📃 **{GetText("list_of_commands")}**\n", cmdsArray, el => $"{el.Aliases.First(),-15} {"["+el.Aliases.Skip(1).FirstOrDefault()+"]",-8}").ConfigureAwait(false);
await ConfirmLocalized("commands_instr", Prefix).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -75,32 +77,33 @@ namespace NadekoBot.Modules.Help
if (com == null)
{
await channel.SendErrorAsync("I can't find that command. Please check the **command** and **command prefix** before trying again.");
await ReplyErrorLocalized("command_not_found").ConfigureAwait(false);
return;
}
var str = $"**`{com.Aliases.First()}`**";
var str = string.Format("**`{0}`**", com.Aliases.First());
var alias = com.Aliases.Skip(1).FirstOrDefault();
if (alias != null)
str += $" **/ `{alias}`**";
str += string.Format(" **/ `{0}`**", alias);
var embed = new EmbedBuilder()
.AddField(fb => fb.WithName(str).WithValue($"{string.Format(com.Summary, com.Module.Aliases.First())} {GetCommandRequirements(com)}").WithIsInline(true))
.AddField(fb => fb.WithName("**Usage**").WithValue($"{string.Format(com.Remarks, com.Module.Aliases.First())}").WithIsInline(false))
.AddField(fb => fb.WithName(GetText("usage")).WithValue(string.Format(com.Remarks, com.Module.Aliases.First())).WithIsInline(false))
.WithColor(NadekoBot.OkColor);
await channel.EmbedAsync(embed).ConfigureAwait(false);
}
private string GetCommandRequirements(CommandInfo cmd) =>
String.Join(" ", cmd.Preconditions
string.Join(" ", cmd.Preconditions
.Where(ca => ca is OwnerOnlyAttribute || ca is RequireUserPermissionAttribute)
.Select(ca =>
{
if (ca is OwnerOnlyAttribute)
return "**Bot Owner only.**";
return Format.Bold(GetText("bot_owner_only"));
var cau = (RequireUserPermissionAttribute)ca;
if (cau.GuildPermission != null)
return $"**Requires {cau.GuildPermission} server permission.**".Replace("Guild", "Server");
else
return $"**Requires {cau.ChannelPermission} channel permission.**".Replace("Guild", "Server");
return Format.Bold(GetText("server_permission", cau.GuildPermission))
.Replace("Guild", "Server");
return Format.Bold(GetText("channel_permission", cau.ChannelPermission))
.Replace("Guild", "Server");
}));
[NadekoCommand, Usage, Description, Aliases]
@ -109,14 +112,14 @@ namespace NadekoBot.Modules.Help
public async Task Hgit()
{
var helpstr = new StringBuilder();
helpstr.AppendLine("You can support the project on patreon: <https://patreon.com/nadekobot> or paypal: <https://www.paypal.me/Kwoth>\n");
helpstr.AppendLine("##Table Of Contents");
helpstr.AppendLine(GetText("cmdlist_donate", PatreonUrl, PaypalUrl) + "\n");
helpstr.AppendLine("##"+ GetText("table_of_contents"));
helpstr.AppendLine(string.Join("\n", NadekoBot.CommandService.Modules.Where(m => m.GetTopLevelModule().Name.ToLowerInvariant() != "help")
.Select(m => m.GetTopLevelModule().Name)
.Distinct()
.OrderBy(m => m)
.Prepend("Help")
.Select(m => $"- [{m}](#{m.ToLowerInvariant()})")));
.Select(m => string.Format("- [{0}](#{1})", m, m.ToLowerInvariant()))));
helpstr.AppendLine();
string lastModule = null;
foreach (var com in NadekoBot.CommandService.Commands.OrderBy(com => com.Module.GetTopLevelModule().Name).GroupBy(c => c.Aliases.First()).Select(g => g.First()))
@ -127,44 +130,35 @@ namespace NadekoBot.Modules.Help
if (lastModule != null)
{
helpstr.AppendLine();
helpstr.AppendLine("###### [Back to TOC](#table-of-contents)");
helpstr.AppendLine($"###### [{GetText("back_to_toc")}](#{GetText("table_of_contents").ToLowerInvariant().Replace(' ', '-')})");
}
helpstr.AppendLine();
helpstr.AppendLine("### " + module.Name + " ");
helpstr.AppendLine("Command and aliases | Description | Usage");
helpstr.AppendLine($"{GetText("cmd_and_alias")} | {GetText("desc")} | {GetText("usage")}");
helpstr.AppendLine("----------------|--------------|-------");
lastModule = module.Name;
}
helpstr.AppendLine($"{string.Join(" ", com.Aliases.Select(a => "`" + a + "`"))} | {string.Format(com.Summary, com.Module.GetPrefix())} {GetCommandRequirements(com)} | {string.Format(com.Remarks, com.Module.GetPrefix())}");
helpstr.AppendLine($"{string.Join(" ", com.Aliases.Select(a => "`" + a + "`"))} |" +
$" {string.Format(com.Summary, com.Module.GetPrefix())} {GetCommandRequirements(com)} |" +
$" {string.Format(com.Remarks, com.Module.GetPrefix())}");
}
helpstr = helpstr.Replace(NadekoBot.Client.CurrentUser.Username , "@BotName");
File.WriteAllText("../../docs/Commands List.md", helpstr.ToString());
await Context.Channel.SendConfirmAsync("Commandlist Regenerated").ConfigureAwait(false);
await ReplyConfirmLocalized("commandlist_regen").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Guide()
{
var channel = (ITextChannel)Context.Channel;
await channel.SendConfirmAsync(
@"**LIST OF COMMANDS**: <http://nadekobot.readthedocs.io/en/latest/Commands%20List/>
**Hosting Guides and docs can be found here**: <http://nadekobot.readthedocs.io/en/latest/>").ConfigureAwait(false);
await ConfirmLocalized("guide",
"http://nadekobot.readthedocs.io/en/latest/Commands%20List/",
"http://nadekobot.readthedocs.io/en/latest/").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Donate()
{
var channel = (ITextChannel)Context.Channel;
await channel.SendConfirmAsync(
$@"You can support the NadekoBot project on patreon. <https://patreon.com/nadekobot> or
Paypal <https://paypal.me/Kwoth>
Don't forget to leave your discord name or id in the message.
**Thank you** ").ConfigureAwait(false);
await ReplyConfirmLocalized("donate", PatreonUrl, PaypalUrl).ConfigureAwait(false);
}
}

View File

@ -21,7 +21,7 @@ namespace NadekoBot.Modules.Music
{
[NadekoModule("Music", "!!")]
[DontAutoLoad]
public partial class Music : DiscordModule
public partial class Music : NadekoModule
{
public static ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>();
@ -415,8 +415,6 @@ namespace NadekoBot.Modules.Music
var arg = directory;
if (string.IsNullOrWhiteSpace(arg))
return;
try
{
var dir = new DirectoryInfo(arg);
var fileEnum = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(x => !x.Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System));
@ -425,18 +423,19 @@ namespace NadekoBot.Modules.Music
{
try
{
await QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false);
await QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false);
}
catch (PlaylistFullException)
{
break;
}
catch { }
catch
{
// ignored
}
}
await Context.Channel.SendConfirmAsync("🎵 Directory queue complete.").ConfigureAwait(false);
}
catch { }
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]

View File

@ -17,11 +17,11 @@ using System.Collections.Concurrent;
namespace NadekoBot.Modules.NSFW
{
[NadekoModule("NSFW", "~")]
public class NSFW : DiscordModule
public class NSFW : NadekoModule
{
private static ConcurrentDictionary<ulong, Timer> AutoHentaiTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
private static ConcurrentHashSet<ulong> _hentaiBombBlacklist { get; } = new ConcurrentHashSet<ulong>();
private static readonly ConcurrentDictionary<ulong, Timer> AutoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentHashSet<ulong> HentaiBombBlacklist = new ConcurrentHashSet<ulong>();
private async Task InternalHentai(IMessageChannel channel, string tag, bool noError)
{
@ -30,7 +30,7 @@ namespace NadekoBot.Modules.NSFW
tag = "rating%3Aexplicit+" + tag;
var rng = new NadekoRandom();
Task<string> provider = Task.FromResult("");
var provider = Task.FromResult("");
switch (rng.Next(0, 4))
{
case 0:
@ -45,20 +45,18 @@ namespace NadekoBot.Modules.NSFW
case 3:
provider = GetYandereImageLink(tag);
break;
default:
break;
}
var link = await provider.ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(link))
{
if (!noError)
await channel.SendErrorAsync("No results found.").ConfigureAwait(false);
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
return;
}
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithImageUrl(link)
.WithDescription("Tag: " + tag))
.WithDescription($"{GetText("tag")}: " + tag))
.ConfigureAwait(false);
}
@ -74,11 +72,10 @@ namespace NadekoBot.Modules.NSFW
if (interval == 0)
{
if (AutoHentaiTimers.TryRemove(Context.Channel.Id, out t))
{
if (!AutoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return;
t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer
await Context.Channel.SendConfirmAsync("Autohentai stopped.").ConfigureAwait(false);
}
await ReplyConfirmLocalized("autohentai_stopped").ConfigureAwait(false);
return;
}
@ -96,7 +93,10 @@ namespace NadekoBot.Modules.NSFW
else
await InternalHentai(Context.Channel, tagsArr[new NadekoRandom().Next(0, tagsArr.Length)], true).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}, null, interval * 1000, interval * 1000);
AutoHentaiTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) =>
@ -105,15 +105,16 @@ namespace NadekoBot.Modules.NSFW
return t;
});
await Context.Channel.SendConfirmAsync($"Autohentai started. Reposting every {interval}s with one of the following tags:\n{string.Join(", ", tagsArr)}")
.ConfigureAwait(false);
await ReplyConfirmLocalized("autohentai_started",
interval,
string.Join(", ", tagsArr)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task HentaiBomb([Remainder] string tag = null)
{
if (!_hentaiBombBlacklist.Add(Context.User.Id))
if (!HentaiBombBlacklist.Add(Context.User.Id))
return;
try
{
@ -125,19 +126,19 @@ namespace NadekoBot.Modules.NSFW
GetKonachanImageLink(tag),
GetYandereImageLink(tag)).ConfigureAwait(false);
var linksEnum = links?.Where(l => l != null);
var linksEnum = links?.Where(l => l != null).ToArray();
if (links == null || !linksEnum.Any())
{
await Context.Channel.SendErrorAsync("No results found.").ConfigureAwait(false);
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
return;
}
await Context.Channel.SendMessageAsync(String.Join("\n\n", linksEnum)).ConfigureAwait(false);
await Context.Channel.SendMessageAsync(string.Join("\n\n", linksEnum)).ConfigureAwait(false);
}
finally
{
await Task.Delay(5000).ConfigureAwait(false);
_hentaiBombBlacklist.TryRemove(Context.User.Id);
HentaiBombBlacklist.TryRemove(Context.User.Id);
}
}
#endif
@ -157,7 +158,7 @@ namespace NadekoBot.Modules.NSFW
var url = await GetE621ImageLink(tag).ConfigureAwait(false);
if (url == null)
await Context.Channel.SendErrorAsync(Context.User.Mention + " No results.");
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
else
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(Context.User.Mention + " " + tag)
@ -178,7 +179,7 @@ namespace NadekoBot.Modules.NSFW
var url = await GetDanbooruImageLink(tag).ConfigureAwait(false);
if (url == null)
await Context.Channel.SendErrorAsync(Context.User.Mention + " No results.").ConfigureAwait(false);
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
else
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(Context.User.Mention + " " + tag)
@ -229,7 +230,7 @@ namespace NadekoBot.Modules.NSFW
{
obj = JArray.Parse(await http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}").ConfigureAwait(false))[0];
}
await Context.Channel.SendMessageAsync($"http://media.oboobs.ru/{ obj["preview"].ToString() }").ConfigureAwait(false);
await Context.Channel.SendMessageAsync($"http://media.oboobs.ru/{obj["preview"]}").ConfigureAwait(false);
}
catch (Exception ex)
{
@ -247,7 +248,7 @@ namespace NadekoBot.Modules.NSFW
{
obj = JArray.Parse(await http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}").ConfigureAwait(false))[0];
}
await Context.Channel.SendMessageAsync($"http://media.obutts.ru/{ obj["preview"].ToString() }").ConfigureAwait(false);
await Context.Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}").ConfigureAwait(false);
}
catch (Exception ex)
{

View File

@ -0,0 +1,129 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.Threading.Tasks;
namespace NadekoBot.Modules
{
public abstract class NadekoModule : ModuleBase
{
protected readonly Logger _log;
protected CultureInfo _cultureInfo;
public readonly string Prefix;
public readonly string ModuleTypeName;
public readonly string LowerModuleTypeName;
protected NadekoModule(bool isTopLevelModule = true)
{
//if it's top level module
ModuleTypeName = isTopLevelModule ? this.GetType().Name : this.GetType().DeclaringType.Name;
LowerModuleTypeName = ModuleTypeName.ToLowerInvariant();
if (!NadekoBot.ModulePrefixes.TryGetValue(ModuleTypeName, out Prefix))
Prefix = "?err?";
_log = LogManager.GetCurrentClassLogger();
}
protected override void BeforeExecute()
{
_cultureInfo = NadekoBot.Localization.GetCultureInfo(Context.Guild?.Id);
_log.Warn("Culture info is {0}", _cultureInfo);
}
//public Task<IUserMessage> ReplyConfirmLocalized(string titleKey, string textKey, string url = null, string footer = null)
//{
// var title = NadekoBot.ResponsesResourceManager.GetString(titleKey, cultureInfo);
// var text = NadekoBot.ResponsesResourceManager.GetString(textKey, cultureInfo);
// return Context.Channel.SendConfirmAsync(title, text, url, footer);
//}
//public Task<IUserMessage> ReplyConfirmLocalized(string textKey)
//{
// var text = NadekoBot.ResponsesResourceManager.GetString(textKey, cultureInfo);
// return Context.Channel.SendConfirmAsync(Context.User.Mention + " " + textKey);
//}
//public Task<IUserMessage> ReplyErrorLocalized(string titleKey, string textKey, string url = null, string footer = null)
//{
// var title = NadekoBot.ResponsesResourceManager.GetString(titleKey, cultureInfo);
// var text = NadekoBot.ResponsesResourceManager.GetString(textKey, cultureInfo);
// return Context.Channel.SendErrorAsync(title, text, url, footer);
//}
/// <summary>
/// Used as failsafe in case response key doesn't exist in the selected or default language.
/// </summary>
private static readonly CultureInfo _usCultureInfo = new CultureInfo("en-US");
public static string GetTextStatic(string key, CultureInfo cultureInfo, string lowerModuleTypeName)
{
var text = NadekoBot.ResponsesResourceManager.GetString(lowerModuleTypeName + "_" + key, cultureInfo);
if (string.IsNullOrWhiteSpace(text))
{
LogManager.GetCurrentClassLogger().Warn(lowerModuleTypeName + "_" + key + " key is missing from " + cultureInfo + " response strings. PLEASE REPORT THIS.");
text = NadekoBot.ResponsesResourceManager.GetString(lowerModuleTypeName + "_" + key, _usCultureInfo) ?? $"Error: dkey {lowerModuleTypeName + "_" + key} not found!";
if (string.IsNullOrWhiteSpace(text))
return "I cant tell if you command is executed, because there was an error printing out the response. Key '" +
lowerModuleTypeName + "_" + key + "' " + "is missing from resources. Please report this.";
}
return text;
}
public static string GetTextStatic(string key, CultureInfo cultureInfo, string lowerModuleTypeName,
params object[] replacements)
{
try
{
return string.Format(GetTextStatic(key, cultureInfo, lowerModuleTypeName), replacements);
}
catch (FormatException)
{
return "I cant tell if you command is executed, because there was an error printing out the response. Key '" +
lowerModuleTypeName + "_" + key + "' " + "is not properly formatted. Please report this.";
}
}
protected string GetText(string key) =>
GetTextStatic(key, _cultureInfo, LowerModuleTypeName);
protected string GetText(string key, params object[] replacements) =>
GetTextStatic(key, _cultureInfo, LowerModuleTypeName, replacements);
public Task<IUserMessage> ErrorLocalized(string textKey, params object[] replacements)
{
var text = GetText(textKey, replacements);
return Context.Channel.SendErrorAsync(text);
}
public Task<IUserMessage> ReplyErrorLocalized(string textKey, params object[] replacements)
{
var text = GetText(textKey, replacements);
return Context.Channel.SendErrorAsync(Context.User.Mention + " " + text);
}
public Task<IUserMessage> ConfirmLocalized(string textKey, params object[] replacements)
{
var text = GetText(textKey, replacements);
return Context.Channel.SendConfirmAsync(text);
}
public Task<IUserMessage> ReplyConfirmLocalized(string textKey, params object[] replacements)
{
var text = GetText(textKey, replacements);
return Context.Channel.SendConfirmAsync(Context.User.Mention + " " + text);
}
}
public abstract class NadekoSubmodule : NadekoModule
{
protected NadekoSubmodule() : base(false)
{
}
}
}

View File

@ -0,0 +1,15 @@
using Discord;
using NadekoBot.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules
{
public static class NadekoModuleExtensions
{
}
}

View File

@ -101,23 +101,23 @@ namespace NadekoBot.Modules.Permissions
{
return true;
}
else
{
activeCdsForGuild.Add(new ActiveCooldown()
{
UserId = user.Id,
Command = cmd.Aliases.First().ToLowerInvariant(),
});
var t = Task.Run(async () =>
var _ = Task.Run(async () =>
{
try
{
await Task.Delay(cdRule.Seconds * 1000);
activeCdsForGuild.RemoveWhere(ac => ac.Command == cmd.Aliases.First().ToLowerInvariant() && ac.UserId == user.Id);
}
catch { }
});
catch
{
// ignored
}
});
}
return false;
}

View File

@ -41,8 +41,6 @@ namespace NadekoBot.Modules.Permissions
}
static FilterCommands()
{
using (var uow = DbHandler.UnitOfWork())
{
var guildConfigs = NadekoBot.AllGuildConfigs;
@ -57,8 +55,6 @@ namespace NadekoBot.Modules.Permissions
WordFilteringServers = new ConcurrentHashSet<ulong>(serverFiltering.Select(gc => gc.GuildId));
WordFilteringChannels = new ConcurrentHashSet<ulong>(guildConfigs.SelectMany(gc => gc.FilterWordsChannelIds.Select(fwci => fwci.ChannelId)));
}
}
[NadekoCommand, Usage, Description, Aliases]

View File

@ -15,7 +15,7 @@ using NLog;
namespace NadekoBot.Modules.Permissions
{
[NadekoModule("Permissions", ";")]
public partial class Permissions : DiscordModule
public partial class Permissions : NadekoModule
{
public class PermissionCache
{

View File

@ -16,21 +16,21 @@ using System.Collections.Concurrent;
namespace NadekoBot.Modules.Pokemon
{
[NadekoModule("Pokemon", ">")]
public partial class Pokemon : DiscordModule
public class Pokemon : NadekoModule
{
private static List<PokemonType> PokemonTypes = new List<PokemonType>();
private static ConcurrentDictionary<ulong, PokeStats> Stats = new ConcurrentDictionary<ulong, PokeStats>();
private static readonly List<PokemonType> _pokemonTypes = new List<PokemonType>();
private static readonly ConcurrentDictionary<ulong, PokeStats> _stats = new ConcurrentDictionary<ulong, PokeStats>();
public const string PokemonTypesFile = "data/pokemon_types.json";
private static new Logger _log { get; }
private new static Logger _log { get; }
static Pokemon()
{
_log = LogManager.GetCurrentClassLogger();
if (File.Exists(PokemonTypesFile))
{
PokemonTypes = JsonConvert.DeserializeObject<List<PokemonType>>(File.ReadAllText(PokemonTypesFile));
_pokemonTypes = JsonConvert.DeserializeObject<List<PokemonType>>(File.ReadAllText(PokemonTypesFile));
}
else
{
@ -42,21 +42,18 @@ namespace NadekoBot.Modules.Pokemon
private int GetDamage(PokemonType usertype, PokemonType targetType)
{
var rng = new Random();
int damage = rng.Next(40, 60);
foreach (PokemonMultiplier Multiplier in usertype.Multipliers)
var damage = rng.Next(40, 60);
foreach (var multiplierObj in usertype.Multipliers)
{
if (Multiplier.Type == targetType.Name)
{
var multiplier = Multiplier.Multiplication;
damage = (int)(damage * multiplier);
}
if (multiplierObj.Type != targetType.Name) continue;
damage = (int)(damage * multiplierObj.Multiplication);
}
return damage;
}
private PokemonType GetPokeType(ulong id)
private static PokemonType GetPokeType(ulong id)
{
Dictionary<ulong, string> setTypes;
@ -69,20 +66,18 @@ namespace NadekoBot.Modules.Pokemon
{
return StringToPokemonType(setTypes[id]);
}
int count = PokemonTypes.Count;
var count = _pokemonTypes.Count;
int remainder = Math.Abs((int)(id % (ulong)count));
var remainder = Math.Abs((int)(id % (ulong)count));
return PokemonTypes[remainder];
return _pokemonTypes[remainder];
}
private PokemonType StringToPokemonType(string v)
private static PokemonType StringToPokemonType(string v)
{
var str = v?.ToUpperInvariant();
var list = PokemonTypes;
foreach (PokemonType p in list)
var list = _pokemonTypes;
foreach (var p in list)
{
if (str == p.Name)
{
@ -92,7 +87,6 @@ namespace NadekoBot.Modules.Pokemon
return null;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Attack(string move, IGuildUser targetUser = null)
@ -105,46 +99,44 @@ namespace NadekoBot.Modules.Pokemon
if (targetUser == null)
{
await Context.Channel.SendMessageAsync("No such person.").ConfigureAwait(false);
await ReplyErrorLocalized("user_not_found").ConfigureAwait(false);
return;
}
else if (targetUser == user)
if (targetUser == user)
{
await Context.Channel.SendMessageAsync("You can't attack yourself.").ConfigureAwait(false);
await ReplyErrorLocalized("cant_attack_yourself").ConfigureAwait(false);
return;
}
// Checking stats first, then move
//Set up the userstats
PokeStats userStats;
userStats = Stats.GetOrAdd(user.Id, new PokeStats());
var userStats = _stats.GetOrAdd(user.Id, new PokeStats());
//Check if able to move
//User not able if HP < 0, has made more than 4 attacks
if (userStats.Hp < 0)
{
await Context.Channel.SendMessageAsync($"{user.Mention} has fainted and was not able to move!").ConfigureAwait(false);
await ReplyErrorLocalized("you_fainted").ConfigureAwait(false);
return;
}
if (userStats.MovesMade >= 5)
{
await Context.Channel.SendMessageAsync($"{user.Mention} has used too many moves in a row and was not able to move!").ConfigureAwait(false);
await ReplyErrorLocalized("too_many_moves").ConfigureAwait(false);
return;
}
if (userStats.LastAttacked.Contains(targetUser.Id))
{
await Context.Channel.SendMessageAsync($"{user.Mention} can't attack again without retaliation!").ConfigureAwait(false);
await ReplyErrorLocalized("cant_attack_again").ConfigureAwait(false);
return;
}
//get target stats
PokeStats targetStats;
targetStats = Stats.GetOrAdd(targetUser.Id, new PokeStats());
var targetStats = _stats.GetOrAdd(targetUser.Id, new PokeStats());
//If target's HP is below 0, no use attacking
if (targetStats.Hp <= 0)
{
await Context.Channel.SendMessageAsync($"{targetUser.Mention} has already fainted!").ConfigureAwait(false);
await ReplyErrorLocalized("too_many_moves", targetUser).ConfigureAwait(false);
return;
}
@ -154,7 +146,7 @@ namespace NadekoBot.Modules.Pokemon
var enabledMoves = userType.Moves;
if (!enabledMoves.Contains(move.ToLowerInvariant()))
{
await Context.Channel.SendMessageAsync($"{user.Mention} is not able to use **{move}**. Type {NadekoBot.ModulePrefixes[typeof(Pokemon).Name]}ml to see moves").ConfigureAwait(false);
await ReplyErrorLocalized("invalid_move", Format.Bold(move), Prefix).ConfigureAwait(false);
return;
}
@ -165,31 +157,31 @@ namespace NadekoBot.Modules.Pokemon
//apply damage to target
targetStats.Hp -= damage;
var response = $"{user.Mention} used **{move}**{userType.Icon} on {targetUser.Mention}{targetType.Icon} for **{damage}** damage";
var response = GetText("attack", Format.Bold(move), userType.Icon, Format.Bold(targetUser.ToString()), targetType.Icon, Format.Bold(damage.ToString()));
//Damage type
if (damage < 40)
{
response += "\nIt's not effective..";
response += "\n" + GetText("not_effective");
}
else if (damage > 60)
{
response += "\nIt's super effective!";
response += "\n" + GetText("super_effective");
}
else
{
response += "\nIt's somewhat effective";
response += "\n" + GetText("somewhat_effective");
}
//check fainted
if (targetStats.Hp <= 0)
{
response += $"\n**{targetUser.Mention}** has fainted!";
response += "\n" + GetText("fainted", Format.Bold(targetUser.ToString()));
}
else
{
response += $"\n**{targetUser.Mention}** has {targetStats.Hp} HP remaining";
response += "\n" + GetText("hp_remaining", Format.Bold(targetUser.ToString()), targetStats.Hp);
}
//update other stats
@ -203,10 +195,10 @@ namespace NadekoBot.Modules.Pokemon
//update dictionary
//This can stay the same right?
Stats[user.Id] = userStats;
Stats[targetUser.Id] = targetStats;
_stats[user.Id] = userStats;
_stats[targetUser.Id] = targetStats;
await Context.Channel.SendMessageAsync(response).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(Context.User.Mention + " " + response).ConfigureAwait(false);
}
@ -218,12 +210,10 @@ namespace NadekoBot.Modules.Pokemon
var userType = GetPokeType(user.Id);
var movesList = userType.Moves;
var str = $"**Moves for `{userType.Name}` type.**";
foreach (string m in movesList)
{
str += $"\n{userType.Icon}{m}";
}
await Context.Channel.SendMessageAsync(str).ConfigureAwait(false);
var embed = new EmbedBuilder().WithOkColor()
.WithTitle(GetText("moves", userType))
.WithDescription(string.Join("\n", movesList.Select(m => userType.Icon + " " + m)));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -232,17 +222,18 @@ namespace NadekoBot.Modules.Pokemon
{
IGuildUser user = (IGuildUser)Context.User;
if (targetUser == null) {
await Context.Channel.SendMessageAsync("No such person.").ConfigureAwait(false);
if (targetUser == null)
{
await ReplyErrorLocalized("user_not_found").ConfigureAwait(false);
return;
}
if (Stats.ContainsKey(targetUser.Id))
if (_stats.ContainsKey(targetUser.Id))
{
var targetStats = Stats[targetUser.Id];
var targetStats = _stats[targetUser.Id];
if (targetStats.Hp == targetStats.MaxHp)
{
await Context.Channel.SendMessageAsync($"{targetUser.Mention} already has full HP!").ConfigureAwait(false);
await ReplyErrorLocalized("already_full", Format.Bold(targetUser.ToString())).ConfigureAwait(false);
return;
}
//Payment~
@ -253,7 +244,7 @@ namespace NadekoBot.Modules.Pokemon
{
if (!await CurrencyHandler.RemoveCurrencyAsync(user, $"Poke-Heal {target}", amount, true).ConfigureAwait(false))
{
try { await Context.Channel.SendMessageAsync($"{user.Mention} You don't have enough {NadekoBot.BotConfig.CurrencyName}s.").ConfigureAwait(false); } catch { }
await ReplyErrorLocalized("no_currency", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
return;
}
}
@ -263,23 +254,20 @@ namespace NadekoBot.Modules.Pokemon
if (targetStats.Hp < 0)
{
//Could heal only for half HP?
Stats[targetUser.Id].Hp = (targetStats.MaxHp / 2);
_stats[targetUser.Id].Hp = (targetStats.MaxHp / 2);
if (target == "yourself")
{
await Context.Channel.SendMessageAsync($"You revived yourself with one {NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false);
await ReplyConfirmLocalized("revive_yourself", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
return;
}
await ReplyConfirmLocalized("revive_other", Format.Bold(targetUser.ToString()), NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
}
await ReplyConfirmLocalized("healed", Format.Bold(targetUser.ToString()), NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
}
else
{
await Context.Channel.SendMessageAsync($"{user.Mention} revived {targetUser.Mention} with one {NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false);
}
return;
}
await Context.Channel.SendMessageAsync($"{user.Mention} healed {targetUser.Mention} with one {NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false);
return;
}
else
{
await Context.Channel.SendMessageAsync($"{targetUser.Mention} already has full HP!").ConfigureAwait(false);
await ErrorLocalized("already_full", Format.Bold(targetUser.ToString()));
}
}
@ -288,15 +276,9 @@ namespace NadekoBot.Modules.Pokemon
[RequireContext(ContextType.Guild)]
public async Task Type(IGuildUser targetUser = null)
{
IGuildUser user = (IGuildUser)Context.User;
if (targetUser == null)
{
return;
}
targetUser = targetUser ?? (IGuildUser)Context.User;
var pType = GetPokeType(targetUser.Id);
await Context.Channel.SendMessageAsync($"Type of {targetUser.Mention} is **{pType.Name.ToLowerInvariant()}**{pType.Icon}").ConfigureAwait(false);
await ReplyConfirmLocalized("type_of_user", Format.Bold(targetUser.ToString()), pType).ConfigureAwait(false);
}
@ -309,7 +291,7 @@ namespace NadekoBot.Modules.Pokemon
var targetType = StringToPokemonType(typeTargeted);
if (targetType == null)
{
await Context.Channel.EmbedAsync(PokemonTypes.Aggregate(new EmbedBuilder().WithDescription("List of the available types:"),
await Context.Channel.EmbedAsync(_pokemonTypes.Aggregate(new EmbedBuilder().WithDescription("List of the available types:"),
(eb, pt) => eb.AddField(efb => efb.WithName(pt.Name)
.WithValue(pt.Icon)
.WithIsInline(true)))
@ -318,7 +300,7 @@ namespace NadekoBot.Modules.Pokemon
}
if (targetType == GetPokeType(user.Id))
{
await Context.Channel.SendMessageAsync($"Your type is already {targetType.Name.ToLowerInvariant()}{targetType.Icon}").ConfigureAwait(false);
await ReplyErrorLocalized("already_that_type", targetType).ConfigureAwait(false);
return;
}
@ -326,20 +308,19 @@ namespace NadekoBot.Modules.Pokemon
var amount = 1;
if (amount > 0)
{
if (!await CurrencyHandler.RemoveCurrencyAsync(user, $"{user.Mention} change type to {typeTargeted}", amount, true).ConfigureAwait(false))
if (!await CurrencyHandler.RemoveCurrencyAsync(user, $"{user} change type to {typeTargeted}", amount, true).ConfigureAwait(false))
{
try { await Context.Channel.SendMessageAsync($"{user.Mention} You don't have enough {NadekoBot.BotConfig.CurrencyName}s.").ConfigureAwait(false); } catch { }
await ReplyErrorLocalized("no_currency", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
return;
}
}
//Actually changing the type here
Dictionary<ulong, string> setTypes;
using (var uow = DbHandler.UnitOfWork())
{
var pokeUsers = uow.PokeGame.GetAll();
setTypes = pokeUsers.ToDictionary(x => x.UserId, y => y.type);
var pokeUsers = uow.PokeGame.GetAll().ToArray();
var setTypes = pokeUsers.ToDictionary(x => x.UserId, y => y.type);
var pt = new UserPokeTypes
{
UserId = user.Id,
@ -353,7 +334,7 @@ namespace NadekoBot.Modules.Pokemon
else
{
//update user in db
var pokeUserCmd = pokeUsers.Where(p => p.UserId == user.Id).FirstOrDefault();
var pokeUserCmd = pokeUsers.FirstOrDefault(p => p.UserId == user.Id);
pokeUserCmd.type = targetType.Name;
uow.PokeGame.Update(pokeUserCmd);
}
@ -361,9 +342,10 @@ namespace NadekoBot.Modules.Pokemon
}
//Now for the response
await Context.Channel.SendMessageAsync($"Set type of {user.Mention} to {typeTargeted}{targetType.Icon} for a {NadekoBot.BotConfig.CurrencySign}").ConfigureAwait(false);
await ReplyConfirmLocalized("settype_success",
targetType,
NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
}
}
}

View File

@ -15,6 +15,9 @@ namespace NadekoBot.Modules.Pokemon
public List<PokemonMultiplier> Multipliers { get; set; }
public string Icon { get; set; }
public string[] Moves { get; set; }
public override string ToString() =>
Icon + "**" + Name.ToLowerInvariant() + "**" + Icon;
}
public class PokemonMultiplier
{

View File

@ -230,7 +230,7 @@ namespace NadekoBot.Modules.Searches
var link = "http://anilist.co/api/anime/search/" + Uri.EscapeUriString(query);
using (var http = new HttpClient())
{
var res = await http.GetStringAsync("http://anilist.co/api/anime/search/" + Uri.EscapeUriString(query) + $"?access_token={anilistToken}").ConfigureAwait(false);
var res = await http.GetStringAsync(link + $"?access_token={anilistToken}").ConfigureAwait(false);
var smallObj = JArray.Parse(res)[0];
var aniData = await http.GetStringAsync("http://anilist.co/api/anime/" + smallObj["id"] + $"?access_token={anilistToken}").ConfigureAwait(false);

View File

@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Searches
var rankimg = $"{model.Competitive.rank_img}";
var rank = $"{model.Competitive.rank}";
var competitiveplay = $"{model.Games.Competitive.played}";
//var competitiveplay = $"{model.Games.Competitive.played}";
if (string.IsNullOrWhiteSpace(rank))
{
var embed = new EmbedBuilder()

View File

@ -12,7 +12,8 @@ namespace NadekoBot.Modules.Searches
[Group]
public class PlaceCommands : ModuleBase
{
private static string typesStr { get; } = $"`List of \"{NadekoBot.ModulePrefixes[typeof(Searches).Name]}place\" tags:`\n" + String.Join(", ", Enum.GetNames(typeof(PlaceType)));
private static string typesStr { get; } =
string.Format("`List of \"{0}place\" tags:`\n", NadekoBot.ModulePrefixes[typeof(Searches).Name]) + String.Join(", ", Enum.GetNames(typeof(PlaceType)));
public enum PlaceType
{

View File

@ -103,19 +103,23 @@ namespace NadekoBot.Modules.Searches
oldStatus.IsLive != newStatus.IsLive)
{
var server = NadekoBot.Client.GetGuild(fs.GuildId);
if (server == null)
return;
var channel = server.GetTextChannel(fs.ChannelId);
var channel = server?.GetTextChannel(fs.ChannelId);
if (channel == null)
return;
try
{
var msg = await channel.EmbedAsync(fs.GetEmbed(newStatus)).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
}
catch { }
}
catch
{
// ignored
}
}));
FirstPass = false;

View File

@ -28,7 +28,7 @@ using System.Xml.Linq;
namespace NadekoBot.Modules.Searches
{
[NadekoModule("Searches", "~")]
public partial class Searches : DiscordModule
public partial class Searches : NadekoModule
{
[NadekoCommand, Usage, Description, Aliases]
public async Task Weather([Remainder] string query)
@ -499,12 +499,12 @@ namespace NadekoBot.Modules.Searches
var data = JsonConvert.DeserializeObject<DefineModel>(res);
var sense = data.Results.Where(x => x.Senses != null && x.Senses[0].Definition != null).FirstOrDefault()?.Senses[0];
var sense = data.Results.FirstOrDefault(x => x.Senses?[0].Definition != null)?.Senses[0];
if (sense?.Definition == null)
return;
string definition = sense.Definition.ToString();
var definition = sense.Definition.ToString();
if (!(sense.Definition is string))
definition = ((JArray)JToken.Parse(sense.Definition.ToString())).First.ToString();
@ -536,7 +536,7 @@ namespace NadekoBot.Modules.Searches
}
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
string res = "";
var res = "";
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
@ -548,7 +548,7 @@ namespace NadekoBot.Modules.Searches
{
var items = JObject.Parse(res);
var item = items["defs"]["def"];
var hashtag = item["hashtag"].ToString();
//var hashtag = item["hashtag"].ToString();
var link = item["uri"].ToString();
var desc = item["text"].ToString();
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()

View File

@ -32,7 +32,6 @@ namespace NadekoBot.Modules.Utility
var voicechn = (await guild.GetVoiceChannelsAsync()).Count();
var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(guild.Id >> 22);
var sb = new StringBuilder();
var users = await guild.GetUsersAsync().ConfigureAwait(false);
var features = string.Join("\n", guild.Features);
if (string.IsNullOrWhiteSpace(features))
@ -45,13 +44,13 @@ namespace NadekoBot.Modules.Utility
.AddField(fb => fb.WithName("**Members**").WithValue(users.Count.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Text Channels**").WithValue(textchn.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Voice Channels**").WithValue(voicechn.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true))
.AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName("**Region**").WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Roles**").WithValue((guild.Roles.Count - 1).ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Features**").WithValue(features).WithIsInline(true))
.WithImageUrl(guild.IconUrl)
.WithColor(NadekoBot.OkColor);
if (guild.Emojis.Count() > 0)
if (guild.Emojis.Any())
{
embed.AddField(fb => fb.WithName($"**Custom Emojis ({guild.Emojis.Count})**").WithValue(string.Join(" ", guild.Emojis.Shuffle().Take(25).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))));
}
@ -71,7 +70,7 @@ namespace NadekoBot.Modules.Utility
.WithTitle(ch.Name)
.WithDescription(ch.Topic?.SanitizeMentions())
.AddField(fb => fb.WithName("**ID**").WithValue(ch.Id.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true))
.AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName("**Users**").WithValue(usercount.ToString()).WithIsInline(true))
.WithColor(NadekoBot.OkColor);
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -81,7 +80,6 @@ namespace NadekoBot.Modules.Utility
[RequireContext(ContextType.Guild)]
public async Task UserInfo(IGuildUser usr = null)
{
var channel = (ITextChannel)Context.Channel;
var user = usr ?? Context.User as IGuildUser;
if (user == null)
@ -95,7 +93,7 @@ namespace NadekoBot.Modules.Utility
}
embed.AddField(fb => fb.WithName("**ID**").WithValue(user.Id.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Joined Server**").WithValue($"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "unavail."}").WithIsInline(true))
.AddField(fb => fb.WithName("**Joined Discord**").WithValue($"{user.CreatedAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true))
.AddField(fb => fb.WithName("**Joined Discord**").WithValue($"{user.CreatedAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Take(10).Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true))
.WithColor(NadekoBot.OkColor);

View File

@ -91,7 +91,7 @@ namespace NadekoBot.Modules.Utility
public void Reset()
{
source.Cancel();
var t = Task.Run(Run);
var _ = Task.Run(Run);
}
public void Stop()

View File

@ -45,15 +45,15 @@ namespace NadekoBot.Modules.Utility
foreach (var r in reminders)
{
try { var t = StartReminder(r); } catch (Exception ex) { _log.Warn(ex); }
Task.Run(() => StartReminder(r));
}
}
private static async Task StartReminder(Reminder r)
{
var now = DateTime.Now;
var twoMins = new TimeSpan(0, 2, 0);
TimeSpan time = r.When - now;
var time = r.When - now;
if (time.TotalMilliseconds > int.MaxValue)
return;

View File

@ -21,7 +21,7 @@ using NadekoBot.Services;
namespace NadekoBot.Modules.Utility
{
[NadekoModule("Utility", ".")]
public partial class Utility : DiscordModule
public partial class Utility : NadekoModule
{
private static ConcurrentDictionary<ulong, Timer> rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();
@ -468,7 +468,6 @@ namespace NadekoBot.Modules.Utility
[OwnerOnly]
public async Task SaveChat(int cnt)
{
var sb = new StringBuilder();
var msgs = new List<IMessage>(cnt);
await Context.Channel.GetMessagesAsync(cnt).ForEachAsync(dled => msgs.AddRange(dled)).ConfigureAwait(false);

View File

@ -14,8 +14,12 @@ using System.Collections.Generic;
using NadekoBot.Modules.Permissions;
using NadekoBot.TypeReaders;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics;
using NadekoBot.Modules.Music;
using NadekoBot.Services.Database.Models;
using System.Resources;
using NadekoBot.Resources;
namespace NadekoBot
{
@ -29,7 +33,10 @@ namespace NadekoBot
public static CommandService CommandService { get; private set; }
public static CommandHandler CommandHandler { get; private set; }
public static DiscordShardedClient Client { get; private set; }
public static BotCredentials Credentials { get; private set; }
public static BotCredentials Credentials { get; }
public static Localization Localization { get; private set; }
public static ResourceManager ResponsesResourceManager { get; } = new ResourceManager(typeof(ResponseStrings));
public static GoogleApiService Google { get; private set; }
public static StatsService Stats { get; private set; }
@ -38,7 +45,7 @@ namespace NadekoBot
public static ConcurrentDictionary<string, string> ModulePrefixes { get; private set; }
public static bool Ready { get; private set; }
public static IEnumerable<GuildConfig> AllGuildConfigs { get; }
public static ImmutableArray<GuildConfig> AllGuildConfigs { get; }
public static BotConfig BotConfig { get; }
static NadekoBot()
@ -48,7 +55,7 @@ namespace NadekoBot
using (var uow = DbHandler.UnitOfWork())
{
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs();
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs().ToImmutableArray();
BotConfig = uow.BotConfig.GetOrCreate();
OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16));
ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16));
@ -79,6 +86,7 @@ namespace NadekoBot
#endif
//initialize Services
Localization = new Localization(NadekoBot.BotConfig.Locale, NadekoBot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale));
CommandService = new CommandService(new CommandServiceConfig() {
CaseSensitiveCommands = false,
DefaultRunMode = RunMode.Sync
@ -102,13 +110,16 @@ namespace NadekoBot
CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader());
CommandService.AddTypeReader<IGuild>(new GuildTypeReader());
var sw = Stopwatch.StartNew();
//connect
await Client.LoginAsync(TokenType.Bot, Credentials.Token).ConfigureAwait(false);
await Client.ConnectAsync().ConfigureAwait(false);
//await Client.DownloadAllUsersAsync().ConfigureAwait(false);
Stats.Initialize();
_log.Info("Connected");
sw.Stop();
_log.Info("Connected in " + sw.Elapsed.TotalSeconds.ToString("F2"));
//load commands and prefixes

View File

@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cadministration_005Ccommands/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cgambling_005Ccommands/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -3704,6 +3704,87 @@ namespace NadekoBot.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to languageset langset.
/// </summary>
public static string languageset_cmd {
get {
return ResourceManager.GetString("languageset_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Sets this server&apos;s response language If bot&apos;s response strings have been translated to that language, bot will use that language in this server. Reset by using `default` as the locale name. Provide no arguments to see currently set language..
/// </summary>
public static string languageset_desc {
get {
return ResourceManager.GetString("languageset_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}langset de-DE ` or `{0}langset default`.
/// </summary>
public static string languageset_usage {
get {
return ResourceManager.GetString("languageset_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to langsetdefault langsetd.
/// </summary>
public static string languagesetdefault_cmd {
get {
return ResourceManager.GetString("languagesetdefault_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Sets the bot&apos;s default response language. All servers which use a default locale will use this one. Setting to `default` will use the host&apos;s current culture. Provide no arguments to see currently set language..
/// </summary>
public static string languagesetdefault_desc {
get {
return ResourceManager.GetString("languagesetdefault_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}langsetd en-US` or `{0}langsetd default`.
/// </summary>
public static string languagesetdefault_usage {
get {
return ResourceManager.GetString("languagesetdefault_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to languageslist langli.
/// </summary>
public static string languageslist_cmd {
get {
return ResourceManager.GetString("languageslist_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to List of languages for which translation (or part of it) exist atm..
/// </summary>
public static string languageslist_desc {
get {
return ResourceManager.GetString("languageslist_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}langli`.
/// </summary>
public static string languageslist_usage {
get {
return ResourceManager.GetString("languageslist_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to lcsc.
/// </summary>
@ -8988,7 +9069,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to {0}yodify I was once an adventurer like you` or `{0}yoda my feelings hurt`.
/// Looks up a localized string similar to `{0}yoda my feelings hurt`.
/// </summary>
public static string yodify_usage {
get {

View File

@ -2698,7 +2698,7 @@
<value>Translates your normal sentences into Yoda styled sentences!</value>
</data>
<data name="yodify_usage" xml:space="preserve">
<value>{0}yodify I was once an adventurer like you` or `{0}yoda my feelings hurt`</value>
<value>`{0}yoda my feelings hurt`</value>
</data>
<data name="attack_cmd" xml:space="preserve">
<value>attack</value>
@ -3114,4 +3114,35 @@
<data name="timezone_usage" xml:space="preserve">
<value>`{0}timezone`</value>
</data>
<<<<<<< HEAD
</root>
=======
<data name="languagesetdefault_cmd" xml:space="preserve">
<value>langsetdefault langsetd</value>
</data>
<data name="languagesetdefault_desc" xml:space="preserve">
<value>Sets the bot's default response language. All servers which use a default locale will use this one. Setting to `default` will use the host's current culture. Provide no arguments to see currently set language.</value>
</data>
<data name="languagesetdefault_usage" xml:space="preserve">
<value>`{0}langsetd en-US` or `{0}langsetd default`</value>
</data>
<data name="languageset_cmd" xml:space="preserve">
<value>languageset langset</value>
</data>
<data name="languageset_desc" xml:space="preserve">
<value>Sets this server's response language If bot's response strings have been translated to that language, bot will use that language in this server. Reset by using `default` as the locale name. Provide no arguments to see currently set language.</value>
</data>
<data name="languageset_usage" xml:space="preserve">
<value>`{0}langset de-DE ` or `{0}langset default`</value>
</data>
<data name="languageslist_cmd" xml:space="preserve">
<value>languageslist langli</value>
</data>
<data name="languageslist_desc" xml:space="preserve">
<value>List of languages for which translation (or part of it) exist atm.</value>
</data>
<data name="languageslist_usage" xml:space="preserve">
<value>`{0}langli`</value>
</data>
</root>
>>>>>>> Kwoth/dev

File diff suppressed because it is too large Load Diff

View File

@ -117,4 +117,930 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="clashofclans_base_already_claimed" xml:space="preserve">
<value>That base is already claimed or destroyed.</value>
</data>
<data name="clashofclans_base_already_destroyed" xml:space="preserve">
<value>That base is already destroyed.</value>
</data>
<data name="clashofclans_base_already_unclaimed" xml:space="preserve">
<value>That base is not claimed.</value>
</data>
<data name="clashofclans_base_destroyed" xml:space="preserve">
<value>**DESTROYED** base #{0} in a war against {1}</value>
</data>
<data name="clashofclans_base_unclaimed" xml:space="preserve">
<value>{0} has **UNCLAIMED** base #{1} in a war against {2}</value>
</data>
<data name="clashofclans_claimed_base" xml:space="preserve">
<value>{0} claimed a base #{1} in a war against {2}</value>
</data>
<data name="clashofclans_claimed_other" xml:space="preserve">
<value>@{0} You already claimed base #{1}. You can't claim a new one.</value>
</data>
<data name="clashofclans_claim_expired" xml:space="preserve">
<value>Claim from @{0} for a war against {1} has expired.</value>
</data>
<data name="clashofclans_enemy" xml:space="preserve">
<value>Enemy</value>
</data>
<data name="clashofclans_info_about_war" xml:space="preserve">
<value>Info about war against {0}</value>
</data>
<data name="clashofclans_invalid_base_number" xml:space="preserve">
<value>Invalid base number.</value>
</data>
<data name="clashofclans_invalid_size" xml:space="preserve">
<value>Not a Valid war size.</value>
</data>
<data name="clashofclans_list_active_wars" xml:space="preserve">
<value>List Of Active Wars</value>
</data>
<data name="clashofclans_not_claimed" xml:space="preserve">
<value>not claimed</value>
</data>
<data name="clashofclans_not_partic" xml:space="preserve">
<value>You are not participating in that war.</value>
</data>
<data name="clashofclans_not_partic_or_destroyed" xml:space="preserve">
<value>@{0} You are either not participating in that war, or that base is already destroyed.</value>
</data>
<data name="clashofclans_no_active_wars" xml:space="preserve">
<value>No active wars.</value>
</data>
<data name="clashofclans_size" xml:space="preserve">
<value>Size</value>
</data>
<data name="clashofclans_war_already_started" xml:space="preserve">
<value>War against {0} has already started.</value>
</data>
<data name="clashofclans_war_created" xml:space="preserve">
<value>War against {0} created.</value>
</data>
<data name="clashofclans_war_ended" xml:space="preserve">
<value>War against {0} ended.</value>
</data>
<data name="clashofclans_war_not_exist" xml:space="preserve">
<value>That war does not exist.</value>
</data>
<data name="clashofclans_war_started" xml:space="preserve">
<value>War against {0} started!</value>
</data>
<data name="customreactions_all_stats_cleared" xml:space="preserve">
<value>All custom reaction stats cleared.</value>
</data>
<data name="customreactions_deleted" xml:space="preserve">
<value>Custom Reaction deleted</value>
</data>
<data name="customreactions_insuff_perms" xml:space="preserve">
<value>Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for server custom reactions.</value>
</data>
<data name="customreactions_list_all" xml:space="preserve">
<value>List of all custom reactions</value>
</data>
<data name="customreactions_name" xml:space="preserve">
<value>Custom Reactions</value>
</data>
<data name="customreactions_new_cust_react" xml:space="preserve">
<value>New Custom Reaction</value>
</data>
<data name="customreactions_no_found" xml:space="preserve">
<value>No custom reactions found.</value>
</data>
<data name="customreactions_no_found_id" xml:space="preserve">
<value>No custom reaction found with that id.</value>
</data>
<data name="customreactions_response" xml:space="preserve">
<value>Response</value>
</data>
<data name="customreactions_stats" xml:space="preserve">
<value>Custom Reaction Stats</value>
</data>
<data name="customreactions_stats_cleared" xml:space="preserve">
<value>Stats cleared for {0} custom reaction.</value>
</data>
<data name="customreactions_stats_not_found" xml:space="preserve">
<value>No stats for that trigger found, no action taken.</value>
</data>
<data name="customreactions_trigger" xml:space="preserve">
<value>Trigger</value>
</data>
<data name="nsfw_autohentai_stopped" xml:space="preserve">
<value>Autohentai stopped.</value>
</data>
<data name="nsfw_not_found" xml:space="preserve">
<value>No results found.</value>
</data>
<data name="pokemon_already_fainted" xml:space="preserve">
<value>{0} has already fainted.</value>
</data>
<data name="pokemon_already_full" xml:space="preserve">
<value>{0} already has full HP.</value>
</data>
<data name="pokemon_already_that_type" xml:space="preserve">
<value>Your type is already {0}</value>
</data>
<data name="pokemon_attack" xml:space="preserve">
<value>used {0}{1} on {2}{3} for {4} damage.</value>
<comment>Kwoth used punch:type_icon: on Sanity:type_icon: for 50 damage.</comment>
</data>
<data name="pokemon_cant_attack_again" xml:space="preserve">
<value>You can't attack again without retaliation!</value>
</data>
<data name="pokemon_cant_attack_yourself" xml:space="preserve">
<value>You can't attack yourself.</value>
</data>
<data name="pokemon_fainted" xml:space="preserve">
<value>{0} has fainted!</value>
</data>
<data name="pokemon_healed" xml:space="preserve">
<value>healed {0} with one {1}</value>
</data>
<data name="pokemon_hp_remaining" xml:space="preserve">
<value>{0} has {1} HP remaining.</value>
</data>
<data name="pokemon_invalid_move" xml:space="preserve">
<value>You can't use {0}. Type `{1}ml` to see a list of moves you can use.</value>
</data>
<data name="pokemon_moves" xml:space="preserve">
<value>Movelist for {0} type</value>
</data>
<data name="pokemon_not_effective" xml:space="preserve">
<value>It's not effective.</value>
</data>
<data name="pokemon_no_currency" xml:space="preserve">
<value>You don't have enough {0}</value>
</data>
<data name="pokemon_revive_other" xml:space="preserve">
<value>revived {0} with one {1}</value>
</data>
<data name="pokemon_revive_yourself" xml:space="preserve">
<value>You revived yourself with one {0}</value>
</data>
<data name="pokemon_settype_success" xml:space="preserve">
<value>Your type has been changed to {0} for a {1}</value>
</data>
<data name="pokemon_somewhat_effective" xml:space="preserve">
<value>It's somewhat effective.</value>
</data>
<data name="pokemon_super_effective" xml:space="preserve">
<value>It's super effective!</value>
</data>
<data name="pokemon_too_many_moves" xml:space="preserve">
<value>You used too many moves in a row, so you can't move!</value>
</data>
<data name="pokemon_type_of_user" xml:space="preserve">
<value>Type of {0} is {1}</value>
</data>
<data name="pokemon_user_not_found" xml:space="preserve">
<value>User not found.</value>
</data>
<data name="pokemon_you_fainted" xml:space="preserve">
<value>You fainted, so you are not able to move!</value>
</data>
<data name="administration_aar_disabled" xml:space="preserve">
<value>**Auto assign role** on user join is now **disabled**.</value>
</data>
<data name="administration_aar_enabled" xml:space="preserve">
<value>**Auto assign role** on user join is now **enabled**.</value>
</data>
<data name="administration_attachments" xml:space="preserve">
<value>Attachments</value>
</data>
<data name="administration_avatar_changed" xml:space="preserve">
<value>Avatar Changed</value>
</data>
<data name="administration_bandm" xml:space="preserve">
<value>You have been banned from {0} server.
Reason: {1}</value>
</data>
<data name="administration_banned_pl" xml:space="preserve">
<value>banned</value>
<comment>PLURAL</comment>
</data>
<data name="administration_banned_user" xml:space="preserve">
<value>User Banned</value>
</data>
<data name="administration_bot_name" xml:space="preserve">
<value>Bot name changed to {0}</value>
</data>
<data name="administration_bot_status" xml:space="preserve">
<value>Bot status changed to {0}</value>
</data>
<data name="administration_byedel_off" xml:space="preserve">
<value>Automatic deletion of bye messages has been disabled.</value>
</data>
<data name="administration_byedel_on" xml:space="preserve">
<value>Bye messages will be deleted after {0} seconds.</value>
</data>
<data name="administration_byemsg_cur" xml:space="preserve">
<value>Current bye message: {0}</value>
</data>
<data name="administration_byemsg_enable" xml:space="preserve">
<value>Enable bye messages by typing {0}</value>
</data>
<data name="administration_byemsg_new" xml:space="preserve">
<value>New bye message set.</value>
</data>
<data name="administration_bye_off" xml:space="preserve">
<value>Bye announcements disabled.</value>
</data>
<data name="administration_bye_on" xml:space="preserve">
<value>Bye announcements enabled on this channel.</value>
</data>
<data name="administration_ch_name_change" xml:space="preserve">
<value>Channel Name Changed</value>
</data>
<data name="administration_ch_old_name" xml:space="preserve">
<value>Old Name</value>
</data>
<data name="administration_ch_topic_change" xml:space="preserve">
<value>Channel Topic Changed</value>
</data>
<data name="administration_cleaned_up" xml:space="preserve">
<value>Cleaned up.</value>
</data>
<data name="administration_content" xml:space="preserve">
<value>Content</value>
</data>
<data name="administration_cr" xml:space="preserve">
<value>Sucessfully created role {0}</value>
</data>
<data name="administration_createtextchan" xml:space="preserve">
<value>Text channel {0} created.</value>
</data>
<data name="administration_createvoich" xml:space="preserve">
<value>Voice channel {0} created.</value>
</data>
<data name="administration_deafen" xml:space="preserve">
<value>Deafen successful.</value>
</data>
<data name="administration_deleted_server" xml:space="preserve">
<value>Deleted server {0}</value>
</data>
<data name="administration_delmsg_off" xml:space="preserve">
<value>Stopped automatic deletion of successful command invokations.</value>
</data>
<data name="administration_delmsg_on" xml:space="preserve">
<value>Now automatically deleting sucessful command invokations.</value>
</data>
<data name="administration_deltextchan" xml:space="preserve">
<value>Text channel {0} deleted.</value>
</data>
<data name="administration_delvoich" xml:space="preserve">
<value>Voice channel {0} deleted.</value>
</data>
<data name="administration_dm_from" xml:space="preserve">
<value>DM from</value>
</data>
<data name="administration_donadd" xml:space="preserve">
<value>Sucessfully added a new donator.Total donated amount from this user: {0} 👑</value>
</data>
<data name="administration_donators" xml:space="preserve">
<value>Thanks to the people listed below for making this project hjappen!</value>
</data>
<data name="administration_fwall_start" xml:space="preserve">
<value>I will forward DMs to all owners.</value>
</data>
<data name="administration_fwall_stop" xml:space="preserve">
<value>I will forward DMs only to the first owner.</value>
</data>
<data name="administration_fwdm_start" xml:space="preserve">
<value>I will forward DMs from now on.</value>
</data>
<data name="administration_fwdm_stop" xml:space="preserve">
<value>I will stop forwarding DMs from now on.</value>
</data>
<data name="administration_greetdel_off" xml:space="preserve">
<value>Automatic deletion of greet messages has been disabled.</value>
</data>
<data name="administration_greetdel_on" xml:space="preserve">
<value>Greet messages will be deleted after {0} seconds.</value>
</data>
<data name="administration_greetdmmsg_cur" xml:space="preserve">
<value>Current DM greet message: {0}</value>
</data>
<data name="administration_greetdmmsg_enable" xml:space="preserve">
<value>Enable DM greet messages by typing {0}</value>
</data>
<data name="administration_greetdmmsg_new" xml:space="preserve">
<value>New DM greet message set.</value>
</data>
<data name="administration_greetdm_off" xml:space="preserve">
<value>DM greet announcements disabled.</value>
</data>
<data name="administration_greetdm_on" xml:space="preserve">
<value>DM greet announcements enabled.</value>
</data>
<data name="administration_greetmsg_cur" xml:space="preserve">
<value>Current greet message: {0}</value>
</data>
<data name="administration_greetmsg_enable" xml:space="preserve">
<value>Enable greet messages by typing {0}</value>
</data>
<data name="administration_greetmsg_new" xml:space="preserve">
<value>New greet message set.</value>
</data>
<data name="administration_greet_off" xml:space="preserve">
<value>Greet announcements disabled.</value>
</data>
<data name="administration_greet_on" xml:space="preserve">
<value>Greet announcements enabled on this channel.</value>
</data>
<data name="administration_hierarchy" xml:space="preserve">
<value>You can't use this command on users with a role higher or equal to yours in the role hierarchy.</value>
</data>
<data name="administration_images_loaded" xml:space="preserve">
<value>Images loaded after {0} seconds!</value>
</data>
<data name="administration_invalid_format" xml:space="preserve">
<value>Invalid input format.</value>
</data>
<data name="administration_invalid_params" xml:space="preserve">
<value>Invalid parameters.</value>
</data>
<data name="administration_joined" xml:space="preserve">
<value>{0} has joined {1}</value>
</data>
<data name="administration_kickdm" xml:space="preserve">
<value>You have been kicked from {0} server.
Reason: {1}</value>
</data>
<data name="administration_kicked_user" xml:space="preserve">
<value>User Kicked</value>
</data>
<data name="administration_lang_list" xml:space="preserve">
<value>List Of Languages
{0}</value>
</data>
<data name="administration_lang_set" xml:space="preserve">
<value>Your server's locale is now {0} - {1}</value>
</data>
<data name="administration_lang_set_bot" xml:space="preserve">
<value>Bot's default locale is now {0} - {1}</value>
</data>
<data name="administration_lang_set_bot_show" xml:space="preserve">
<value>Bot's language is set to {0} - {0}</value>
</data>
<data name="administration_lang_set_fail" xml:space="preserve">
<value>Failed setting locale. Revisit this command's help.</value>
</data>
<data name="administration_lang_set_show" xml:space="preserve">
<value>This server's language is set to {0} - {0}</value>
</data>
<data name="administration_left" xml:space="preserve">
<value>{0} has left {1}</value>
</data>
<data name="administration_left_server" xml:space="preserve">
<value>Left server {0}</value>
</data>
<data name="administration_log" xml:space="preserve">
<value>Logging {0} event in this channel.</value>
</data>
<data name="administration_log_all" xml:space="preserve">
<value>Logging all events in this channel.</value>
</data>
<data name="administration_log_disabled" xml:space="preserve">
<value>Logging disabled.</value>
</data>
<data name="administration_log_events" xml:space="preserve">
<value>Log events you can subscribe to:</value>
</data>
<data name="administration_log_ignore" xml:space="preserve">
<value>Logging will ignore {0}</value>
</data>
<data name="administration_log_not_ignore" xml:space="preserve">
<value>Logging will not ignore {0}</value>
</data>
<data name="administration_log_stop" xml:space="preserve">
<value>Stopped logging {0} event.</value>
</data>
<data name="administration_menrole" xml:space="preserve">
<value>{0} has invoked a mention on the following roles</value>
</data>
<data name="administration_message_from_bo" xml:space="preserve">
<value>Message from {0} `[Bot Owner]`:</value>
</data>
<data name="administration_message_sent" xml:space="preserve">
<value>Message sent.</value>
</data>
<data name="administration_moved" xml:space="preserve">
<value>{0} moved from {1} to {2}</value>
</data>
<data name="administration_msg_del" xml:space="preserve">
<value>Message Deleted in #{0}</value>
</data>
<data name="administration_msg_update" xml:space="preserve">
<value>Message Updated in #{0}</value>
</data>
<data name="administration_muted_pl" xml:space="preserve">
<value>Muted</value>
<comment>PLURAL (users have been muted)</comment>
</data>
<data name="administration_muted_sn" xml:space="preserve">
<value>Muted</value>
<comment>singular "User muted."</comment>
</data>
<data name="administration_mute_error" xml:space="preserve">
<value>I don't have the permission necessary for that most likely.</value>
</data>
<data name="administration_mute_role_set" xml:space="preserve">
<value>New mute role set.</value>
</data>
<data name="administration_need_admin" xml:space="preserve">
<value>I need **Administration** permission to do that.</value>
</data>
<data name="administration_new_msg" xml:space="preserve">
<value>New Message</value>
</data>
<data name="administration_new_nick" xml:space="preserve">
<value>New Nickname</value>
</data>
<data name="administration_new_topic" xml:space="preserve">
<value>New Topic</value>
</data>
<data name="administration_nick_change" xml:space="preserve">
<value>Nickname Changed</value>
</data>
<data name="administration_no_server" xml:space="preserve">
<value>Can't find that server</value>
</data>
<data name="administration_no_shard_id" xml:space="preserve">
<value>No shard with that ID found.</value>
</data>
<data name="administration_old_msg" xml:space="preserve">
<value>Old Message</value>
</data>
<data name="administration_old_nick" xml:space="preserve">
<value>Old Nickname</value>
</data>
<data name="administration_old_topic" xml:space="preserve">
<value>Old Topic</value>
</data>
<data name="administration_perms" xml:space="preserve">
<value>Error. Most likely I don't have sufficient permissions.</value>
</data>
<data name="administration_perms_reset" xml:space="preserve">
<value>Permissions for this server are reset.</value>
</data>
<data name="administration_prot_active" xml:space="preserve">
<value>Active Protections</value>
</data>
<data name="administration_prot_disable" xml:space="preserve">
<value>{0} has been **disabled** on this server.</value>
</data>
<data name="administration_prot_enable" xml:space="preserve">
<value>{0} Enabled</value>
</data>
<data name="administration_prot_error" xml:space="preserve">
<value>Error. I need ManageRoles permission</value>
</data>
<data name="administration_prot_none" xml:space="preserve">
<value>No protections enabled.</value>
</data>
<data name="administration_raid_cnt" xml:space="preserve">
<value>User threshold must be between {0} and {1}.</value>
</data>
<data name="administration_raid_stats" xml:space="preserve">
<value>If {0} or more users join within {1} seconds, I will {2} them.</value>
</data>
<data name="administration_raid_time" xml:space="preserve">
<value>Time must be between {0} and {1} seconds.</value>
</data>
<data name="administration_rar" xml:space="preserve">
<value>Successfully removed all roles from user {0}</value>
</data>
<data name="administration_rar_err" xml:space="preserve">
<value>Failed to remove roles. I have insufficient permissions.</value>
</data>
<data name="administration_rc" xml:space="preserve">
<value>Color of {0} role has been changed.</value>
</data>
<data name="administration_rc_not_exist" xml:space="preserve">
<value>That role does not exist.</value>
</data>
<data name="administration_rc_params" xml:space="preserve">
<value>The parameters specified are invalid.</value>
</data>
<data name="administration_rc_perms" xml:space="preserve">
<value>Error occured due to invalid color or insufficient permissions.</value>
</data>
<data name="administration_remrole" xml:space="preserve">
<value>Successfully removed role {0} from user {1}</value>
</data>
<data name="administration_remrole_err" xml:space="preserve">
<value>Failed to remove role. I have insufficient permissions.</value>
</data>
<data name="administration_renrole" xml:space="preserve">
<value>Role renamed.</value>
</data>
<data name="administration_renrole_err" xml:space="preserve">
<value>Failed to rename role. I have insufficient permissions.</value>
</data>
<data name="administration_renrole_perms" xml:space="preserve">
<value>You can't edit roles higher than your highest role.</value>
</data>
<data name="administration_reprm" xml:space="preserve">
<value>Removed the playing message: {0}</value>
</data>
<data name="administration_role_added" xml:space="preserve">
<value>Role {0} as been added to the list.</value>
</data>
<data name="administration_role_clean" xml:space="preserve">
<value>{0} not found.Cleaned up.</value>
</data>
<data name="administration_role_in_list" xml:space="preserve">
<value>Role {0} is already in the list.</value>
</data>
<data name="administration_ropl_added" xml:space="preserve">
<value>Added.</value>
</data>
<data name="administration_ropl_disabled" xml:space="preserve">
<value>Rotating playing status disabled.</value>
</data>
<data name="administration_ropl_enabled" xml:space="preserve">
<value>Rotating playing status enabled.</value>
</data>
<data name="administration_ropl_list" xml:space="preserve">
<value>Here is a list of rotating statuses:
{0}</value>
</data>
<data name="administration_ropl_not_set" xml:space="preserve">
<value>No rotating playing statuses set.</value>
</data>
<data name="administration_self_assign_already" xml:space="preserve">
<value>You already have {0} role.</value>
</data>
<data name="administration_self_assign_already_excl" xml:space="preserve">
<value>You already have {0} exclusive self-assigned role.</value>
</data>
<data name="administration_self_assign_excl" xml:space="preserve">
<value>Self assigned roles are now exclusive!</value>
</data>
<data name="administration_self_assign_list" xml:space="preserve">
<value>There are {0} self assignable roles</value>
</data>
<data name="administration_self_assign_not" xml:space="preserve">
<value>That role is not self-assignable.</value>
</data>
<data name="administration_self_assign_not_have" xml:space="preserve">
<value>You don't have {0} role.</value>
</data>
<data name="administration_self_assign_no_excl" xml:space="preserve">
<value>Self assigned roles are now not exclusive!</value>
</data>
<data name="administration_self_assign_perms" xml:space="preserve">
<value>I am unable to add that role to you. `I can't add roles to owners or other roles higher than my role in the role hierarchy.`</value>
</data>
<data name="administration_self_assign_rem" xml:space="preserve">
<value>{0} has been removed from the list of self-assignable roles.</value>
</data>
<data name="administration_self_assign_remove" xml:space="preserve">
<value>You no longer have {0} role.</value>
</data>
<data name="administration_self_assign_sucess" xml:space="preserve">
<value>You now have {0} role.</value>
</data>
<data name="administration_setrole" xml:space="preserve">
<value>Sucessfully added role {0} to user {1}</value>
</data>
<data name="administration_setrole_err" xml:space="preserve">
<value>Failed to add role. I have insufficient permissions.</value>
</data>
<data name="administration_set_avatar" xml:space="preserve">
<value>New avatar set!</value>
</data>
<data name="administration_set_channel_name" xml:space="preserve">
<value>New channel name set.</value>
</data>
<data name="administration_set_game" xml:space="preserve">
<value>New game set!</value>
</data>
<data name="administration_set_stream" xml:space="preserve">
<value>New stream set!</value>
</data>
<data name="administration_set_topic" xml:space="preserve">
<value>New channel topic set.</value>
</data>
<data name="administration_shard_reconnected" xml:space="preserve">
<value>Shard {0} reconnected.</value>
</data>
<data name="administration_shard_reconnecting" xml:space="preserve">
<value>Shard {0} reconnecting.</value>
</data>
<data name="administration_shutting_down" xml:space="preserve">
<value>Shutting down</value>
</data>
<data name="administration_slowmode_desc" xml:space="preserve">
<value>Users can't send more than {0} messages every {1} seconds.</value>
</data>
<data name="administration_slowmode_disabled" xml:space="preserve">
<value>Slow mode disabled.</value>
</data>
<data name="administration_slowmode_init" xml:space="preserve">
<value>Slow mode initiated</value>
</data>
<data name="administration_soft_banned_pl" xml:space="preserve">
<value>soft-banned (kicked)</value>
<comment>PLURAL</comment>
</data>
<data name="administration_spam_ignore" xml:space="preserve">
<value>{0} will ignore this channel.</value>
</data>
<data name="administration_spam_not_ignore" xml:space="preserve">
<value>{0} will no longer ignore this channel.</value>
</data>
<data name="administration_spam_stats" xml:space="preserve">
<value>If a user posts {0} same messages in a row, I will {1} them.
__IgnoredChannels__: {2}</value>
</data>
<data name="administration_text_chan_created" xml:space="preserve">
<value>Text Channel Destroyed </value>
</data>
<data name="administration_text_chan_destroyed" xml:space="preserve">
<value>Text Channel Destroyed </value>
</data>
<data name="administration_undeafen" xml:space="preserve">
<value>Undeafen successful.</value>
</data>
<data name="administration_unmuted_sn" xml:space="preserve">
<value>Unmuted</value>
<comment>singular</comment>
</data>
<data name="administration_username" xml:space="preserve">
<value>Username</value>
</data>
<data name="administration_username_changed" xml:space="preserve">
<value>Username Changed</value>
</data>
<data name="administration_users" xml:space="preserve">
<value>Users</value>
</data>
<data name="administration_user_banned" xml:space="preserve">
<value>User Banned</value>
</data>
<data name="administration_user_chat_mute" xml:space="preserve">
<value>{0} has been **muted** from chatting.</value>
</data>
<data name="administration_user_chat_unmute" xml:space="preserve">
<value>{0} has been **unmuted** from chatting.</value>
</data>
<data name="administration_user_joined" xml:space="preserve">
<value>User Joined</value>
</data>
<data name="administration_user_left" xml:space="preserve">
<value>User Left</value>
</data>
<data name="administration_user_muted" xml:space="preserve">
<value>{0} has been **muted** from text and voice chat.</value>
</data>
<data name="administration_user_role_add" xml:space="preserve">
<value>User's Role Added</value>
</data>
<data name="administration_user_role_rem" xml:space="preserve">
<value>User's Role Removed</value>
</data>
<data name="administration_user_status_change" xml:space="preserve">
<value>{0} is now {1}</value>
</data>
<data name="administration_user_unmuted" xml:space="preserve">
<value>{0} has been **unmuted** from text and voice chat.</value>
</data>
<data name="administration_user_vjoined" xml:space="preserve">
<value>{0} has joined {1} voice channel.</value>
</data>
<data name="administration_user_vleft" xml:space="preserve">
<value>{0} has left {1} voice channel.</value>
</data>
<data name="administration_user_vmoved" xml:space="preserve">
<value>{0} moved from {1} to {2} voice channel.</value>
</data>
<data name="administration_user_voice_mute" xml:space="preserve">
<value>{0} has been **voice muted**.</value>
</data>
<data name="administration_user_voice_unmute" xml:space="preserve">
<value>{0} has been **voice unmuted**.</value>
</data>
<data name="administration_voice_chan_created" xml:space="preserve">
<value>Voice Channel Destroyed</value>
</data>
<data name="administration_voice_chan_destroyed" xml:space="preserve">
<value>Voice Channel Destroyed</value>
</data>
<data name="administration_vt_disabled" xml:space="preserve">
<value>Disabled voice + text feature.</value>
</data>
<data name="administration_vt_enabled" xml:space="preserve">
<value>Enabled voice + text feature.</value>
</data>
<data name="administration_vt_exit" xml:space="preserve">
<value>I don't have **manage roles** and/or **manage channels** permission, so I cannot run `voice+text` on {0} server.</value>
</data>
<data name="administration_vt_no_admin" xml:space="preserve">
<value>You are enabling/disabling this feature and **I do not have ADMINISTRATOR permissions**. This may cause some issues, and you will have to clean up text channels yourself afterwards.</value>
</data>
<data name="administration_vt_perms" xml:space="preserve">
<value>I require atleast **manage roles** and **manage channels** permissions to enable this feature. (preffered Administration permission)</value>
</data>
<data name="administration_xmuted_text" xml:space="preserve">
<value>User {0} from text chat</value>
</data>
<data name="administration_xmuted_text_and_voice" xml:space="preserve">
<value>User {0} from text and voice chat</value>
</data>
<data name="administration_xmuted_voice" xml:space="preserve">
<value>User {0} from voice chat</value>
</data>
<data name="administraton_sbdm" xml:space="preserve">
<value>You have been soft-banned from {0} server.
Reason: {1}</value>
</data>
<data name="administraton_user_unbanned" xml:space="preserve">
<value>User Unbanned</value>
</data>
<data name="adminsitration_migration_done" xml:space="preserve">
<value>Migration done!</value>
</data>
<data name="adminsitration_migration_error" xml:space="preserve">
<value>Error while migrating, check bot's console for more information.</value>
</data>
<data name="adminsitration_presence_updates" xml:space="preserve">
<value>Presence Updates</value>
</data>
<data name="adminsitration_sb_user" xml:space="preserve">
<value>User Soft-Banned</value>
</data>
<data name="gambling_awarded" xml:space="preserve">
<value>has awarded {0} to {1}</value>
</data>
<data name="gambling_betflip_gamble" xml:space="preserve">
<value>Betflip Gamble</value>
</data>
<data name="gambling_better_luck" xml:space="preserve">
<value>Better luck next time ^_^</value>
</data>
<data name="gambling_br_win" xml:space="preserve">
<value>Congratulations! You won {0} for rolling above {1}</value>
</data>
<data name="gambling_deck_reshuffled" xml:space="preserve">
<value>Deck reshuffled.</value>
</data>
<data name="gambling_flipped" xml:space="preserve">
<value>flipped {0}.</value>
<comment>User flipped tails.</comment>
</data>
<data name="gambling_flip_guess" xml:space="preserve">
<value>You guessed it! You won {0}</value>
</data>
<data name="gambling_flip_invalid" xml:space="preserve">
<value>Invalid number specified. You can flip 1 to {0} coins.</value>
</data>
<data name="gambling_flowerreaction_desc" xml:space="preserve">
<value>Add {0} reaction to this message to get {1} </value>
</data>
<data name="gambling_flowerreaction_footer" xml:space="preserve">
<value>This event is active for up to {0} hours.</value>
</data>
<data name="gambling_flowerreaction_title" xml:space="preserve">
<value>Flower reaction event started!</value>
</data>
<data name="gambling_gifted" xml:space="preserve">
<value>has gifted {0} to {1}</value>
<comment>X has gifted 15 flowers to Y</comment>
</data>
<data name="gambling_has" xml:space="preserve">
<value>{0} has {1}</value>
<comment>X has Y flowers</comment>
</data>
<data name="gambling_heads" xml:space="preserve">
<value>Heads</value>
</data>
<data name="gambling_leaderboard" xml:space="preserve">
<value>Leaderboard</value>
</data>
<data name="gambling_mass_award" xml:space="preserve">
<value>Awarded {0} to {1} users from {2} role.</value>
</data>
<data name="gambling_max_bet_limit" xml:space="preserve">
<value>You can't bet more than {0}</value>
</data>
<data name="gambling_min_bet_limit" xml:space="preserve">
<value>You can't bet less than {0}</value>
</data>
<data name="gambling_not_enough" xml:space="preserve">
<value>You don't have enough {0}</value>
</data>
<data name="gambling_no_more_cards" xml:space="preserve">
<value>No more cards in the deck.</value>
</data>
<data name="gambling_raffled_user" xml:space="preserve">
<value>Raffled User</value>
</data>
<data name="gambling_roll" xml:space="preserve">
<value>You rolled {0}.</value>
</data>
<data name="gambling_slot_bet" xml:space="preserve">
<value>Bet</value>
</data>
<data name="gambling_slot_jackpot" xml:space="preserve">
<value>WOAAHHHHHH!!! Congratulations!!! x{0}</value>
</data>
<data name="gambling_slot_single" xml:space="preserve">
<value>A single {0}, x{1}</value>
</data>
<data name="gambling_slot_three" xml:space="preserve">
<value>Wow! Lucky! Three of a kind! x{0}</value>
</data>
<data name="gambling_slot_two" xml:space="preserve">
<value>Good job! Two {0} - bet x{1}</value>
</data>
<data name="gambling_slot_won" xml:space="preserve">
<value>Won</value>
</data>
<data name="gambling_sneakygamestatus_desc" xml:space="preserve">
<value>Users must type a secret code to get {0}.
Lasts {1} seconds. Don't tell anyone. Shhh.</value>
</data>
<data name="gambling_sneakygamestatus_end" xml:space="preserve">
<value>SneakyGame event ended. {0} users received the reward.</value>
</data>
<data name="gambling_sneakygamestatus_title" xml:space="preserve">
<value>SneakyGameStatus event started</value>
</data>
<data name="gambling_tails" xml:space="preserve">
<value>Tails</value>
</data>
<data name="gambling_take" xml:space="preserve">
<value>successfully took {0} from {1}</value>
</data>
<data name="gambling_take_fail" xml:space="preserve">
<value>was unable to take {0} from{1} because the user doesn't have that much {2}!</value>
</data>
<data name="help_back_to_toc" xml:space="preserve">
<value>Back to ToC</value>
</data>
<data name="help_bot_owner_only" xml:space="preserve">
<value>Bot Owner Only</value>
</data>
<data name="help_channel_permission" xml:space="preserve">
<value>Requires {0} channel permission.</value>
</data>
<data name="help_cmdlist_donate" xml:space="preserve">
<value>You can support the project on patreon: &lt;{0}&gt; or paypal: &lt;{1}&gt;</value>
</data>
<data name="help_cmd_and_alias" xml:space="preserve">
<value>Command and aliases</value>
</data>
<data name="help_commandlist_regen" xml:space="preserve">
<value>Commandlist Regenerated.</value>
</data>
<data name="help_commands_instr" xml:space="preserve">
<value>Type `{0}h CommandName` to see the help for that specified command. e.g. `{0}h &gt;8ball`</value>
</data>
<data name="help_command_not_found" xml:space="preserve">
<value>I can't find that command. Please verify that the command exists before trying again.</value>
</data>
<data name="help_desc" xml:space="preserve">
<value>Description</value>
</data>
<data name="help_donate" xml:space="preserve">
<value>You can support the NadekoBot project on
Patreon &lt;{0}&gt; or
Paypal &lt;{1}&gt;
Don't forget to leave your discord name or id in the message.
**Thank you** ♥️</value>
</data>
<data name="help_guide" xml:space="preserve">
<value>**List of Commands**: &lt;{0}&gt;
**Hosting Guides and docs can be found here**: &lt;{1}&gt;</value>
</data>
<data name="help_list_of_commands" xml:space="preserve">
<value>List Of Commands</value>
</data>
<data name="help_list_of_modules" xml:space="preserve">
<value>List Of Modules</value>
</data>
<data name="help_modules_footer" xml:space="preserve">
<value>Type `{0}cmds ModuleName` to get a list of commands in that module. eg `{0}cmds games`</value>
</data>
<data name="help_module_not_found" xml:space="preserve">
<value>That module does not exist.</value>
</data>
<data name="help_server_permission" xml:space="preserve">
<value>Requires {0} server permission.</value>
</data>
<data name="help_table_of_contents" xml:space="preserve">
<value>Table Of Contents</value>
</data>
<data name="help_usage" xml:space="preserve">
<value>Usage</value>
</data>
<data name="nsfw_autohentai_started" xml:space="preserve">
<value>Autohentai started. Reposting every {0}s with one of the following tags:
{1}</value>
</data>
<data name="nsfw_tag" xml:space="preserve">
<value>Tag</value>
</data>
</root>

View File

@ -0,0 +1,260 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace NadekoBot.Resources {
using System;
using System.Reflection;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ResponseStrings_sr_SP {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
internal ResponseStrings_sr_SP() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NadekoBot.Resources.ResponseStrings-sr-SP", typeof(ResponseStrings_sr_SP).GetTypeInfo().Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to {0} has already fainted..
/// </summary>
public static string pokemon_already_fainted {
get {
return ResourceManager.GetString("pokemon_already_fainted", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} already has full HP..
/// </summary>
public static string pokemon_already_full {
get {
return ResourceManager.GetString("pokemon_already_full", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your type is already {0}.
/// </summary>
public static string pokemon_already_that_type {
get {
return ResourceManager.GetString("pokemon_already_that_type", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to used {0}{1} on {2}{3} for {4} damage..
/// </summary>
public static string pokemon_attack {
get {
return ResourceManager.GetString("pokemon_attack", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You can&apos;t attack again without retaliation!.
/// </summary>
public static string pokemon_cant_attack_again {
get {
return ResourceManager.GetString("pokemon_cant_attack_again", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You can&apos;t attack yourself..
/// </summary>
public static string pokemon_cant_attack_yourself {
get {
return ResourceManager.GetString("pokemon_cant_attack_yourself", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} has fainted!.
/// </summary>
public static string pokemon_fainted {
get {
return ResourceManager.GetString("pokemon_fainted", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to healed {0} with one {1}.
/// </summary>
public static string pokemon_healed {
get {
return ResourceManager.GetString("pokemon_healed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} has {1} HP remaining..
/// </summary>
public static string pokemon_hp_remaining {
get {
return ResourceManager.GetString("pokemon_hp_remaining", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You can&apos;t use {0}. Type `{1}ml` to see a list of moves you can use..
/// </summary>
public static string pokemon_invalid_move {
get {
return ResourceManager.GetString("pokemon_invalid_move", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Movelist for {0} type.
/// </summary>
public static string pokemon_moves {
get {
return ResourceManager.GetString("pokemon_moves", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You don&apos;t have enough {0}.
/// </summary>
public static string pokemon_no_currency {
get {
return ResourceManager.GetString("pokemon_no_currency", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to It&apos;s not effective..
/// </summary>
public static string pokemon_not_effective {
get {
return ResourceManager.GetString("pokemon_not_effective", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to revived {0} with one {1}.
/// </summary>
public static string pokemon_revive_other {
get {
return ResourceManager.GetString("pokemon_revive_other", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You revived yourself with one {0}.
/// </summary>
public static string pokemon_revive_yourself {
get {
return ResourceManager.GetString("pokemon_revive_yourself", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your type has been changed to {0} for a {1}.
/// </summary>
public static string pokemon_settype_success {
get {
return ResourceManager.GetString("pokemon_settype_success", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to It&apos;s somewhat effective..
/// </summary>
public static string pokemon_somewhat_effective {
get {
return ResourceManager.GetString("pokemon_somewhat_effective", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to It&apos;s super effective!.
/// </summary>
public static string pokemon_super_effective {
get {
return ResourceManager.GetString("pokemon_super_effective", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You used too many moves in a row, so you can&apos;t move!.
/// </summary>
public static string pokemon_too_many_moves {
get {
return ResourceManager.GetString("pokemon_too_many_moves", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type of {0} is {1}.
/// </summary>
public static string pokemon_type_of_user {
get {
return ResourceManager.GetString("pokemon_type_of_user", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to User not found..
/// </summary>
public static string pokemon_user_not_found {
get {
return ResourceManager.GetString("pokemon_user_not_found", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You fainted, so you are not able to move!.
/// </summary>
public static string pokemon_you_fainted {
get {
return ResourceManager.GetString("pokemon_you_fainted", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,368 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="pokemon_already_fainted" xml:space="preserve">
<value>{0} је већ онесвешћен.</value>
</data>
<data name="pokemon_already_full" xml:space="preserve">
<value>{0} је већ потпуно здрав.</value>
</data>
<data name="pokemon_already_that_type" xml:space="preserve">
<value>Твој тип већ јесте {0}</value>
</data>
<data name="pokemon_attack" xml:space="preserve">
<value>је искористио {0}{1} на {2}{3} и нанео {4} штете.</value>
<comment>Kwoth used punch:type_icon: on Sanity:type_icon: for 50 damage.</comment>
</data>
<data name="pokemon_cant_attack_again" xml:space="preserve">
<value>Не можеш напасти пре узвратног ударца.</value>
</data>
<data name="pokemon_cant_attack_yourself" xml:space="preserve">
<value>Не можеш напасти себе.</value>
</data>
<data name="pokemon_fainted" xml:space="preserve">
<value>{0} се онесвестио.</value>
</data>
<data name="pokemon_healed" xml:space="preserve">
<value>је излечио {0} са једним {1}</value>
</data>
<data name="pokemon_hp_remaining" xml:space="preserve">
<value>{0} има {1} преосталог здравља.</value>
</data>
<data name="pokemon_invalid_move" xml:space="preserve">
<value>Не можеш искористити {0}. Укуцај `{1}ml` да би видео листу удараца које можеш користити.</value>
</data>
<data name="pokemon_moves" xml:space="preserve">
<value>Листа покрета за {0} тип</value>
</data>
<data name="pokemon_not_effective" xml:space="preserve">
<value>Није ефективно.</value>
</data>
<data name="pokemon_no_currency" xml:space="preserve">
<value>Немаш довољно {0}</value>
</data>
<data name="pokemon_revive_other" xml:space="preserve">
<value>је оживео {0} са једним {1}</value>
</data>
<data name="pokemon_revive_yourself" xml:space="preserve">
<value>Оживео си себе са једним {0}</value>
</data>
<data name="pokemon_settype_success" xml:space="preserve">
<value>Твој тип је промењен на {0} за један {1}</value>
</data>
<data name="pokemon_somewhat_effective" xml:space="preserve">
<value>Има неког ефекта!</value>
</data>
<data name="pokemon_super_effective" xml:space="preserve">
<value>Супер ефективно!</value>
</data>
<data name="pokemon_too_many_moves" xml:space="preserve">
<value>Користио си превише напада узастопно, не можеш да се крећеш!</value>
</data>
<data name="pokemon_type_of_user" xml:space="preserve">
<value>{0}-ов тип је {1}</value>
</data>
<data name="pokemon_user_not_found" xml:space="preserve">
<value>Корисник није нађен.</value>
</data>
<data name="pokemon_you_fainted" xml:space="preserve">
<value>Онесвешћен си, не можеш да се крећеш.</value>
</data>
<data name="clashofclans_base_already_claimed" xml:space="preserve">
<value>Та база је већ под захетвом или је уништена.</value>
</data>
<data name="clashofclans_base_already_destroyed" xml:space="preserve">
<value>Та база је већ под захтевом.</value>
</data>
<data name="clashofclans_base_already_unclaimed" xml:space="preserve">
<value>Та база није под захтевом.</value>
</data>
<data name="clashofclans_base_unclaimed" xml:space="preserve">
<value>{0} је **ПОНИШТИО ЗАХТЕВ** за базу #{1} у рату против {2}</value>
</data>
<data name="clashofclans_claimed_base" xml:space="preserve">
<value>{0} захтева базу #{1} у рату против {2}</value>
</data>
<data name="clashofclans_claimed_other" xml:space="preserve">
<value>@{0} Ти већ захтеваш базу #{1}. Не можеш захтевати још једну.</value>
</data>
<data name="clashofclans_claim_expired" xml:space="preserve">
<value>Захтев од @{0} за рат против {1} је истекао.</value>
</data>
<data name="clashofclans_enemy" xml:space="preserve">
<value>Противник</value>
</data>
<data name="clashofclans_info_about_war" xml:space="preserve">
<value>Подаци о рату против {0}</value>
</data>
<data name="clashofclans_invalid_base_number" xml:space="preserve">
<value>Број базе није валидан.</value>
</data>
<data name="clashofclans_invalid_size" xml:space="preserve">
<value>Величина рата није валидна.</value>
</data>
<data name="clashofclans_list_active_wars" xml:space="preserve">
<value>Листа Ратова У Току</value>
</data>
<data name="clashofclans_not_claimed" xml:space="preserve">
<value>нема захтева</value>
</data>
<data name="clashofclans_not_partic" xml:space="preserve">
<value>Ти не учествујеш у том рату.</value>
</data>
<data name="clashofclans_not_partic_or_destroyed" xml:space="preserve">
<value>@{0} Ти или не учествујеш у рату или је та база већ уништена.</value>
</data>
<data name="clashofclans_no_active_wars" xml:space="preserve">
<value>Нема ратова у току.</value>
</data>
<data name="clashofclans_size" xml:space="preserve">
<value>Величина</value>
</data>
<data name="clashofclans_war_already_started" xml:space="preserve">
<value>Рат против {0} је већ почео.</value>
</data>
<data name="clashofclans_war_created" xml:space="preserve">
<value>Рат против {0} је направљен.</value>
</data>
<data name="clashofclans_war_ended" xml:space="preserve">
<value>Рат против {0} је завршен.</value>
</data>
<data name="clashofclans_war_not_exist" xml:space="preserve">
<value>Тај рат не постоји.</value>
</data>
<data name="clashofclans_war_started" xml:space="preserve">
<value>Рат против {0} је започет!</value>
</data>
<data name="clasofclans_base_destroyed" xml:space="preserve">
<value>је **УНИШТИО** базу #{0} у рату против {1}</value>
</data>
<data name="customreactions_all_stats_cleared" xml:space="preserve">
<value>Сва статистика Реакција по Избору је обрисана.</value>
</data>
<data name="customreactions_deleted" xml:space="preserve">
<value>Реакција по Избору обрисана</value>
</data>
<data name="customreactions_insuff_perms" xml:space="preserve">
<value>Немате дозволу. Потребно је поседовати бота за глобалне Реакције по Избору, а Администратор за серверске Реакције по Избору.</value>
</data>
<data name="customreactions_list_all" xml:space="preserve">
<value>Листа свих реакција по избору</value>
</data>
<data name="customreactions_name" xml:space="preserve">
<value>Реакције по Избору</value>
</data>
<data name="customreactions_new_cust_react" xml:space="preserve">
<value>Нова Реакција по Избору</value>
</data>
<data name="customreactions_no_found" xml:space="preserve">
<value>Реакција по избору није нађена.</value>
</data>
<data name="customreactions_no_found_id" xml:space="preserve">
<value>Није нађена реакција са тим идентификатором.</value>
</data>
<data name="customreactions_response" xml:space="preserve">
<value>Одговор</value>
</data>
<data name="customreactions_stats" xml:space="preserve">
<value>Статистика Реакција по Избору</value>
</data>
<data name="customreactions_stats_cleared" xml:space="preserve">
<value>Статистика је обрисана за {0} реакцију по избору.</value>
</data>
<data name="customreactions_stats_not_found" xml:space="preserve">
<value>Није нађена статистика за тај окидач. Нема промене.</value>
</data>
<data name="customreactions_trigger" xml:space="preserve">
<value>Окидач</value>
</data>
<data name="help_back_to_toc" xml:space="preserve">
<value>Назад на Садржај</value>
</data>
<data name="help_bot_owner_only" xml:space="preserve">
<value>Само Власник Бота</value>
</data>
<data name="help_channel_permission" xml:space="preserve">
<value>Захтева {0} дозволу на каналу.</value>
</data>
<data name="help_cmdlist_donate" xml:space="preserve">
<value>Можете подржати пројекат на Пејтриону: &lt;{0}&gt; или Пејпалу: &lt;{1}&gt;</value>
</data>
<data name="help_cmd_and_alias" xml:space="preserve">
<value>Команде и псеудоними</value>
</data>
<data name="help_commandlist_regen" xml:space="preserve">
<value>Листа команди је обновљена.</value>
</data>
<data name="help_commands_instr" xml:space="preserve">
<value>Укуцај `{0}h ИмеКоманде` да видиш помоћ за ту команду. нпр. `{0}h &gt;8ball`</value>
</data>
<data name="help_command_not_found" xml:space="preserve">
<value>Не могу да нађем ту команду. Проверите да ли команда постоји, па покушајте поново.</value>
</data>
<data name="help_desc" xml:space="preserve">
<value>Опис</value>
</data>
<data name="help_donate" xml:space="preserve">
<value>Можете подржати НадекоБот пројекат на
Пејтриону &lt;{0}&gt; или
Пејпалу &lt;{1}&gt;
Не заборавите да оставите своје корисничко име или ИД.
**Хвала** ♥️</value>
</data>
<data name="help_guide" xml:space="preserve">
<value>**List of Commands**: &lt;{0}&gt;
**Hosting Guides and docs can be found here**: &lt;{1}&gt;</value>
</data>
<data name="help_list_of_commands" xml:space="preserve">
<value>Листа Команди</value>
</data>
<data name="help_list_of_modules" xml:space="preserve">
<value>Листа Модула</value>
</data>
<data name="help_modules_footer" xml:space="preserve">
<value>Укуцај `{0}cmds ИмеМодула` да видиш листу свих команди у том модулу. нпр `{0}cmds games`</value>
</data>
<data name="help_module_not_found" xml:space="preserve">
<value>Тај модул не постоји.</value>
</data>
<data name="help_server_permission" xml:space="preserve">
<value>Захтева {0} дозволу на серверу.</value>
</data>
<data name="help_table_of_contents" xml:space="preserve">
<value>Садржај</value>
</data>
<data name="help_usage" xml:space="preserve">
<value>Упутство</value>
</data>
<data name="nsfw_autohentai_started" xml:space="preserve">
<value>Аутохентаи започет. Постоваћу сваких {0} сек. користећи један од следећих тагова:
{1}</value>
</data>
<data name="nsfw_autohentai_stopped" xml:space="preserve">
<value>АутоХентаи заустављен.</value>
</data>
<data name="nsfw_no_results" xml:space="preserve">
<value>Нема резултата.</value>
</data>
<data name="nsfw_tag" xml:space="preserve">
<value>Таг</value>
</data>
</root>

View File

@ -30,9 +30,9 @@ namespace Services.CleverBotApi
public static ChatterBot Create(ChatterBotType type, object arg)
{
#if GLOBAL_NADEKO
var url = "http://www.cleverbot.com/webservicemin?uc=3210&botapi=nadekobot";
var url = "http://www.cleverbot.com/webservicemin?uc=777&botapi=nadekobot";
#else
var url = "http://www.cleverbot.com/webservicemin?uc=3210&botapi=chatterbotapi";
var url = "http://www.cleverbot.com/webservicemin?uc=777&botapi=chatterbotapi";
#endif
switch (type)

View File

@ -73,7 +73,7 @@ namespace NadekoBot.Services
if (!ownerChannels.Any())
_log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file.");
else
_log.Info($"Created {ownerChannels.Count} out of {NadekoBot.Credentials.OwnerIds.Length} owner message channels.");
_log.Info($"Created {ownerChannels.Count} out of {NadekoBot.Credentials.OwnerIds.Count} owner message channels.");
_client.MessageReceived += MessageReceivedHandler;
}
@ -273,7 +273,7 @@ namespace NadekoBot.Services
await msg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false);
await DMForwardCommands.HandleDMForwarding(msg, ownerChannels).ConfigureAwait(false);
await SelfCommands.HandleDmForwarding(msg, ownerChannels).ConfigureAwait(false);
}
}
}

View File

@ -60,6 +60,7 @@ Nadeko Support Server: https://discord.gg/nadekobot";
public string OkColor { get; set; } = "71cd40";
public string ErrorColor { get; set; } = "ee281f";
public string Locale { get; set; } = null;
}
public class PlayingStatus :DbEntity

View File

@ -12,7 +12,6 @@ namespace NadekoBot.Services.Database.Models
public string Trigger { get; set; }
public bool IsRegex { get; set; }
public bool OwnerOnly { get; set; }
public override string ToString() => $"`#{Id}` `Trigger:` {Trigger}\n `Response:` {Response}";
}
public class ReactionResponse : DbEntity

View File

@ -64,6 +64,9 @@ namespace NadekoBot.Services.Database.Models
public AntiRaidSetting AntiRaidSetting { get; set; }
public AntiSpamSetting AntiSpamSetting { get; set; }
public string Locale { get; set; } = null;
public string TimeZoneId { get; set; } = null;
//public List<ProtectionIgnoredChannel> ProtectionIgnoredChannels { get; set; } = new List<ProtectionIgnoredChannel>();
}

View File

@ -132,7 +132,7 @@ namespace NadekoBot.Services.Database
{
#region QUOTES
var quoteEntity = modelBuilder.Entity<Quote>();
//var quoteEntity = modelBuilder.Entity<Quote>();
#endregion
@ -166,7 +166,7 @@ namespace NadekoBot.Services.Database
#endregion
#region BotConfig
var botConfigEntity = modelBuilder.Entity<BotConfig>();
//var botConfigEntity = modelBuilder.Entity<BotConfig>();
//botConfigEntity
// .HasMany(c => c.ModulePrefixes)
// .WithOne(mp => mp.BotConfig)

View File

@ -1,4 +1,6 @@
using Discord;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace NadekoBot.Services
{
@ -9,7 +11,7 @@ namespace NadekoBot.Services
string Token { get; }
string GoogleApiKey { get; }
ulong[] OwnerIds { get; }
ImmutableHashSet<ulong> OwnerIds { get; }
string MashapeKey { get; }
string LoLApiKey { get; }

View File

@ -5,6 +5,8 @@ using Discord;
using System.Linq;
using NLog;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace NadekoBot.Services.Impl
{
@ -21,7 +23,7 @@ namespace NadekoBot.Services.Impl
public string Token { get; }
public ulong[] OwnerIds { get; }
public ImmutableHashSet<ulong> OwnerIds { get; }
public string LoLApiKey { get; }
public string OsuApiKey { get; }
@ -61,7 +63,7 @@ namespace NadekoBot.Services.Impl
Token = data[nameof(Token)];
if (string.IsNullOrWhiteSpace(Token))
throw new ArgumentNullException(nameof(Token), "Token is missing from credentials.json or Environment varibles.");
OwnerIds = data.GetSection("OwnerIds").GetChildren().Select(c => ulong.Parse(c.Value)).ToArray();
OwnerIds = data.GetSection("OwnerIds").GetChildren().Select(c => ulong.Parse(c.Value)).ToImmutableHashSet();
LoLApiKey = data[nameof(LoLApiKey)];
GoogleApiKey = data[nameof(GoogleApiKey)];
MashapeKey = data[nameof(MashapeKey)];

View File

@ -1,45 +1,122 @@
namespace NadekoBot.Services
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using System;
using NadekoBot.Services.Database;
using NLog;
namespace NadekoBot.Services
{
public class Localization
{
public string this[string key] => LoadCommandString(key);
private readonly Logger _log;
public ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; }
public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture;
private Localization() { }
public Localization(string defaultCulture, IDictionary<ulong, string> cultureInfoNames)
{
_log = LogManager.GetCurrentClassLogger();
if (string.IsNullOrWhiteSpace(defaultCulture))
DefaultCultureInfo = new CultureInfo("en-US");
else
{
try
{
DefaultCultureInfo = new CultureInfo(defaultCulture);
}
catch
{
_log.Warn("Unable to load default bot's locale/language. Using en-US.");
DefaultCultureInfo = new CultureInfo("en-US");
}
}
GuildCultureInfos = new ConcurrentDictionary<ulong, CultureInfo>(cultureInfoNames.ToDictionary(x => x.Key, x =>
{
CultureInfo cultureInfo = null;
try
{
cultureInfo = new CultureInfo(x.Value);
}
catch { }
return cultureInfo;
}).Where(x => x.Value != null));
}
public void SetGuildCulture(IGuild guild, CultureInfo ci) =>
SetGuildCulture(guild.Id, ci);
public void SetGuildCulture(ulong guildId, CultureInfo ci)
{
if (ci == DefaultCultureInfo)
{
RemoveGuildCulture(guildId);
return;
}
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.GuildConfigs.For(guildId, set => set);
gc.Locale = ci.Name;
uow.Complete();
}
GuildCultureInfos.AddOrUpdate(guildId, ci, (id, old) => ci);
}
public void RemoveGuildCulture(IGuild guild) =>
RemoveGuildCulture(guild.Id);
public void RemoveGuildCulture(ulong guildId) {
CultureInfo throwaway;
if (GuildCultureInfos.TryRemove(guildId, out throwaway))
{
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.GuildConfigs.For(guildId, set => set);
gc.Locale = null;
uow.Complete();
}
}
}
public void SetDefaultCulture(CultureInfo ci)
{
using (var uow = DbHandler.UnitOfWork())
{
var bc = uow.BotConfig.GetOrCreate();
bc.Locale = ci.Name;
uow.Complete();
}
DefaultCultureInfo = ci;
}
public void ResetDefaultCulture() =>
SetDefaultCulture(CultureInfo.CurrentCulture);
public CultureInfo GetCultureInfo(IGuild guild) =>
GetCultureInfo(guild?.Id);
public CultureInfo GetCultureInfo(ulong? guildId)
{
if (guildId == null)
return DefaultCultureInfo;
CultureInfo info = null;
GuildCultureInfos.TryGetValue(guildId.Value, out info);
return info ?? DefaultCultureInfo;
}
public static string LoadCommandString(string key)
{
string toReturn = Resources.CommandStrings.ResourceManager.GetString(key);
return string.IsNullOrWhiteSpace(toReturn) ? key : toReturn;
}
//private static string GetCommandString(string key)
//{
// return key;
//var resx = new List<DictionaryEntry>();
//var fs = new StreamReader(File.OpenRead("./Strings.resx"));
//Console.WriteLine(fs.ReadToEnd());
//using (var reader = new ResourceReader(fs.BaseStream))
//{
// List<DictionaryEntry> existing = new List<DictionaryEntry>();
// foreach (DictionaryEntry item in reader)
// {
// existing.Add(item);
// }
// var existingResource = resx.Where(r => r.Key.ToString() == key).FirstOrDefault();
// if (existingResource.Key == null)
// {
// resx.Add(new DictionaryEntry() { Key = key, Value = key });
// }
// else
// return existingResource.Value.ToString();
//}
//using (var writer = new ResourceWriter(new FileStream("./Strings.resx", FileMode.OpenOrCreate)))
//{
// resx.ForEach(r =>
// {
// writer.AddResource(r.Key.ToString(), r.Value.ToString());
// });
// writer.Generate();
//}
//return key;
//}
}
}

View File

@ -15,7 +15,7 @@ namespace NadekoBot.Services.Impl
private DiscordShardedClient client;
private DateTime started;
public const string BotVersion = "1.1.6";
public const string BotVersion = "1.1.8-alpha";
public string Author => "Kwoth#2560";
public string Library => "Discord.Net";
@ -96,18 +96,21 @@ namespace NadekoBot.Services.Impl
content.Headers.Clear();
content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
var res = await http.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false);
await http.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false);
}
};
}
catch { }
catch
{
// ignored
}
}, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
}
public void Initialize()
{
var guilds = this.client.GetGuilds();
_textChannels = guilds.Sum(g => g.Channels.Where(cx => cx is ITextChannel).Count());
var guilds = this.client.GetGuilds().ToArray();
_textChannels = guilds.Sum(g => g.Channels.Count(cx => cx is ITextChannel));
_voiceChannels = guilds.Sum(g => g.Channels.Count) - _textChannels;
}

View File

@ -41,7 +41,6 @@ namespace NadekoBot.Services
return minValue;
var bytes = new byte[sizeof(int)];
rng.GetBytes(bytes);
var num = BitConverter.ToInt32(bytes, 0);
var sign = Math.Sign(BitConverter.ToInt32(bytes, 0));
return (sign * BitConverter.ToInt32(bytes, 0)) % (maxValue - minValue) + minValue;
}

View File

@ -18,7 +18,7 @@
},
"dependencies": {
"AngleSharp": "0.9.9",
"libvideo": "1.0.0",
"libvideo": "1.0.1",
"CoreCLR-NCalc": "2.1.2",
"Google.Apis.Urlshortener.v1": "1.19.0.138",
"Google.Apis.YouTube.v3": "1.20.0.701",