forked from TeamInklings/Octobot
Refactor guild data storage (#13)
Co-authored-by: mctaylors <volkovvladislav8@gmail.com>
This commit is contained in:
parent
f0a6c8faff
commit
7b8888dae3
24 changed files with 941 additions and 661 deletions
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
|
@ -13,7 +13,9 @@ updates:
|
||||||
# Allow both direct and indirect updates for all packages
|
# Allow both direct and indirect updates for all packages
|
||||||
- dependency-type: "all"
|
- dependency-type: "all"
|
||||||
assignees:
|
assignees:
|
||||||
- "l1ttleO"
|
- "Octol1ttle"
|
||||||
|
labels:
|
||||||
|
- "type: dependencies"
|
||||||
|
|
||||||
- package-ecosystem: "nuget" # See documentation for possible values
|
- package-ecosystem: "nuget" # See documentation for possible values
|
||||||
directory: "/Boyfriend" # Location of package manifests
|
directory: "/Boyfriend" # Location of package manifests
|
||||||
|
@ -24,4 +26,6 @@ updates:
|
||||||
- dependency-type: "all"
|
- dependency-type: "all"
|
||||||
# Add assignees
|
# Add assignees
|
||||||
assignees:
|
assignees:
|
||||||
- "l1ttleO"
|
- "Octol1ttle"
|
||||||
|
labels:
|
||||||
|
- "type: dependencies"
|
||||||
|
|
2
.github/workflows/resharper.yml
vendored
2
.github/workflows/resharper.yml
vendored
|
@ -29,7 +29,7 @@ jobs:
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
|
|
||||||
- name: ReSharper CLI InspectCode
|
- name: ReSharper CLI InspectCode
|
||||||
uses: muno92/resharper_inspectcode@1.6.0
|
uses: muno92/resharper_inspectcode@1.6.6
|
||||||
with:
|
with:
|
||||||
solutionPath: ./Boyfriend-CSharp.sln
|
solutionPath: ./Boyfriend-CSharp.sln
|
||||||
ignoreIssueType: InvertIf
|
ignoreIssueType: InvertIf
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Timers;
|
||||||
|
using Boyfriend.Data;
|
||||||
using Discord;
|
using Discord;
|
||||||
|
using Discord.Rest;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using Newtonsoft.Json;
|
using Timer = System.Timers.Timer;
|
||||||
|
|
||||||
namespace Boyfriend;
|
namespace Boyfriend;
|
||||||
|
|
||||||
|
@ -20,7 +22,10 @@ public static class Boyfriend {
|
||||||
LargeThreshold = 500
|
LargeThreshold = 500
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly List<Tuple<Game, TimeSpan>> ActivityList = new() {
|
private static DateTimeOffset _nextSongAt = DateTimeOffset.MinValue;
|
||||||
|
private static uint _nextSongIndex;
|
||||||
|
|
||||||
|
private static readonly Tuple<Game, TimeSpan>[] ActivityList = {
|
||||||
Tuple.Create(new Game("Masayoshi Minoshima (ft. nomico) - Bad Apple!!", ActivityType.Listening),
|
Tuple.Create(new Game("Masayoshi Minoshima (ft. nomico) - Bad Apple!!", ActivityType.Listening),
|
||||||
new TimeSpan(0, 3, 40)),
|
new TimeSpan(0, 3, 40)),
|
||||||
Tuple.Create(new Game("Xi - Blue Zenith", ActivityType.Listening), new TimeSpan(0, 4, 16)),
|
Tuple.Create(new Game("Xi - Blue Zenith", ActivityType.Listening), new TimeSpan(0, 4, 16)),
|
||||||
|
@ -32,33 +37,13 @@ public static class Boyfriend {
|
||||||
|
|
||||||
public static readonly DiscordSocketClient Client = new(Config);
|
public static readonly DiscordSocketClient Client = new(Config);
|
||||||
|
|
||||||
private static readonly Dictionary<ulong, Dictionary<string, string>> GuildConfigDictionary = new();
|
private static readonly List<Task> GuildTickTasks = new();
|
||||||
|
|
||||||
private static readonly Dictionary<ulong, Dictionary<ulong, ReadOnlyCollection<ulong>>> RemovedRolesDictionary =
|
|
||||||
new();
|
|
||||||
|
|
||||||
public static readonly Dictionary<string, string> DefaultConfig = new() {
|
|
||||||
{ "Prefix", "!" },
|
|
||||||
{ "Lang", "en" },
|
|
||||||
{ "ReceiveStartupMessages", "false" },
|
|
||||||
{ "WelcomeMessage", "default" },
|
|
||||||
{ "SendWelcomeMessages", "true" },
|
|
||||||
{ "BotLogChannel", "0" },
|
|
||||||
{ "StarterRole", "0" },
|
|
||||||
{ "MuteRole", "0" },
|
|
||||||
{ "RemoveRolesOnMute", "false" },
|
|
||||||
{ "FrowningFace", "true" },
|
|
||||||
{ "EventStartedReceivers", "interested,role" },
|
|
||||||
{ "EventNotificationRole", "0" },
|
|
||||||
{ "EventNotificationChannel", "0" },
|
|
||||||
{ "EventEarlyNotificationOffset", "0" }
|
|
||||||
};
|
|
||||||
|
|
||||||
public static void Main() {
|
public static void Main() {
|
||||||
Init().GetAwaiter().GetResult();
|
InitAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task Init() {
|
private static async Task InitAsync() {
|
||||||
var token = (await File.ReadAllTextAsync("token.txt")).Trim();
|
var token = (await File.ReadAllTextAsync("token.txt")).Trim();
|
||||||
|
|
||||||
Client.Log += Log;
|
Client.Log += Log;
|
||||||
|
@ -68,13 +53,35 @@ public static class Boyfriend {
|
||||||
|
|
||||||
EventHandler.InitEvents();
|
EventHandler.InitEvents();
|
||||||
|
|
||||||
while (ActivityList.Count > 0)
|
var timer = new Timer();
|
||||||
foreach (var activity in ActivityList) {
|
timer.Interval = 1000;
|
||||||
await Client.SetActivityAsync(activity.Item1);
|
timer.AutoReset = true;
|
||||||
await Task.Delay(activity.Item2);
|
timer.Elapsed += TickAllGuildsAsync;
|
||||||
|
if (ActivityList.Length is 0) timer.Dispose(); // CodeQL moment
|
||||||
|
timer.Start();
|
||||||
|
|
||||||
|
while (ActivityList.Length > 0)
|
||||||
|
if (DateTimeOffset.Now >= _nextSongAt) {
|
||||||
|
var nextSong = ActivityList[_nextSongIndex];
|
||||||
|
await Client.SetActivityAsync(nextSong.Item1);
|
||||||
|
_nextSongAt = DateTimeOffset.Now.Add(nextSong.Item2);
|
||||||
|
_nextSongIndex++;
|
||||||
|
if (_nextSongIndex >= ActivityList.Length) _nextSongIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async void TickAllGuildsAsync(object? sender, ElapsedEventArgs e) {
|
||||||
|
foreach (var guild in Client.Guilds) GuildTickTasks.Add(TickGuildAsync(guild));
|
||||||
|
|
||||||
|
try { Task.WaitAll(GuildTickTasks.ToArray()); } catch (AggregateException ex) {
|
||||||
|
foreach (var exc in ex.InnerExceptions)
|
||||||
|
await Log(new LogMessage(LogSeverity.Error, nameof(Boyfriend),
|
||||||
|
"Exception while ticking guilds", exc));
|
||||||
|
}
|
||||||
|
|
||||||
|
GuildTickTasks.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
public static Task Log(LogMessage msg) {
|
public static Task Log(LogMessage msg) {
|
||||||
switch (msg.Severity) {
|
switch (msg.Severity) {
|
||||||
case LogSeverity.Critical:
|
case LogSeverity.Critical:
|
||||||
|
@ -102,53 +109,63 @@ public static class Boyfriend {
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task WriteGuildConfigAsync(ulong id) {
|
private static async Task TickGuildAsync(SocketGuild guild) {
|
||||||
await File.WriteAllTextAsync($"config_{id}.json",
|
var data = GuildData.Get(guild);
|
||||||
JsonConvert.SerializeObject(GuildConfigDictionary[id], Formatting.Indented));
|
var config = data.Preferences;
|
||||||
|
var saveData = false;
|
||||||
|
_ = int.TryParse(config["EventEarlyNotificationOffset"], out var offset);
|
||||||
|
foreach (var schEvent in guild.Events)
|
||||||
|
if (schEvent.Status is GuildScheduledEventStatus.Scheduled && config["AutoStartEvents"] is "true" &&
|
||||||
|
DateTimeOffset.Now >= schEvent.StartTime) { await schEvent.StartAsync(); } else if
|
||||||
|
(!data.EarlyNotifications.Contains(schEvent.Id) &&
|
||||||
|
DateTimeOffset.Now >= schEvent.StartTime.Subtract(new TimeSpan(0, offset, 0))) {
|
||||||
|
data.EarlyNotifications.Add(schEvent.Id);
|
||||||
|
var receivers = config["EventStartedReceivers"];
|
||||||
|
var role = guild.GetRole(ulong.Parse(config["EventNotificationRole"]));
|
||||||
|
var mentions = StringBuilder;
|
||||||
|
|
||||||
if (RemovedRolesDictionary.TryGetValue(id, out var removedRoles))
|
if (receivers.Contains("role") && role is not null) mentions.Append($"{role.Mention} ");
|
||||||
await File.WriteAllTextAsync($"removedroles_{id}.json",
|
if (receivers.Contains("users") || receivers.Contains("interested"))
|
||||||
JsonConvert.SerializeObject(removedRoles, Formatting.Indented));
|
mentions = (await schEvent.GetUsersAsync(15))
|
||||||
}
|
.Where(user => role is null || !((RestGuildUser)user).RoleIds.Contains(role.Id))
|
||||||
|
.Aggregate(mentions, (current, user) => current.Append($"{user.Mention} "));
|
||||||
|
|
||||||
public static Dictionary<string, string> GetGuildConfig(ulong id) {
|
await Utils.GetEventNotificationChannel(guild)?.SendMessageAsync(string.Format(
|
||||||
if (GuildConfigDictionary.TryGetValue(id, out var cfg)) return cfg;
|
Messages.EventEarlyNotification,
|
||||||
|
mentions,
|
||||||
|
Utils.Wrap(schEvent.Name),
|
||||||
|
schEvent.StartTime.ToUnixTimeSeconds().ToString()))!;
|
||||||
|
mentions.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
var path = $"config_{id}.json";
|
foreach (var mData in data.MemberData.Values) {
|
||||||
|
if (DateTimeOffset.Now >= mData.BannedUntil) _ = guild.RemoveBanAsync(mData.Id);
|
||||||
|
|
||||||
if (!File.Exists(path)) File.Create(path).Dispose();
|
if (mData.IsInGuild) {
|
||||||
|
if (DateTimeOffset.Now >= mData.MutedUntil) {
|
||||||
|
await Utils.UnmuteMemberAsync(data, Client.CurrentUser.ToString(), guild.GetUser(mData.Id),
|
||||||
|
Messages.PunishmentExpired);
|
||||||
|
saveData = true;
|
||||||
|
}
|
||||||
|
|
||||||
var json = File.ReadAllText(path);
|
for (var i = mData.Reminders.Count - 1; i >= 0; i--) {
|
||||||
var config = JsonConvert.DeserializeObject<Dictionary<string, string>>(json)
|
var reminder = mData.Reminders[i];
|
||||||
?? new Dictionary<string, string>();
|
if (DateTimeOffset.Now >= reminder.RemindAt) {
|
||||||
|
var channel = guild.GetTextChannel(reminder.ReminderChannel);
|
||||||
|
if (channel is null) {
|
||||||
|
await Utils.SendDirectMessage(Client.GetUser(mData.Id), reminder.ReminderText);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (config.Keys.Count < DefaultConfig.Keys.Count) {
|
await channel.SendMessageAsync($"<@{mData.Id}> {Utils.Wrap(reminder.ReminderText)}");
|
||||||
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
|
mData.Reminders.RemoveAt(i);
|
||||||
// Conversion will result in a lot of memory allocations
|
|
||||||
foreach (var key in DefaultConfig.Keys)
|
saveData = true;
|
||||||
if (!config.ContainsKey(key))
|
}
|
||||||
config.Add(key, DefaultConfig[key]);
|
}
|
||||||
} else if (config.Keys.Count > DefaultConfig.Keys.Count) {
|
}
|
||||||
foreach (var key in config.Keys.Where(key => !DefaultConfig.ContainsKey(key))) config.Remove(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GuildConfigDictionary.Add(id, config);
|
if (saveData) data.Save(true).Wait();
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dictionary<ulong, ReadOnlyCollection<ulong>> GetRemovedRoles(ulong id) {
|
|
||||||
if (RemovedRolesDictionary.TryGetValue(id, out var dict)) return dict;
|
|
||||||
var path = $"removedroles_{id}.json";
|
|
||||||
|
|
||||||
if (!File.Exists(path)) File.Create(path).Dispose();
|
|
||||||
|
|
||||||
var json = File.ReadAllText(path);
|
|
||||||
var removedRoles = JsonConvert.DeserializeObject<Dictionary<ulong, ReadOnlyCollection<ulong>>>(json)
|
|
||||||
?? new Dictionary<ulong, ReadOnlyCollection<ulong>>();
|
|
||||||
|
|
||||||
RemovedRolesDictionary.Add(id, removedRoles);
|
|
||||||
|
|
||||||
return removedRoles;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Boyfriend.Commands;
|
using Boyfriend.Commands;
|
||||||
|
using Boyfriend.Data;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
@ -12,7 +13,8 @@ public sealed class CommandProcessor {
|
||||||
public static readonly ICommand[] Commands = {
|
public static readonly ICommand[] Commands = {
|
||||||
new BanCommand(), new ClearCommand(), new HelpCommand(),
|
new BanCommand(), new ClearCommand(), new HelpCommand(),
|
||||||
new KickCommand(), new MuteCommand(), new PingCommand(),
|
new KickCommand(), new MuteCommand(), new PingCommand(),
|
||||||
new SettingsCommand(), new UnbanCommand(), new UnmuteCommand()
|
new SettingsCommand(), new UnbanCommand(), new UnmuteCommand(),
|
||||||
|
new RemindCommand()
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly StringBuilder _stackedPrivateFeedback = new();
|
private readonly StringBuilder _stackedPrivateFeedback = new();
|
||||||
|
@ -30,19 +32,18 @@ public sealed class CommandProcessor {
|
||||||
|
|
||||||
public async Task HandleCommandAsync() {
|
public async Task HandleCommandAsync() {
|
||||||
var guild = Context.Guild;
|
var guild = Context.Guild;
|
||||||
var config = Boyfriend.GetGuildConfig(guild.Id);
|
var data = GuildData.Get(guild);
|
||||||
var muteRole = Utils.GetMuteRole(guild);
|
Utils.SetCurrentLanguage(guild);
|
||||||
Utils.SetCurrentLanguage(guild.Id);
|
|
||||||
|
|
||||||
if (GetMember().Roles.Contains(muteRole)) {
|
if (GetMember().Roles.Contains(data.MuteRole)) {
|
||||||
_ = Context.Message.ReplyAsync(Messages.UserCannotUnmuteThemselves);
|
_ = Context.Message.DeleteAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = Context.Message.Content.Split("\n");
|
var list = Context.Message.Content.Split("\n");
|
||||||
var cleanList = Context.Message.CleanContent.Split("\n");
|
var cleanList = Context.Message.CleanContent.Split("\n");
|
||||||
for (var i = 0; i < list.Length; i++)
|
for (var i = 0; i < list.Length; i++)
|
||||||
_tasks.Add(RunCommandOnLine(list[i], cleanList[i], config["Prefix"]));
|
_tasks.Add(RunCommandOnLine(list[i], cleanList[i], data.Preferences["Prefix"]));
|
||||||
|
|
||||||
try { Task.WaitAll(_tasks.ToArray()); } catch (AggregateException e) {
|
try { Task.WaitAll(_tasks.ToArray()); } catch (AggregateException e) {
|
||||||
foreach (var ex in e.InnerExceptions)
|
foreach (var ex in e.InnerExceptions)
|
||||||
|
@ -52,7 +53,7 @@ public sealed class CommandProcessor {
|
||||||
|
|
||||||
_tasks.Clear();
|
_tasks.Clear();
|
||||||
|
|
||||||
if (ConfigWriteScheduled) await Boyfriend.WriteGuildConfigAsync(guild.Id);
|
if (ConfigWriteScheduled) await data.Save(true);
|
||||||
|
|
||||||
SendFeedbacks();
|
SendFeedbacks();
|
||||||
}
|
}
|
||||||
|
@ -79,8 +80,9 @@ public sealed class CommandProcessor {
|
||||||
|
|
||||||
public void Audit(string action, bool isPublic = true) {
|
public void Audit(string action, bool isPublic = true) {
|
||||||
var format = $"*[{Context.User.Mention}: {action}]*";
|
var format = $"*[{Context.User.Mention}: {action}]*";
|
||||||
if (isPublic) Utils.SafeAppendToBuilder(_stackedPublicFeedback, format, Context.Guild.SystemChannel);
|
var data = GuildData.Get(Context.Guild);
|
||||||
Utils.SafeAppendToBuilder(_stackedPrivateFeedback, format, Utils.GetBotLogChannel(Context.Guild.Id));
|
if (isPublic) Utils.SafeAppendToBuilder(_stackedPublicFeedback, format, data.PublicFeedbackChannel);
|
||||||
|
Utils.SafeAppendToBuilder(_stackedPrivateFeedback, format, data.PrivateFeedbackChannel);
|
||||||
if (_tasks.Count is 0) SendFeedbacks(false);
|
if (_tasks.Count is 0) SendFeedbacks(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,8 +90,9 @@ public sealed class CommandProcessor {
|
||||||
if (reply && _stackedReplyMessage.Length > 0)
|
if (reply && _stackedReplyMessage.Length > 0)
|
||||||
_ = Context.Message.ReplyAsync(_stackedReplyMessage.ToString(), false, null, AllowedMentions.None);
|
_ = Context.Message.ReplyAsync(_stackedReplyMessage.ToString(), false, null, AllowedMentions.None);
|
||||||
|
|
||||||
var adminChannel = Utils.GetBotLogChannel(Context.Guild.Id);
|
var data = GuildData.Get(Context.Guild);
|
||||||
var systemChannel = Context.Guild.SystemChannel;
|
var adminChannel = data.PublicFeedbackChannel;
|
||||||
|
var systemChannel = data.PrivateFeedbackChannel;
|
||||||
if (_stackedPrivateFeedback.Length > 0 && adminChannel is not null &&
|
if (_stackedPrivateFeedback.Length > 0 && adminChannel is not null &&
|
||||||
adminChannel.Id != Context.Message.Channel.Id) {
|
adminChannel.Id != Context.Message.Channel.Id) {
|
||||||
_ = Utils.SilentSendAsync(adminChannel, _stackedPrivateFeedback.ToString());
|
_ = Utils.SilentSendAsync(adminChannel, _stackedPrivateFeedback.ToString());
|
||||||
|
@ -111,19 +114,30 @@ public sealed class CommandProcessor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketUser? GetUser(string[] args, string[] cleanArgs, int index, string? argument) {
|
public Tuple<ulong, SocketUser?>? GetUser(string[] args, string[] cleanArgs, int index) {
|
||||||
if (index >= args.Length) {
|
if (index >= args.Length) {
|
||||||
Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingUser}",
|
Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingUser}",
|
||||||
Context.Message);
|
Context.Message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = Boyfriend.Client.GetUser(Utils.ParseMention(args[index]));
|
var mention = Utils.ParseMention(args[index]);
|
||||||
if (user is null && argument is not null)
|
if (mention is 0) {
|
||||||
Utils.SafeAppendToBuilder(_stackedReplyMessage,
|
Utils.SafeAppendToBuilder(_stackedReplyMessage,
|
||||||
$"{ReplyEmojis.InvalidArgument} {string.Format(Messages.InvalidUser, Utils.Wrap(cleanArgs[index]))}",
|
$"{ReplyEmojis.InvalidArgument} {string.Format(Messages.InvalidUser, Utils.Wrap(cleanArgs[index]))}",
|
||||||
Context.Message);
|
Context.Message);
|
||||||
return user;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var exists = Utils.UserExists(mention);
|
||||||
|
if (!exists) {
|
||||||
|
Utils.SafeAppendToBuilder(_stackedReplyMessage,
|
||||||
|
$"{ReplyEmojis.Error} {string.Format(Messages.UserNotFound, Utils.Wrap(cleanArgs[index]))}",
|
||||||
|
Context.Message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Tuple.Create(mention, Boyfriend.Client.GetUser(mention))!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasPermission(GuildPermission permission) {
|
public bool HasPermission(GuildPermission permission) {
|
||||||
|
@ -134,7 +148,7 @@ public sealed class CommandProcessor {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Context.Guild.GetUser(Context.User.Id).GuildPermissions.Has(permission)
|
if (!GetMember().GuildPermissions.Has(permission)
|
||||||
&& Context.Guild.OwnerId != Context.User.Id) {
|
&& Context.Guild.OwnerId != Context.User.Id) {
|
||||||
Utils.SafeAppendToBuilder(_stackedReplyMessage,
|
Utils.SafeAppendToBuilder(_stackedReplyMessage,
|
||||||
$"{ReplyEmojis.NoPermission} {Utils.GetMessage($"UserCannot{permission}")}",
|
$"{ReplyEmojis.NoPermission} {Utils.GetMessage($"UserCannot{permission}")}",
|
||||||
|
@ -145,11 +159,15 @@ public sealed class CommandProcessor {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketGuildUser? GetMember(SocketUser user) {
|
private SocketGuildUser GetMember() {
|
||||||
return Context.Guild.GetUser(user.Id);
|
return GetMember(Context.User.Id)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketGuildUser? GetMember(string[] args, string[] cleanArgs, int index, string? argument) {
|
public SocketGuildUser? GetMember(ulong id) {
|
||||||
|
return Context.Guild.GetUser(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketGuildUser? GetMember(string[] args, int index) {
|
||||||
if (index >= args.Length) {
|
if (index >= args.Length) {
|
||||||
Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingMember}",
|
Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingMember}",
|
||||||
Context.Message);
|
Context.Message);
|
||||||
|
@ -157,17 +175,13 @@ public sealed class CommandProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
var member = Context.Guild.GetUser(Utils.ParseMention(args[index]));
|
var member = Context.Guild.GetUser(Utils.ParseMention(args[index]));
|
||||||
if (member is null && argument is not null)
|
if (member is null)
|
||||||
Utils.SafeAppendToBuilder(_stackedReplyMessage,
|
Utils.SafeAppendToBuilder(_stackedReplyMessage,
|
||||||
$"{ReplyEmojis.InvalidArgument} {string.Format(Messages.InvalidMember, Utils.Wrap(cleanArgs[index]))}",
|
$"{ReplyEmojis.InvalidArgument} {Messages.InvalidMember}",
|
||||||
Context.Message);
|
Context.Message);
|
||||||
return member;
|
return member;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SocketGuildUser GetMember() {
|
|
||||||
return Context.Guild.GetUser(Context.User.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ulong? GetBan(string[] args, int index) {
|
public ulong? GetBan(string[] args, int index) {
|
||||||
if (index >= args.Length) {
|
if (index >= args.Length) {
|
||||||
Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingUser}",
|
Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingUser}",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Boyfriend.Data;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
|
||||||
|
@ -7,10 +8,10 @@ public sealed class BanCommand : ICommand {
|
||||||
public string[] Aliases { get; } = { "ban", "бан" };
|
public string[] Aliases { get; } = { "ban", "бан" };
|
||||||
|
|
||||||
public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
||||||
var toBan = cmd.GetUser(args, cleanArgs, 0, "ToBan");
|
var toBan = cmd.GetUser(args, cleanArgs, 0);
|
||||||
if (toBan is null || !cmd.HasPermission(GuildPermission.BanMembers)) return;
|
if (toBan is null || !cmd.HasPermission(GuildPermission.BanMembers)) return;
|
||||||
|
|
||||||
var memberToBan = cmd.GetMember(toBan);
|
var memberToBan = cmd.GetMember(toBan.Item1);
|
||||||
if (memberToBan is not null && !cmd.CanInteractWith(memberToBan, "Ban")) return;
|
if (memberToBan is not null && !cmd.CanInteractWith(memberToBan, "Ban")) return;
|
||||||
|
|
||||||
var duration = CommandProcessor.GetTimeSpan(args, 1);
|
var duration = CommandProcessor.GetTimeSpan(args, 1);
|
||||||
|
@ -18,21 +19,27 @@ public sealed class BanCommand : ICommand {
|
||||||
if (reason is not null) await BanUserAsync(cmd, toBan, duration, reason);
|
if (reason is not null) await BanUserAsync(cmd, toBan, duration, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task BanUserAsync(CommandProcessor cmd, SocketUser toBan, TimeSpan duration, string reason) {
|
private static async Task BanUserAsync(CommandProcessor cmd, Tuple<ulong, SocketUser?> toBan, TimeSpan duration,
|
||||||
|
string reason) {
|
||||||
var author = cmd.Context.User;
|
var author = cmd.Context.User;
|
||||||
var guild = cmd.Context.Guild;
|
var guild = cmd.Context.Guild;
|
||||||
await Utils.SendDirectMessage(toBan,
|
if (toBan.Item2 is not null)
|
||||||
string.Format(Messages.YouWereBanned, author.Mention, guild.Name, Utils.Wrap(reason)));
|
await Utils.SendDirectMessage(toBan.Item2,
|
||||||
|
string.Format(Messages.YouWereBanned, author.Mention, guild.Name, Utils.Wrap(reason)));
|
||||||
|
|
||||||
var guildBanMessage = $"({author}) {reason}";
|
var guildBanMessage = $"({author}) {reason}";
|
||||||
await guild.AddBanAsync(toBan, 0, guildBanMessage);
|
await guild.AddBanAsync(toBan.Item1, 0, guildBanMessage);
|
||||||
|
|
||||||
var feedback = string.Format(Messages.FeedbackUserBanned, toBan.Mention,
|
var memberData = GuildData.Get(guild).MemberData[toBan.Item1];
|
||||||
Utils.GetHumanizedTimeOffset(duration), Utils.Wrap(reason));
|
memberData.BannedUntil
|
||||||
|
= duration.TotalSeconds < 1 ? DateTimeOffset.MaxValue : DateTimeOffset.Now.Add(duration);
|
||||||
|
memberData.Roles.Clear();
|
||||||
|
|
||||||
|
cmd.ConfigWriteScheduled = true;
|
||||||
|
|
||||||
|
var feedback = string.Format(Messages.FeedbackUserBanned, $"<@{toBan.Item1.ToString()}>",
|
||||||
|
Utils.GetHumanizedTimeSpan(duration), Utils.Wrap(reason));
|
||||||
cmd.Reply(feedback, ReplyEmojis.Banned);
|
cmd.Reply(feedback, ReplyEmojis.Banned);
|
||||||
cmd.Audit(feedback);
|
cmd.Audit(feedback);
|
||||||
|
|
||||||
if (duration.TotalSeconds > 0)
|
|
||||||
await Task.FromResult(Utils.DelayedUnbanAsync(cmd, toBan.Id, Messages.PunishmentExpired, duration));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Discord;
|
using System.Diagnostics;
|
||||||
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
|
||||||
namespace Boyfriend.Commands;
|
namespace Boyfriend.Commands;
|
||||||
|
@ -7,7 +8,7 @@ public sealed class ClearCommand : ICommand {
|
||||||
public string[] Aliases { get; } = { "clear", "purge", "очистить", "стереть" };
|
public string[] Aliases { get; } = { "clear", "purge", "очистить", "стереть" };
|
||||||
|
|
||||||
public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
||||||
if (cmd.Context.Channel is not SocketTextChannel channel) throw new Exception();
|
if (cmd.Context.Channel is not SocketTextChannel channel) throw new UnreachableException();
|
||||||
|
|
||||||
if (!cmd.HasPermission(GuildPermission.ManageMessages)) return;
|
if (!cmd.HasPermission(GuildPermission.ManageMessages)) return;
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ public sealed class ClearCommand : ICommand {
|
||||||
var user = (SocketGuildUser)cmd.Context.User;
|
var user = (SocketGuildUser)cmd.Context.User;
|
||||||
await channel.DeleteMessagesAsync(messages, Utils.GetRequestOptions(user.ToString()!));
|
await channel.DeleteMessagesAsync(messages, Utils.GetRequestOptions(user.ToString()!));
|
||||||
|
|
||||||
cmd.Audit(string.Format(Messages.FeedbackMessagesCleared, (toDelete + 1).ToString()));
|
cmd.Audit(string.Format(Messages.FeedbackMessagesCleared, (toDelete + 1).ToString(),
|
||||||
|
Utils.MentionChannel(channel.Id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Boyfriend.Data;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
|
||||||
namespace Boyfriend.Commands;
|
namespace Boyfriend.Commands;
|
||||||
|
@ -6,7 +7,7 @@ public sealed class HelpCommand : ICommand {
|
||||||
public string[] Aliases { get; } = { "help", "помощь", "справка" };
|
public string[] Aliases { get; } = { "help", "помощь", "справка" };
|
||||||
|
|
||||||
public Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
public Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
||||||
var prefix = Boyfriend.GetGuildConfig(cmd.Context.Guild.Id)["Prefix"];
|
var prefix = GuildData.Get(cmd.Context.Guild).Preferences["Prefix"];
|
||||||
var toSend = Boyfriend.StringBuilder.Append(Messages.CommandHelp);
|
var toSend = Boyfriend.StringBuilder.Append(Messages.CommandHelp);
|
||||||
|
|
||||||
foreach (var command in CommandProcessor.Commands)
|
foreach (var command in CommandProcessor.Commands)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Boyfriend.Data;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
|
||||||
|
@ -7,7 +8,7 @@ public sealed class KickCommand : ICommand {
|
||||||
public string[] Aliases { get; } = { "kick", "кик", "выгнать" };
|
public string[] Aliases { get; } = { "kick", "кик", "выгнать" };
|
||||||
|
|
||||||
public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
||||||
var toKick = cmd.GetMember(args, cleanArgs, 0, "ToKick");
|
var toKick = cmd.GetMember(args, 0);
|
||||||
if (toKick is null || !cmd.HasPermission(GuildPermission.KickMembers)) return;
|
if (toKick is null || !cmd.HasPermission(GuildPermission.KickMembers)) return;
|
||||||
|
|
||||||
if (cmd.CanInteractWith(toKick, "Kick"))
|
if (cmd.CanInteractWith(toKick, "Kick"))
|
||||||
|
@ -22,6 +23,9 @@ public sealed class KickCommand : ICommand {
|
||||||
string.Format(Messages.YouWereKicked, cmd.Context.User.Mention, cmd.Context.Guild.Name,
|
string.Format(Messages.YouWereKicked, cmd.Context.User.Mention, cmd.Context.Guild.Name,
|
||||||
Utils.Wrap(reason)));
|
Utils.Wrap(reason)));
|
||||||
|
|
||||||
|
GuildData.Get(cmd.Context.Guild).MemberData[toKick.Id].Roles.Clear();
|
||||||
|
cmd.ConfigWriteScheduled = true;
|
||||||
|
|
||||||
await toKick.KickAsync(guildKickMessage);
|
await toKick.KickAsync(guildKickMessage);
|
||||||
var format = string.Format(Messages.FeedbackMemberKicked, toKick.Mention, Utils.Wrap(reason));
|
var format = string.Format(Messages.FeedbackMemberKicked, toKick.Mention, Utils.Wrap(reason));
|
||||||
cmd.Reply(format, ReplyEmojis.Kicked);
|
cmd.Reply(format, ReplyEmojis.Kicked);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
using Boyfriend.Data;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Net;
|
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
|
||||||
namespace Boyfriend.Commands;
|
namespace Boyfriend.Commands;
|
||||||
|
@ -8,64 +8,38 @@ public sealed class MuteCommand : ICommand {
|
||||||
public string[] Aliases { get; } = { "mute", "timeout", "заглушить", "мут" };
|
public string[] Aliases { get; } = { "mute", "timeout", "заглушить", "мут" };
|
||||||
|
|
||||||
public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
||||||
var toMute = cmd.GetMember(args, cleanArgs, 0, "ToMute");
|
var toMute = cmd.GetMember(args, 0);
|
||||||
if (toMute is null) return;
|
if (toMute is null) return;
|
||||||
|
|
||||||
var duration = CommandProcessor.GetTimeSpan(args, 1);
|
var duration = CommandProcessor.GetTimeSpan(args, 1);
|
||||||
var reason = cmd.GetRemaining(args, duration.TotalSeconds < 1 ? 1 : 2, "MuteReason");
|
var reason = cmd.GetRemaining(args, duration.TotalSeconds < 1 ? 1 : 2, "MuteReason");
|
||||||
if (reason is null) return;
|
if (reason is null) return;
|
||||||
var role = Utils.GetMuteRole(cmd.Context.Guild);
|
var guildData = GuildData.Get(cmd.Context.Guild);
|
||||||
|
var role = guildData.MuteRole;
|
||||||
|
|
||||||
if ((role is not null && toMute.Roles.Contains(role))
|
if ((role is not null && toMute.Roles.Contains(role))
|
||||||
|| (toMute.TimedOutUntil is not null
|
|| (toMute.TimedOutUntil is not null
|
||||||
&& toMute.TimedOutUntil.Value.ToUnixTimeSeconds()
|
&& toMute.TimedOutUntil.Value
|
||||||
> DateTimeOffset.Now.ToUnixTimeSeconds())) {
|
> DateTimeOffset.Now)) {
|
||||||
cmd.Reply(Messages.MemberAlreadyMuted, ReplyEmojis.Error);
|
cmd.Reply(Messages.MemberAlreadyMuted, ReplyEmojis.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rolesRemoved = Boyfriend.GetRemovedRoles(cmd.Context.Guild.Id);
|
|
||||||
|
|
||||||
if (rolesRemoved.TryGetValue(toMute.Id, out var mutedRemovedRoles)) {
|
|
||||||
foreach (var roleId in mutedRemovedRoles) await toMute.AddRoleAsync(roleId);
|
|
||||||
rolesRemoved.Remove(toMute.Id);
|
|
||||||
cmd.ConfigWriteScheduled = true;
|
|
||||||
cmd.Reply(Messages.RolesReturned, ReplyEmojis.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cmd.HasPermission(GuildPermission.ModerateMembers) && cmd.CanInteractWith(toMute, "Mute"))
|
if (cmd.HasPermission(GuildPermission.ModerateMembers) && cmd.CanInteractWith(toMute, "Mute"))
|
||||||
await MuteMemberAsync(cmd, toMute, duration, reason);
|
await MuteMemberAsync(cmd, toMute, duration, guildData, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task MuteMemberAsync(CommandProcessor cmd, SocketGuildUser toMute,
|
private static async Task MuteMemberAsync(CommandProcessor cmd, SocketGuildUser toMute,
|
||||||
TimeSpan duration, string reason) {
|
TimeSpan duration, GuildData data, string reason) {
|
||||||
var guild = cmd.Context.Guild;
|
|
||||||
var config = Boyfriend.GetGuildConfig(guild.Id);
|
|
||||||
var requestOptions = Utils.GetRequestOptions($"({cmd.Context.User}) {reason}");
|
var requestOptions = Utils.GetRequestOptions($"({cmd.Context.User}) {reason}");
|
||||||
var role = Utils.GetMuteRole(guild);
|
var role = data.MuteRole;
|
||||||
var hasDuration = duration.TotalSeconds > 0;
|
var hasDuration = duration.TotalSeconds > 0;
|
||||||
|
|
||||||
if (role is not null) {
|
if (role is not null) {
|
||||||
if (config["RemoveRolesOnMute"] is "true") {
|
if (data.Preferences["RemoveRolesOnMute"] is "true")
|
||||||
var rolesRemoved = new List<ulong>();
|
await toMute.RemoveRolesAsync(toMute.Roles, requestOptions);
|
||||||
foreach (var userRole in toMute.Roles)
|
|
||||||
try {
|
|
||||||
if (userRole == guild.EveryoneRole || userRole == role) continue;
|
|
||||||
await toMute.RemoveRoleAsync(role);
|
|
||||||
rolesRemoved.Add(userRole.Id);
|
|
||||||
} catch (HttpException e) {
|
|
||||||
cmd.Reply(string.Format(Messages.RoleRemovalFailed, $"<@&{userRole}>", Utils.Wrap(e.Reason)),
|
|
||||||
ReplyEmojis.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
Boyfriend.GetRemovedRoles(guild.Id).Add(toMute.Id, rolesRemoved.AsReadOnly());
|
|
||||||
cmd.ConfigWriteScheduled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await toMute.AddRoleAsync(role, requestOptions);
|
await toMute.AddRoleAsync(role, requestOptions);
|
||||||
|
|
||||||
if (hasDuration)
|
|
||||||
await Task.FromResult(Utils.DelayedUnmuteAsync(cmd, toMute, Messages.PunishmentExpired, duration));
|
|
||||||
} else {
|
} else {
|
||||||
if (!hasDuration || duration.TotalDays > 28) {
|
if (!hasDuration || duration.TotalDays > 28) {
|
||||||
cmd.Reply(Messages.DurationRequiredForTimeOuts, ReplyEmojis.Error);
|
cmd.Reply(Messages.DurationRequiredForTimeOuts, ReplyEmojis.Error);
|
||||||
|
@ -80,8 +54,11 @@ public sealed class MuteCommand : ICommand {
|
||||||
await toMute.SetTimeOutAsync(duration, requestOptions);
|
await toMute.SetTimeOutAsync(duration, requestOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.MemberData[toMute.Id].MutedUntil = DateTimeOffset.Now.Add(duration);
|
||||||
|
cmd.ConfigWriteScheduled = true;
|
||||||
|
|
||||||
var feedback = string.Format(Messages.FeedbackMemberMuted, toMute.Mention,
|
var feedback = string.Format(Messages.FeedbackMemberMuted, toMute.Mention,
|
||||||
Utils.GetHumanizedTimeOffset(duration),
|
Utils.GetHumanizedTimeSpan(duration),
|
||||||
Utils.Wrap(reason));
|
Utils.Wrap(reason));
|
||||||
cmd.Reply(feedback, ReplyEmojis.Muted);
|
cmd.Reply(feedback, ReplyEmojis.Muted);
|
||||||
cmd.Audit(feedback);
|
cmd.Audit(feedback);
|
||||||
|
|
|
@ -7,7 +7,7 @@ public sealed class PingCommand : ICommand {
|
||||||
var builder = Boyfriend.StringBuilder;
|
var builder = Boyfriend.StringBuilder;
|
||||||
|
|
||||||
builder.Append(Utils.GetBeep())
|
builder.Append(Utils.GetBeep())
|
||||||
.Append(Math.Abs(DateTimeOffset.Now.Subtract(cmd.Context.Message.Timestamp).TotalMilliseconds))
|
.Append(Math.Round(Math.Abs(DateTimeOffset.Now.Subtract(cmd.Context.Message.Timestamp).TotalMilliseconds)))
|
||||||
.Append(Messages.Milliseconds);
|
.Append(Messages.Milliseconds);
|
||||||
|
|
||||||
cmd.Reply(builder.ToString(), ReplyEmojis.Ping);
|
cmd.Reply(builder.ToString(), ReplyEmojis.Ping);
|
||||||
|
|
23
Boyfriend/Commands/RemindCommand.cs
Normal file
23
Boyfriend/Commands/RemindCommand.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using Boyfriend.Data;
|
||||||
|
|
||||||
|
namespace Boyfriend.Commands;
|
||||||
|
|
||||||
|
public sealed class RemindCommand : ICommand {
|
||||||
|
public string[] Aliases { get; } = { "remind", "reminder", "remindme", "напомни", "напомнить", "напоминание" };
|
||||||
|
|
||||||
|
public Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
||||||
|
// TODO: actually make this good
|
||||||
|
var remindIn = CommandProcessor.GetTimeSpan(args, 0);
|
||||||
|
var reminderText = cmd.GetRemaining(cleanArgs, 1, "ReminderText");
|
||||||
|
if (reminderText is not null)
|
||||||
|
GuildData.Get(cmd.Context.Guild).MemberData[cmd.Context.User.Id].Reminders.Add(new Reminder {
|
||||||
|
RemindAt = DateTimeOffset.Now.Add(remindIn),
|
||||||
|
ReminderText = reminderText,
|
||||||
|
ReminderChannel = cmd.Context.Channel.Id
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.ConfigWriteScheduled = true;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Boyfriend.Data;
|
||||||
using Discord;
|
using Discord;
|
||||||
|
|
||||||
namespace Boyfriend.Commands;
|
namespace Boyfriend.Commands;
|
||||||
|
@ -9,14 +10,17 @@ public sealed class SettingsCommand : ICommand {
|
||||||
if (!cmd.HasPermission(GuildPermission.ManageGuild)) return Task.CompletedTask;
|
if (!cmd.HasPermission(GuildPermission.ManageGuild)) return Task.CompletedTask;
|
||||||
|
|
||||||
var guild = cmd.Context.Guild;
|
var guild = cmd.Context.Guild;
|
||||||
var config = Boyfriend.GetGuildConfig(guild.Id);
|
var data = GuildData.Get(guild);
|
||||||
|
var config = data.Preferences;
|
||||||
|
|
||||||
if (args.Length is 0) {
|
if (args.Length is 0) {
|
||||||
var currentSettings = Boyfriend.StringBuilder.AppendLine(Messages.CurrentSettings);
|
var currentSettings = Boyfriend.StringBuilder.AppendLine(Messages.CurrentSettings);
|
||||||
|
|
||||||
foreach (var setting in Boyfriend.DefaultConfig) {
|
foreach (var setting in GuildData.DefaultPreferences) {
|
||||||
var format = "{0}";
|
var format = "{0}";
|
||||||
var currentValue = config[setting.Key] is "default" ? Messages.DefaultWelcomeMessage : config[setting.Key];
|
var currentValue = config[setting.Key] is "default"
|
||||||
|
? Messages.DefaultWelcomeMessage
|
||||||
|
: config[setting.Key];
|
||||||
|
|
||||||
if (setting.Key.EndsWith("Channel")) {
|
if (setting.Key.EndsWith("Channel")) {
|
||||||
if (guild.GetTextChannel(ulong.Parse(currentValue)) is not null) format = "<#{0}>";
|
if (guild.GetTextChannel(ulong.Parse(currentValue)) is not null) format = "<#{0}>";
|
||||||
|
@ -41,10 +45,7 @@ public sealed class SettingsCommand : ICommand {
|
||||||
var selectedSetting = args[0].ToLower();
|
var selectedSetting = args[0].ToLower();
|
||||||
|
|
||||||
var exists = false;
|
var exists = false;
|
||||||
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
|
foreach (var setting in GuildData.DefaultPreferences.Keys.Where(x => x.ToLower() == selectedSetting)) {
|
||||||
// Too many allocations
|
|
||||||
foreach (var setting in Boyfriend.DefaultConfig.Keys) {
|
|
||||||
if (selectedSetting != setting.ToLower()) continue;
|
|
||||||
selectedSetting = setting;
|
selectedSetting = setting;
|
||||||
exists = true;
|
exists = true;
|
||||||
break;
|
break;
|
||||||
|
@ -70,7 +71,7 @@ public sealed class SettingsCommand : ICommand {
|
||||||
}
|
}
|
||||||
} else { value = "reset"; }
|
} else { value = "reset"; }
|
||||||
|
|
||||||
if (IsBool(Boyfriend.DefaultConfig[selectedSetting]) && !IsBool(value)) {
|
if (IsBool(GuildData.DefaultPreferences[selectedSetting]) && !IsBool(value)) {
|
||||||
value = value switch {
|
value = value switch {
|
||||||
"y" or "yes" or "д" or "да" => "true",
|
"y" or "yes" or "д" or "да" => "true",
|
||||||
"n" or "no" or "н" or "нет" => "false",
|
"n" or "no" or "н" or "нет" => "false",
|
||||||
|
@ -95,14 +96,14 @@ public sealed class SettingsCommand : ICommand {
|
||||||
|
|
||||||
var formattedValue = selectedSetting switch {
|
var formattedValue = selectedSetting switch {
|
||||||
"WelcomeMessage" => Utils.Wrap(Messages.DefaultWelcomeMessage),
|
"WelcomeMessage" => Utils.Wrap(Messages.DefaultWelcomeMessage),
|
||||||
"EventStartedReceivers" => Utils.Wrap(Boyfriend.DefaultConfig[selectedSetting])!,
|
"EventStartedReceivers" => Utils.Wrap(GuildData.DefaultPreferences[selectedSetting])!,
|
||||||
_ => value is "reset" or "default" ? Messages.SettingNotDefined
|
_ => value is "reset" or "default" ? Messages.SettingNotDefined
|
||||||
: IsBool(value) ? YesOrNo(value is "true")
|
: IsBool(value) ? YesOrNo(value is "true")
|
||||||
: string.Format(formatting, value)
|
: string.Format(formatting, value)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (value is "reset" or "default") {
|
if (value is "reset" or "default") {
|
||||||
config[selectedSetting] = Boyfriend.DefaultConfig[selectedSetting];
|
config[selectedSetting] = GuildData.DefaultPreferences[selectedSetting];
|
||||||
} else {
|
} else {
|
||||||
if (value == config[selectedSetting]) {
|
if (value == config[selectedSetting]) {
|
||||||
cmd.Reply(string.Format(Messages.SettingsNothingChanged, localizedSelectedSetting, formattedValue),
|
cmd.Reply(string.Format(Messages.SettingsNothingChanged, localizedSelectedSetting, formattedValue),
|
||||||
|
@ -129,13 +130,28 @@ public sealed class SettingsCommand : ICommand {
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedSetting is "MuteRole") Utils.RemoveMuteRoleFromCache(ulong.Parse(config[selectedSetting]));
|
if (selectedSetting.EndsWith("Offset") && !int.TryParse(value, out _)) {
|
||||||
|
cmd.Reply(Messages.InvalidSettingValue, ReplyEmojis.Error);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (selectedSetting) {
|
||||||
|
case "MuteRole":
|
||||||
|
data.MuteRole = guild.GetRole(mention);
|
||||||
|
break;
|
||||||
|
case "PublicFeedbackChannel":
|
||||||
|
data.PublicFeedbackChannel = guild.GetTextChannel(mention);
|
||||||
|
break;
|
||||||
|
case "PrivateFeedbackChannel":
|
||||||
|
data.PrivateFeedbackChannel = guild.GetTextChannel(mention);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
config[selectedSetting] = value;
|
config[selectedSetting] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedSetting is "Lang") {
|
if (selectedSetting is "Lang") {
|
||||||
Utils.SetCurrentLanguage(guild.Id);
|
Utils.SetCurrentLanguage(guild);
|
||||||
localizedSelectedSetting = Utils.GetMessage($"Settings{selectedSetting}");
|
localizedSelectedSetting = Utils.GetMessage($"Settings{selectedSetting}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ public sealed class UnbanCommand : ICommand {
|
||||||
if (reason is not null) await UnbanUserAsync(cmd, id.Value, reason);
|
if (reason is not null) await UnbanUserAsync(cmd, id.Value, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task UnbanUserAsync(CommandProcessor cmd, ulong id, string reason) {
|
private static async Task UnbanUserAsync(CommandProcessor cmd, ulong id, string reason) {
|
||||||
var requestOptions = Utils.GetRequestOptions($"({cmd.Context.User}) {reason}");
|
var requestOptions = Utils.GetRequestOptions($"({cmd.Context.User}) {reason}");
|
||||||
await cmd.Context.Guild.RemoveBanAsync(id, requestOptions);
|
await cmd.Context.Guild.RemoveBanAsync(id, requestOptions);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Boyfriend.Data;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
|
||||||
|
@ -9,38 +10,25 @@ public sealed class UnmuteCommand : ICommand {
|
||||||
public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) {
|
||||||
if (!cmd.HasPermission(GuildPermission.ModerateMembers)) return;
|
if (!cmd.HasPermission(GuildPermission.ModerateMembers)) return;
|
||||||
|
|
||||||
var toUnmute = cmd.GetMember(args, cleanArgs, 0, "ToUnmute");
|
var toUnmute = cmd.GetMember(args, 0);
|
||||||
if (toUnmute is null) return;
|
if (toUnmute is null) return;
|
||||||
var reason = cmd.GetRemaining(args, 1, "UnmuteReason");
|
var reason = cmd.GetRemaining(args, 1, "UnmuteReason");
|
||||||
if (reason is not null && cmd.CanInteractWith(toUnmute, "Unmute"))
|
if (reason is not null && cmd.CanInteractWith(toUnmute, "Unmute"))
|
||||||
await UnmuteMemberAsync(cmd, toUnmute, reason);
|
await UnmuteMemberAsync(cmd, toUnmute, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task UnmuteMemberAsync(CommandProcessor cmd, SocketGuildUser toUnmute,
|
private static async Task UnmuteMemberAsync(CommandProcessor cmd, SocketGuildUser toUnmute,
|
||||||
string reason) {
|
string reason) {
|
||||||
var requestOptions = Utils.GetRequestOptions($"({cmd.Context.User}) {reason}");
|
var isMuted = await Utils.UnmuteMemberAsync(GuildData.Get(cmd.Context.Guild), cmd.Context.User.ToString(),
|
||||||
var role = Utils.GetMuteRole(cmd.Context.Guild);
|
toUnmute, reason);
|
||||||
|
|
||||||
if (role is not null && toUnmute.Roles.Contains(role)) {
|
if (!isMuted) {
|
||||||
var rolesRemoved = Boyfriend.GetRemovedRoles(cmd.Context.Guild.Id);
|
cmd.Reply(Messages.MemberNotMuted, ReplyEmojis.Error);
|
||||||
|
return;
|
||||||
if (rolesRemoved.TryGetValue(toUnmute.Id, out var unmutedRemovedRoles)) {
|
|
||||||
await toUnmute.AddRolesAsync(unmutedRemovedRoles);
|
|
||||||
rolesRemoved.Remove(toUnmute.Id);
|
|
||||||
cmd.ConfigWriteScheduled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await toUnmute.RemoveRoleAsync(role, requestOptions);
|
|
||||||
} else {
|
|
||||||
if (toUnmute.TimedOutUntil is null || toUnmute.TimedOutUntil.Value.ToUnixTimeSeconds() <
|
|
||||||
DateTimeOffset.Now.ToUnixTimeSeconds()) {
|
|
||||||
cmd.Reply(Messages.MemberNotMuted, ReplyEmojis.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await toUnmute.RemoveTimeOutAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.ConfigWriteScheduled = true;
|
||||||
|
|
||||||
var feedback = string.Format(Messages.FeedbackMemberUnmuted, toUnmute.Mention, Utils.Wrap(reason));
|
var feedback = string.Format(Messages.FeedbackMemberUnmuted, toUnmute.Mention, Utils.Wrap(reason));
|
||||||
cmd.Reply(feedback, ReplyEmojis.Unmuted);
|
cmd.Reply(feedback, ReplyEmojis.Unmuted);
|
||||||
cmd.Audit(feedback);
|
cmd.Audit(feedback);
|
||||||
|
|
142
Boyfriend/Data/GuildData.cs
Normal file
142
Boyfriend/Data/GuildData.cs
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
|
||||||
|
namespace Boyfriend.Data;
|
||||||
|
|
||||||
|
public record GuildData {
|
||||||
|
public static readonly Dictionary<string, string> DefaultPreferences = new() {
|
||||||
|
{ "Prefix", "!" },
|
||||||
|
{ "Lang", "en" },
|
||||||
|
{ "ReceiveStartupMessages", "false" },
|
||||||
|
{ "WelcomeMessage", "default" },
|
||||||
|
{ "SendWelcomeMessages", "true" },
|
||||||
|
{ "PublicFeedbackChannel", "0" },
|
||||||
|
{ "PrivateFeedbackChannel", "0" },
|
||||||
|
{ "StarterRole", "0" },
|
||||||
|
{ "MuteRole", "0" },
|
||||||
|
{ "RemoveRolesOnMute", "false" },
|
||||||
|
{ "ReturnRolesOnRejoin", "false" },
|
||||||
|
{ "EventStartedReceivers", "interested,role" },
|
||||||
|
{ "EventNotificationRole", "0" },
|
||||||
|
{ "EventNotificationChannel", "0" },
|
||||||
|
{ "EventEarlyNotificationOffset", "0" },
|
||||||
|
{ "AutoStartEvents", "false" }
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly ConcurrentDictionary<ulong, GuildData> GuildDataDictionary = new();
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions Options = new() {
|
||||||
|
IncludeFields = true,
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly string _configurationFile;
|
||||||
|
|
||||||
|
private readonly ulong _id;
|
||||||
|
|
||||||
|
public readonly List<ulong> EarlyNotifications = new();
|
||||||
|
|
||||||
|
public readonly Dictionary<ulong, MemberData> MemberData;
|
||||||
|
|
||||||
|
public readonly Dictionary<string, string> Preferences;
|
||||||
|
|
||||||
|
private SocketRole? _cachedMuteRole;
|
||||||
|
private SocketTextChannel? _cachedPrivateFeedbackChannel;
|
||||||
|
private SocketTextChannel? _cachedPublicFeedbackChannel;
|
||||||
|
|
||||||
|
[SuppressMessage("Performance", "CA1853:Unnecessary call to \'Dictionary.ContainsKey(key)\'")]
|
||||||
|
// https://github.com/dotnet/roslyn-analyzers/issues/6377
|
||||||
|
private GuildData(SocketGuild guild) {
|
||||||
|
_id = guild.Id;
|
||||||
|
var idString = $"{_id}";
|
||||||
|
var memberDataDir = $"{_id}/MemberData";
|
||||||
|
_configurationFile = $"{_id}/Configuration.json";
|
||||||
|
if (!Directory.Exists(idString)) Directory.CreateDirectory(idString);
|
||||||
|
if (!Directory.Exists(memberDataDir)) Directory.CreateDirectory(memberDataDir);
|
||||||
|
if (!File.Exists(_configurationFile)) File.WriteAllText(_configurationFile, "{}");
|
||||||
|
Preferences
|
||||||
|
= JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(_configurationFile)) ??
|
||||||
|
new Dictionary<string, string>();
|
||||||
|
|
||||||
|
if (Preferences.Keys.Count < DefaultPreferences.Keys.Count)
|
||||||
|
foreach (var key in DefaultPreferences.Keys.Where(key => !Preferences.ContainsKey(key)))
|
||||||
|
Preferences.Add(key, DefaultPreferences[key]);
|
||||||
|
if (Preferences.Keys.Count > DefaultPreferences.Keys.Count)
|
||||||
|
foreach (var key in Preferences.Keys.Where(key => !DefaultPreferences.ContainsKey(key)))
|
||||||
|
Preferences.Remove(key);
|
||||||
|
Preferences.TrimExcess();
|
||||||
|
|
||||||
|
MemberData = new Dictionary<ulong, MemberData>();
|
||||||
|
foreach (var data in Directory.GetFiles(memberDataDir)) {
|
||||||
|
var deserialised
|
||||||
|
= JsonSerializer.Deserialize<MemberData>(File.ReadAllText(data), Options);
|
||||||
|
MemberData.Add(deserialised!.Id, deserialised);
|
||||||
|
}
|
||||||
|
|
||||||
|
guild.DownloadUsersAsync().Wait();
|
||||||
|
foreach (var member in guild.Users.Where(user => !user.IsBot)) {
|
||||||
|
if (MemberData.TryGetValue(member.Id, out var memberData)) {
|
||||||
|
if (!memberData.IsInGuild &&
|
||||||
|
DateTimeOffset.Now.ToUnixTimeSeconds() -
|
||||||
|
Math.Max(memberData.LeftAt.Last().ToUnixTimeSeconds(),
|
||||||
|
memberData.BannedUntil?.ToUnixTimeSeconds() ?? 0) >
|
||||||
|
60 * 60 * 24 * 30) {
|
||||||
|
File.Delete($"{_id}/MemberData/{memberData.Id}.json");
|
||||||
|
MemberData.Remove(memberData.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemberData.Add(member.Id, new MemberData(member));
|
||||||
|
}
|
||||||
|
|
||||||
|
MemberData.TrimExcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketRole? MuteRole {
|
||||||
|
get {
|
||||||
|
if (Preferences["MuteRole"] is "0") return null;
|
||||||
|
return _cachedMuteRole ??= Boyfriend.Client.GetGuild(_id).Roles
|
||||||
|
.Single(x => x.Id == ulong.Parse(Preferences["MuteRole"]));
|
||||||
|
}
|
||||||
|
set => _cachedMuteRole = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketTextChannel? PublicFeedbackChannel {
|
||||||
|
get {
|
||||||
|
if (Preferences["PublicFeedbackChannel"] is "0") return null;
|
||||||
|
return _cachedPublicFeedbackChannel ??= Boyfriend.Client.GetGuild(_id).TextChannels
|
||||||
|
.Single(x => x.Id == ulong.Parse(Preferences["PublicFeedbackChannel"]));
|
||||||
|
}
|
||||||
|
set => _cachedPublicFeedbackChannel = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketTextChannel? PrivateFeedbackChannel {
|
||||||
|
get {
|
||||||
|
if (Preferences["PublicFeedbackChannel"] is "0") return null;
|
||||||
|
return _cachedPrivateFeedbackChannel ??= Boyfriend.Client.GetGuild(_id).TextChannels
|
||||||
|
.Single(x => x.Id == ulong.Parse(Preferences["PrivateFeedbackChannel"]));
|
||||||
|
}
|
||||||
|
set => _cachedPrivateFeedbackChannel = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GuildData Get(SocketGuild guild) {
|
||||||
|
if (GuildDataDictionary.TryGetValue(guild.Id, out var stored)) return stored;
|
||||||
|
var newData = new GuildData(guild);
|
||||||
|
while (!GuildDataDictionary.ContainsKey(guild.Id)) GuildDataDictionary.TryAdd(guild.Id, newData);
|
||||||
|
return newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Save(bool saveMemberData) {
|
||||||
|
Preferences.TrimExcess();
|
||||||
|
await File.WriteAllTextAsync(_configurationFile,
|
||||||
|
JsonSerializer.Serialize(Preferences));
|
||||||
|
if (saveMemberData)
|
||||||
|
foreach (var data in MemberData.Values)
|
||||||
|
await File.WriteAllTextAsync($"{_id}/MemberData/{data.Id}.json",
|
||||||
|
JsonSerializer.Serialize(data, Options));
|
||||||
|
}
|
||||||
|
}
|
38
Boyfriend/Data/MemberData.cs
Normal file
38
Boyfriend/Data/MemberData.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Discord;
|
||||||
|
|
||||||
|
namespace Boyfriend.Data;
|
||||||
|
|
||||||
|
public record MemberData {
|
||||||
|
public DateTimeOffset? BannedUntil;
|
||||||
|
public ulong Id;
|
||||||
|
public bool IsInGuild;
|
||||||
|
public List<DateTimeOffset> JoinedAt;
|
||||||
|
public List<DateTimeOffset> LeftAt;
|
||||||
|
public DateTimeOffset? MutedUntil;
|
||||||
|
public List<Reminder> Reminders;
|
||||||
|
public List<ulong> Roles;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public MemberData(DateTimeOffset? bannedUntil, ulong id, bool isInGuild, List<DateTimeOffset> joinedAt,
|
||||||
|
List<DateTimeOffset> leftAt, DateTimeOffset? mutedUntil, List<Reminder> reminders, List<ulong> roles) {
|
||||||
|
BannedUntil = bannedUntil;
|
||||||
|
Id = id;
|
||||||
|
IsInGuild = isInGuild;
|
||||||
|
JoinedAt = joinedAt;
|
||||||
|
LeftAt = leftAt;
|
||||||
|
MutedUntil = mutedUntil;
|
||||||
|
Reminders = reminders;
|
||||||
|
Roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemberData(IGuildUser user) {
|
||||||
|
Id = user.Id;
|
||||||
|
IsInGuild = true;
|
||||||
|
JoinedAt = new List<DateTimeOffset> { user.JoinedAt!.Value };
|
||||||
|
LeftAt = new List<DateTimeOffset>();
|
||||||
|
Roles = user.RoleIds.ToList();
|
||||||
|
Roles.Remove(user.Guild.Id);
|
||||||
|
Reminders = new List<Reminder>();
|
||||||
|
}
|
||||||
|
}
|
7
Boyfriend/Data/Reminder.cs
Normal file
7
Boyfriend/Data/Reminder.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Boyfriend.Data;
|
||||||
|
|
||||||
|
public struct Reminder {
|
||||||
|
public DateTimeOffset RemindAt;
|
||||||
|
public string ReminderText;
|
||||||
|
public ulong ReminderChannel;
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Boyfriend.Data;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Rest;
|
using Discord.Rest;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
@ -14,20 +16,30 @@ public static class EventHandler {
|
||||||
Client.MessageReceived += MessageReceivedEvent;
|
Client.MessageReceived += MessageReceivedEvent;
|
||||||
Client.MessageUpdated += MessageUpdatedEvent;
|
Client.MessageUpdated += MessageUpdatedEvent;
|
||||||
Client.UserJoined += UserJoinedEvent;
|
Client.UserJoined += UserJoinedEvent;
|
||||||
|
Client.UserLeft += UserLeftEvent;
|
||||||
|
Client.GuildMemberUpdated += RolesUpdatedEvent;
|
||||||
Client.GuildScheduledEventCreated += ScheduledEventCreatedEvent;
|
Client.GuildScheduledEventCreated += ScheduledEventCreatedEvent;
|
||||||
Client.GuildScheduledEventCancelled += ScheduledEventCancelledEvent;
|
Client.GuildScheduledEventCancelled += ScheduledEventCancelledEvent;
|
||||||
Client.GuildScheduledEventStarted += ScheduledEventStartedEvent;
|
Client.GuildScheduledEventStarted += ScheduledEventStartedEvent;
|
||||||
Client.GuildScheduledEventCompleted += ScheduledEventCompletedEvent;
|
Client.GuildScheduledEventCompleted += ScheduledEventCompletedEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Task RolesUpdatedEvent(Cacheable<SocketGuildUser, ulong> oldUser, SocketGuildUser newUser) {
|
||||||
|
var data = GuildData.Get(newUser.Guild).MemberData[newUser.Id];
|
||||||
|
data.Roles = ((IGuildUser)newUser).RoleIds.ToList();
|
||||||
|
data.Roles.Remove(newUser.Guild.Id);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private static Task ReadyEvent() {
|
private static Task ReadyEvent() {
|
||||||
if (!_sendReadyMessages) return Task.CompletedTask;
|
if (!_sendReadyMessages) return Task.CompletedTask;
|
||||||
var i = Random.Shared.Next(3);
|
var i = Random.Shared.Next(3);
|
||||||
|
|
||||||
foreach (var guild in Client.Guilds) {
|
foreach (var guild in Client.Guilds) {
|
||||||
var config = Boyfriend.GetGuildConfig(guild.Id);
|
var data = GuildData.Get(guild);
|
||||||
var channel = guild.GetTextChannel(Utils.ParseMention(config["BotLogChannel"]));
|
var config = data.Preferences;
|
||||||
Utils.SetCurrentLanguage(guild.Id);
|
var channel = data.PrivateFeedbackChannel;
|
||||||
|
Utils.SetCurrentLanguage(guild);
|
||||||
|
|
||||||
if (config["ReceiveStartupMessages"] is not "true" || channel is null) continue;
|
if (config["ReceiveStartupMessages"] is not "true" || channel is null) continue;
|
||||||
_ = channel.SendMessageAsync(string.Format(Messages.Ready, Utils.GetBeep(i)));
|
_ = channel.SendMessageAsync(string.Format(Messages.Ready, Utils.GetBeep(i)));
|
||||||
|
@ -45,19 +57,20 @@ public static class EventHandler {
|
||||||
|
|
||||||
var guild = gChannel.Guild;
|
var guild = gChannel.Guild;
|
||||||
|
|
||||||
Utils.SetCurrentLanguage(guild.Id);
|
Utils.SetCurrentLanguage(guild);
|
||||||
|
|
||||||
var mention = msg.Author.Mention;
|
var mention = msg.Author.Mention;
|
||||||
|
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
|
|
||||||
var auditLogEntry = (await guild.GetAuditLogsAsync(1).FlattenAsync()).First();
|
var auditLogEntry = (await guild.GetAuditLogsAsync(1).FlattenAsync()).First();
|
||||||
if (auditLogEntry.Data is MessageDeleteAuditLogData data && msg.Author.Id == data.Target.Id)
|
if (auditLogEntry.CreatedAt >= DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(1)) &&
|
||||||
|
auditLogEntry.Data is MessageDeleteAuditLogData data && msg.Author.Id == data.Target.Id)
|
||||||
mention = auditLogEntry.User.Mention;
|
mention = auditLogEntry.User.Mention;
|
||||||
|
|
||||||
await Utils.SendFeedbackAsync(string.Format(Messages.CachedMessageDeleted, msg.Author.Mention,
|
await Utils.SendFeedbackAsync(string.Format(Messages.CachedMessageDeleted, msg.Author.Mention,
|
||||||
Utils.MentionChannel(channel.Id),
|
Utils.MentionChannel(channel.Id),
|
||||||
Utils.Wrap(msg.CleanContent)), guild.Id, mention);
|
Utils.Wrap(msg.CleanContent)), guild, mention);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task MessageReceivedEvent(IDeletable messageParam) {
|
private static Task MessageReceivedEvent(IDeletable messageParam) {
|
||||||
|
@ -67,7 +80,8 @@ public static class EventHandler {
|
||||||
"whoami" => message.ReplyAsync("`nobody`"),
|
"whoami" => message.ReplyAsync("`nobody`"),
|
||||||
"сука !!" => message.ReplyAsync("`root`"),
|
"сука !!" => message.ReplyAsync("`root`"),
|
||||||
"воооо" => message.ReplyAsync("`removing /...`"),
|
"воооо" => message.ReplyAsync("`removing /...`"),
|
||||||
"op ??" => message.ReplyAsync("некоторые пасхальные цитаты которые вы могли найти были легально взяты у <@573772175572729876>"),
|
"op ??" => message.ReplyAsync(
|
||||||
|
"некоторые пасхальные цитаты которые вы могли найти были легально взяты у <@573772175572729876>"),
|
||||||
_ => new CommandProcessor(message).HandleCommandAsync()
|
_ => new CommandProcessor(message).HandleCommandAsync()
|
||||||
};
|
};
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -80,34 +94,60 @@ public static class EventHandler {
|
||||||
msg.CleanContent == messageSocket.CleanContent || msg.Author.IsBot) return;
|
msg.CleanContent == messageSocket.CleanContent || msg.Author.IsBot) return;
|
||||||
|
|
||||||
var guild = gChannel.Guild;
|
var guild = gChannel.Guild;
|
||||||
Utils.SetCurrentLanguage(guild.Id);
|
Utils.SetCurrentLanguage(guild);
|
||||||
|
|
||||||
var isLimitedSpace = msg.CleanContent.Length + messageSocket.CleanContent.Length < 1940;
|
var isLimitedSpace = msg.CleanContent.Length + messageSocket.CleanContent.Length < 1940;
|
||||||
|
|
||||||
await Utils.SendFeedbackAsync(string.Format(Messages.CachedMessageEdited, Utils.MentionChannel(channel.Id),
|
await Utils.SendFeedbackAsync(string.Format(Messages.CachedMessageEdited, Utils.MentionChannel(channel.Id),
|
||||||
Utils.Wrap(msg.CleanContent, isLimitedSpace), Utils.Wrap(messageSocket.CleanContent, isLimitedSpace)),
|
Utils.Wrap(msg.CleanContent, isLimitedSpace), Utils.Wrap(messageSocket.CleanContent, isLimitedSpace)),
|
||||||
guild.Id, msg.Author.Mention);
|
guild, msg.Author.Mention);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task UserJoinedEvent(SocketGuildUser user) {
|
private static async Task UserJoinedEvent(SocketGuildUser user) {
|
||||||
|
if (user.IsBot) return;
|
||||||
var guild = user.Guild;
|
var guild = user.Guild;
|
||||||
var config = Boyfriend.GetGuildConfig(guild.Id);
|
var data = GuildData.Get(guild);
|
||||||
Utils.SetCurrentLanguage(guild.Id);
|
var config = data.Preferences;
|
||||||
|
Utils.SetCurrentLanguage(guild);
|
||||||
|
|
||||||
if (config["SendWelcomeMessages"] is "true")
|
if (config["SendWelcomeMessages"] is "true" && data.PublicFeedbackChannel is not null)
|
||||||
await Utils.SilentSendAsync(guild.SystemChannel,
|
await Utils.SilentSendAsync(data.PublicFeedbackChannel,
|
||||||
string.Format(config["WelcomeMessage"] is "default"
|
string.Format(config["WelcomeMessage"] is "default"
|
||||||
? Messages.DefaultWelcomeMessage
|
? Messages.DefaultWelcomeMessage
|
||||||
: config["WelcomeMessage"], user.Mention, guild.Name));
|
: config["WelcomeMessage"], user.Mention, guild.Name));
|
||||||
|
|
||||||
if (config["StarterRole"] is not "0") await user.AddRoleAsync(ulong.Parse(config["StarterRole"]));
|
if (config["StarterRole"] is not "0") await user.AddRoleAsync(ulong.Parse(config["StarterRole"]));
|
||||||
|
|
||||||
|
if (!data.MemberData.ContainsKey(user.Id)) data.MemberData.Add(user.Id, new MemberData(user));
|
||||||
|
var memberData = data.MemberData[user.Id];
|
||||||
|
memberData.IsInGuild = true;
|
||||||
|
memberData.BannedUntil = null;
|
||||||
|
if (memberData.LeftAt.Count > 0) {
|
||||||
|
if (memberData.JoinedAt.Contains(user.JoinedAt!.Value))
|
||||||
|
throw new UnreachableException();
|
||||||
|
memberData.JoinedAt.Add(user.JoinedAt!.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memberData.MutedUntil < DateTimeOffset.Now) {
|
||||||
|
if (data.MuteRole is not null)
|
||||||
|
await user.AddRoleAsync(data.MuteRole);
|
||||||
|
if (config["RemoveRolesOnMute"] is "false" && config["ReturnRolesOnRejoin"] is "true")
|
||||||
|
await user.AddRolesAsync(memberData.Roles);
|
||||||
|
} else if (config["ReturnRolesOnRejoin"] is "true") { await user.AddRolesAsync(memberData.Roles); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task UserLeftEvent(SocketGuild guild, SocketUser user) {
|
||||||
|
var data = GuildData.Get(guild).MemberData[user.Id];
|
||||||
|
data.IsInGuild = false;
|
||||||
|
data.LeftAt.Add(DateTimeOffset.Now);
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task ScheduledEventCreatedEvent(SocketGuildEvent scheduledEvent) {
|
private static async Task ScheduledEventCreatedEvent(SocketGuildEvent scheduledEvent) {
|
||||||
var guild = scheduledEvent.Guild;
|
var guild = scheduledEvent.Guild;
|
||||||
var eventConfig = Boyfriend.GetGuildConfig(guild.Id);
|
var eventConfig = GuildData.Get(guild).Preferences;
|
||||||
var channel = Utils.GetEventNotificationChannel(guild);
|
var channel = Utils.GetEventNotificationChannel(guild);
|
||||||
Utils.SetCurrentLanguage(guild.Id);
|
Utils.SetCurrentLanguage(guild);
|
||||||
|
|
||||||
if (channel is not null) {
|
if (channel is not null) {
|
||||||
var role = guild.GetRole(ulong.Parse(eventConfig["EventNotificationRole"]));
|
var role = guild.GetRole(ulong.Parse(eventConfig["EventNotificationRole"]));
|
||||||
|
@ -125,17 +165,13 @@ public static class EventHandler {
|
||||||
scheduledEvent.StartTime.ToUnixTimeSeconds().ToString(), descAndLink),
|
scheduledEvent.StartTime.ToUnixTimeSeconds().ToString(), descAndLink),
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventConfig["EventEarlyNotificationOffset"] is not "0")
|
|
||||||
_ = Utils.SendEarlyEventStartNotificationAsync(channel, scheduledEvent,
|
|
||||||
int.Parse(eventConfig["EventEarlyNotificationOffset"]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task ScheduledEventCancelledEvent(SocketGuildEvent scheduledEvent) {
|
private static async Task ScheduledEventCancelledEvent(SocketGuildEvent scheduledEvent) {
|
||||||
var guild = scheduledEvent.Guild;
|
var guild = scheduledEvent.Guild;
|
||||||
var eventConfig = Boyfriend.GetGuildConfig(guild.Id);
|
var eventConfig = GuildData.Get(guild).Preferences;
|
||||||
var channel = Utils.GetEventNotificationChannel(guild);
|
var channel = Utils.GetEventNotificationChannel(guild);
|
||||||
Utils.SetCurrentLanguage(guild.Id);
|
Utils.SetCurrentLanguage(guild);
|
||||||
if (channel is not null)
|
if (channel is not null)
|
||||||
await channel.SendMessageAsync(string.Format(Messages.EventCancelled, Utils.Wrap(scheduledEvent.Name),
|
await channel.SendMessageAsync(string.Format(Messages.EventCancelled, Utils.Wrap(scheduledEvent.Name),
|
||||||
eventConfig["FrowningFace"] is "true" ? $" {Messages.SettingsFrowningFace}" : ""));
|
eventConfig["FrowningFace"] is "true" ? $" {Messages.SettingsFrowningFace}" : ""));
|
||||||
|
@ -143,9 +179,9 @@ public static class EventHandler {
|
||||||
|
|
||||||
private static async Task ScheduledEventStartedEvent(SocketGuildEvent scheduledEvent) {
|
private static async Task ScheduledEventStartedEvent(SocketGuildEvent scheduledEvent) {
|
||||||
var guild = scheduledEvent.Guild;
|
var guild = scheduledEvent.Guild;
|
||||||
var eventConfig = Boyfriend.GetGuildConfig(guild.Id);
|
var eventConfig = GuildData.Get(guild).Preferences;
|
||||||
var channel = Utils.GetEventNotificationChannel(guild);
|
var channel = Utils.GetEventNotificationChannel(guild);
|
||||||
Utils.SetCurrentLanguage(guild.Id);
|
Utils.SetCurrentLanguage(guild);
|
||||||
|
|
||||||
if (channel is not null) {
|
if (channel is not null) {
|
||||||
var receivers = eventConfig["EventStartedReceivers"];
|
var receivers = eventConfig["EventStartedReceivers"];
|
||||||
|
@ -154,8 +190,9 @@ public static class EventHandler {
|
||||||
|
|
||||||
if (receivers.Contains("role") && role is not null) mentions.Append($"{role.Mention} ");
|
if (receivers.Contains("role") && role is not null) mentions.Append($"{role.Mention} ");
|
||||||
if (receivers.Contains("users") || receivers.Contains("interested"))
|
if (receivers.Contains("users") || receivers.Contains("interested"))
|
||||||
mentions = (await scheduledEvent.GetUsersAsync(15)).Aggregate(mentions,
|
mentions = (await scheduledEvent.GetUsersAsync(15))
|
||||||
(current, user) => current.Append($"{user.Mention} "));
|
.Where(user => role is null || !((RestGuildUser)user).RoleIds.Contains(role.Id))
|
||||||
|
.Aggregate(mentions, (current, user) => current.Append($"{user.Mention} "));
|
||||||
|
|
||||||
await channel.SendMessageAsync(string.Format(Messages.EventStarted, mentions,
|
await channel.SendMessageAsync(string.Format(Messages.EventStarted, mentions,
|
||||||
Utils.Wrap(scheduledEvent.Name),
|
Utils.Wrap(scheduledEvent.Name),
|
||||||
|
@ -167,9 +204,9 @@ public static class EventHandler {
|
||||||
private static async Task ScheduledEventCompletedEvent(SocketGuildEvent scheduledEvent) {
|
private static async Task ScheduledEventCompletedEvent(SocketGuildEvent scheduledEvent) {
|
||||||
var guild = scheduledEvent.Guild;
|
var guild = scheduledEvent.Guild;
|
||||||
var channel = Utils.GetEventNotificationChannel(guild);
|
var channel = Utils.GetEventNotificationChannel(guild);
|
||||||
Utils.SetCurrentLanguage(guild.Id);
|
Utils.SetCurrentLanguage(guild);
|
||||||
if (channel is not null)
|
if (channel is not null)
|
||||||
await channel.SendMessageAsync(string.Format(Messages.EventCompleted, Utils.Wrap(scheduledEvent.Name),
|
await channel.SendMessageAsync(string.Format(Messages.EventCompleted, Utils.Wrap(scheduledEvent.Name),
|
||||||
Utils.GetHumanizedTimeOffset(DateTimeOffset.Now.Subtract(scheduledEvent.StartTime))));
|
Utils.GetHumanizedTimeSpan(DateTimeOffset.Now.Subtract(scheduledEvent.StartTime))));
|
||||||
}
|
}
|
||||||
}
|
}
|
78
Boyfriend/Messages.Designer.cs
generated
78
Boyfriend/Messages.Designer.cs
generated
|
@ -284,6 +284,15 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Adds a reminder.
|
||||||
|
/// </summary>
|
||||||
|
internal static string CommandDescriptionRemind {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CommandDescriptionRemind", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Allows you to change certain preferences for this guild.
|
/// Looks up a localized string similar to Allows you to change certain preferences for this guild.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -492,7 +501,7 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to You need to specify a guild member instead of {0}!.
|
/// Looks up a localized string similar to You did not specify a member of this guild!.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string InvalidMember {
|
internal static string InvalidMember {
|
||||||
get {
|
get {
|
||||||
|
@ -609,11 +618,11 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to You need to specify a setting to change!.
|
/// Looks up a localized string similar to You need to specify reminder text!.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string MissingSetting {
|
internal static string MissingReminderText {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("MissingSetting", resourceCulture);
|
return ResourceManager.GetString("MissingReminderText", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -680,24 +689,6 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks up a localized string similar to I couldn't remove role {0} because of an error! {1}.
|
|
||||||
/// </summary>
|
|
||||||
internal static string RoleRemovalFailed {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("RoleRemovalFailed", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks up a localized string similar to Someone removed the mute role manually! I added back all roles that I removed during the mute.
|
|
||||||
/// </summary>
|
|
||||||
internal static string RolesReturned {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("RolesReturned", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to That setting doesn't exist!.
|
/// Looks up a localized string similar to That setting doesn't exist!.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -717,11 +708,11 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Bot log channel.
|
/// Looks up a localized string similar to Automatically start scheduled events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string SettingsBotLogChannel {
|
internal static string SettingsAutoStartEvents {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("SettingsBotLogChannel", resourceCulture);
|
return ResourceManager.GetString("SettingsAutoStartEvents", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -806,6 +797,24 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Channel for private notifications.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SettingsPrivateFeedbackChannel {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SettingsPrivateFeedbackChannel", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Channel for public notifications.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SettingsPublicFeedbackChannel {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SettingsPublicFeedbackChannel", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Receive startup messages.
|
/// Looks up a localized string similar to Receive startup messages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -824,6 +833,15 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Return roles on rejoin.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SettingsReturnRolesOnRejoin {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SettingsReturnRolesOnRejoin", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Send welcome messages.
|
/// Looks up a localized string similar to Send welcome messages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1050,11 +1068,11 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to The specified user is not a member of this server!.
|
/// Looks up a localized string similar to I could not find this user in any guild I'm a member of! Check if the ID is correct and that the user was on this server no longer than 30 days ago.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string UserNotInGuild {
|
internal static string UserNotFound {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("UserNotInGuild", resourceCulture);
|
return ResourceManager.GetString("UserNotFound", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1068,7 +1086,7 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to You were banned by {0} in guild {1} for {2}.
|
/// Looks up a localized string similar to You were banned by {0} in guild `{1}` for {2}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string YouWereBanned {
|
internal static string YouWereBanned {
|
||||||
get {
|
get {
|
||||||
|
@ -1077,7 +1095,7 @@ namespace Boyfriend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to You were kicked by {0} in guild {1} for {2}.
|
/// Looks up a localized string similar to You were kicked by {0} in guild `{1}` for {2}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string YouWereKicked {
|
internal static string YouWereKicked {
|
||||||
get {
|
get {
|
||||||
|
|
|
@ -1,64 +1,64 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
<resheader name="writer">System.Resources.ResXResourceWriter, 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="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="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
<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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: 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: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:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
@ -135,43 +135,43 @@
|
||||||
<data name="Beep2" xml:space="preserve">
|
<data name="Beep2" xml:space="preserve">
|
||||||
<value>Bop! </value>
|
<value>Bop! </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Beep3" xml:space="preserve">
|
<data name="Beep3" xml:space="preserve">
|
||||||
<value>Beep! </value>
|
<value>Beep! </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandNoPermissionBot" xml:space="preserve">
|
<data name="CommandNoPermissionBot" xml:space="preserve">
|
||||||
<value>I do not have permission to execute this command!</value>
|
<value>I do not have permission to execute this command!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandNoPermissionUser" xml:space="preserve">
|
<data name="CommandNoPermissionUser" xml:space="preserve">
|
||||||
<value>You do not have permission to execute this command!</value>
|
<value>You do not have permission to execute this command!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="YouWereBanned" xml:space="preserve">
|
<data name="YouWereBanned" xml:space="preserve">
|
||||||
<value>You were banned by {0} in guild {1} for {2}</value>
|
<value>You were banned by {0} in guild `{1}` for {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PunishmentExpired" xml:space="preserve">
|
<data name="PunishmentExpired" xml:space="preserve">
|
||||||
<value>Punishment expired</value>
|
<value>Punishment expired</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearAmountTooSmall" xml:space="preserve">
|
<data name="ClearAmountTooSmall" xml:space="preserve">
|
||||||
<value>You specified less than {0} messages!</value>
|
<value>You specified less than {0} messages!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearAmountTooLarge" xml:space="preserve">
|
<data name="ClearAmountTooLarge" xml:space="preserve">
|
||||||
<value>You specified more than {0} messages!</value>
|
<value>You specified more than {0} messages!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandHelp" xml:space="preserve">
|
<data name="CommandHelp" xml:space="preserve">
|
||||||
<value>Command help:</value>
|
<value>Command help:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="YouWereKicked" xml:space="preserve">
|
<data name="YouWereKicked" xml:space="preserve">
|
||||||
<value>You were kicked by {0} in guild {1} for {2}</value>
|
<value>You were kicked by {0} in guild `{1}` for {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Milliseconds" xml:space="preserve">
|
<data name="Milliseconds" xml:space="preserve">
|
||||||
<value>ms</value>
|
<value>ms</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MemberAlreadyMuted" xml:space="preserve">
|
<data name="MemberAlreadyMuted" xml:space="preserve">
|
||||||
<value>Member is already muted!</value>
|
<value>Member is already muted!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChannelNotSpecified" xml:space="preserve">
|
<data name="ChannelNotSpecified" xml:space="preserve">
|
||||||
<value>Not specified</value>
|
<value>Not specified</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RoleNotSpecified" xml:space="preserve">
|
<data name="RoleNotSpecified" xml:space="preserve">
|
||||||
<value>Not specified</value>
|
<value>Not specified</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CurrentSettings" xml:space="preserve">
|
<data name="CurrentSettings" xml:space="preserve">
|
||||||
|
@ -189,16 +189,10 @@
|
||||||
<data name="SettingsSendWelcomeMessages" xml:space="preserve">
|
<data name="SettingsSendWelcomeMessages" xml:space="preserve">
|
||||||
<value>Send welcome messages</value>
|
<value>Send welcome messages</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsStarterRole" xml:space="preserve">
|
<data name="SettingsMuteRole" xml:space="preserve">
|
||||||
<value>Starter role</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsMuteRole" xml:space="preserve">
|
|
||||||
<value>Mute role</value>
|
<value>Mute role</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsBotLogChannel" xml:space="preserve">
|
<data name="LanguageNotSupported" xml:space="preserve">
|
||||||
<value>Bot log channel</value>
|
|
||||||
</data>
|
|
||||||
<data name="LanguageNotSupported" xml:space="preserve">
|
|
||||||
<value>Language not supported! Supported languages:</value>
|
<value>Language not supported! Supported languages:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Yes" xml:space="preserve">
|
<data name="Yes" xml:space="preserve">
|
||||||
|
@ -213,10 +207,7 @@
|
||||||
<data name="MemberNotMuted" xml:space="preserve">
|
<data name="MemberNotMuted" xml:space="preserve">
|
||||||
<value>Member not muted!</value>
|
<value>Member not muted!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RolesReturned" xml:space="preserve">
|
<data name="SettingsWelcomeMessage" xml:space="preserve">
|
||||||
<value>Someone removed the mute role manually! I added back all roles that I removed during the mute</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsWelcomeMessage" xml:space="preserve">
|
|
||||||
<value>Welcome message</value>
|
<value>Welcome message</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearAmountInvalid" xml:space="preserve">
|
<data name="ClearAmountInvalid" xml:space="preserve">
|
||||||
|
@ -225,9 +216,6 @@
|
||||||
<data name="FeedbackUserBanned" xml:space="preserve">
|
<data name="FeedbackUserBanned" xml:space="preserve">
|
||||||
<value>Banned {0} for{1}: {2}</value>
|
<value>Banned {0} for{1}: {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserNotInGuild" xml:space="preserve">
|
|
||||||
<value>The specified user is not a member of this server!</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingDoesntExist" xml:space="preserve">
|
<data name="SettingDoesntExist" xml:space="preserve">
|
||||||
<value>That setting doesn't exist!</value>
|
<value>That setting doesn't exist!</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -243,10 +231,7 @@
|
||||||
<data name="InvalidChannel" xml:space="preserve">
|
<data name="InvalidChannel" xml:space="preserve">
|
||||||
<value>This channel does not exist!</value>
|
<value>This channel does not exist!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RoleRemovalFailed" xml:space="preserve">
|
<data name="DurationRequiredForTimeOuts" xml:space="preserve">
|
||||||
<value>I couldn't remove role {0} because of an error! {1}</value>
|
|
||||||
</data>
|
|
||||||
<data name="DurationRequiredForTimeOuts" xml:space="preserve">
|
|
||||||
<value>I cannot mute someone for more than 28 days using timeouts! Either specify a duration shorter than 28 days, or set a mute role in settings</value>
|
<value>I cannot mute someone for more than 28 days using timeouts! Either specify a duration shorter than 28 days, or set a mute role in settings</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CannotTimeOutBot" xml:space="preserve">
|
<data name="CannotTimeOutBot" xml:space="preserve">
|
||||||
|
@ -333,28 +318,28 @@
|
||||||
<data name="MissingNumber" xml:space="preserve">
|
<data name="MissingNumber" xml:space="preserve">
|
||||||
<value>You need to specify an integer from {0} to {1}!</value>
|
<value>You need to specify an integer from {0} to {1}!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingUser" xml:space="preserve">
|
<data name="MissingUser" xml:space="preserve">
|
||||||
<value>You need to specify a user!</value>
|
<value>You need to specify a user!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidUser" xml:space="preserve">
|
<data name="InvalidUser" xml:space="preserve">
|
||||||
<value>You need to specify a user instead of {0}!</value>
|
<value>You need to specify a user instead of {0}!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingMember" xml:space="preserve">
|
<data name="MissingMember" xml:space="preserve">
|
||||||
<value>You need to specify a guild member!</value>
|
<value>You need to specify a guild member!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidMember" xml:space="preserve">
|
<data name="InvalidMember" xml:space="preserve">
|
||||||
<value>You need to specify a guild member instead of {0}!</value>
|
<value>You did not specify a member of this guild!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotBanMembers" xml:space="preserve">
|
<data name="UserCannotBanMembers" xml:space="preserve">
|
||||||
<value>You cannot ban users from this guild!</value>
|
<value>You cannot ban users from this guild!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotManageMessages" xml:space="preserve">
|
<data name="UserCannotManageMessages" xml:space="preserve">
|
||||||
<value>You cannot manage messages in this guild!</value>
|
<value>You cannot manage messages in this guild!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotKickMembers" xml:space="preserve">
|
<data name="UserCannotKickMembers" xml:space="preserve">
|
||||||
<value>You cannot kick members from this guild!</value>
|
<value>You cannot kick members from this guild!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotModerateMembers" xml:space="preserve">
|
<data name="UserCannotModerateMembers" xml:space="preserve">
|
||||||
<value>You cannot moderate members in this guild!</value>
|
<value>You cannot moderate members in this guild!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotManageGuild" xml:space="preserve">
|
<data name="UserCannotManageGuild" xml:space="preserve">
|
||||||
|
@ -390,10 +375,7 @@
|
||||||
<data name="MissingUnmuteReason" xml:space="preserve">
|
<data name="MissingUnmuteReason" xml:space="preserve">
|
||||||
<value>You need to specify a reason for unmute this member!</value>
|
<value>You need to specify a reason for unmute this member!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingSetting" xml:space="preserve">
|
<data name="UserCannotBanOwner" xml:space="preserve">
|
||||||
<value>You need to specify a setting to change!</value>
|
|
||||||
</data>
|
|
||||||
<data name="UserCannotBanOwner" xml:space="preserve">
|
|
||||||
<value>You cannot ban the owner of this guild!</value>
|
<value>You cannot ban the owner of this guild!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotBanThemselves" xml:space="preserve">
|
<data name="UserCannotBanThemselves" xml:space="preserve">
|
||||||
|
@ -450,13 +432,37 @@
|
||||||
<data name="BotCannotUnmuteTarget" xml:space="preserve">
|
<data name="BotCannotUnmuteTarget" xml:space="preserve">
|
||||||
<value>I cannot unmute this member!</value>
|
<value>I cannot unmute this member!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotUnmuteTarget" xml:space="preserve">
|
<data name="UserCannotUnmuteTarget" xml:space="preserve">
|
||||||
<value>You cannot unmute this user!</value>
|
<value>You cannot unmute this user!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EventEarlyNotification" xml:space="preserve">
|
<data name="EventEarlyNotification" xml:space="preserve">
|
||||||
<value>{0}Event {1} will start <t:{2}:R>!</value>
|
<value>{0}Event {1} will start <t:{2}:R>!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsEventEarlyNotificationOffset" xml:space="preserve">
|
<data name="SettingsEventEarlyNotificationOffset" xml:space="preserve">
|
||||||
<value>Early event start notification offset</value>
|
<value>Early event start notification offset</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="UserNotFound" xml:space="preserve">
|
||||||
|
<value>I could not find this user in any guild I'm a member of! Check if the ID is correct and that the user was on this server no longer than 30 days ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsStarterRole" xml:space="preserve">
|
||||||
|
<value>Starter role</value>
|
||||||
|
</data>
|
||||||
|
<data name="CommandDescriptionRemind" xml:space="preserve">
|
||||||
|
<value>Adds a reminder</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPublicFeedbackChannel" xml:space="preserve">
|
||||||
|
<value>Channel for public notifications</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPrivateFeedbackChannel" xml:space="preserve">
|
||||||
|
<value>Channel for private notifications</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsReturnRolesOnRejoin" xml:space="preserve">
|
||||||
|
<value>Return roles on rejoin</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsAutoStartEvents" xml:space="preserve">
|
||||||
|
<value>Automatically start scheduled events</value>
|
||||||
|
</data>
|
||||||
|
<data name="MissingReminderText" xml:space="preserve">
|
||||||
|
<value>You need to specify reminder text!</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -1,64 +1,64 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
<resheader name="writer">System.Resources.ResXResourceWriter, 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="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="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
<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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: 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: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:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
@ -135,43 +135,43 @@
|
||||||
<data name="Beep2" xml:space="preserve">
|
<data name="Beep2" xml:space="preserve">
|
||||||
<value>Боп! </value>
|
<value>Боп! </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Beep3" xml:space="preserve">
|
<data name="Beep3" xml:space="preserve">
|
||||||
<value>Бип! </value>
|
<value>Бип! </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandNoPermissionBot" xml:space="preserve">
|
<data name="CommandNoPermissionBot" xml:space="preserve">
|
||||||
<value>У меня недостаточно прав для выполнения этой команды!</value>
|
<value>У меня недостаточно прав для выполнения этой команды!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandNoPermissionUser" xml:space="preserve">
|
<data name="CommandNoPermissionUser" xml:space="preserve">
|
||||||
<value>У тебя недостаточно прав для выполнения этой команды!</value>
|
<value>У тебя недостаточно прав для выполнения этой команды!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="YouWereBanned" xml:space="preserve">
|
<data name="YouWereBanned" xml:space="preserve">
|
||||||
<value>Тебя забанил {0} на сервере {1} за {2}</value>
|
<value>Тебя забанил {0} на сервере `{1}` за {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PunishmentExpired" xml:space="preserve">
|
<data name="PunishmentExpired" xml:space="preserve">
|
||||||
<value>Время наказания истекло</value>
|
<value>Время наказания истекло</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearAmountTooSmall" xml:space="preserve">
|
<data name="ClearAmountTooSmall" xml:space="preserve">
|
||||||
<value>Указано менее {0} сообщений!</value>
|
<value>Указано менее {0} сообщений!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearAmountTooLarge" xml:space="preserve">
|
<data name="ClearAmountTooLarge" xml:space="preserve">
|
||||||
<value>Указано более {0} сообщений!</value>
|
<value>Указано более {0} сообщений!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandHelp" xml:space="preserve">
|
<data name="CommandHelp" xml:space="preserve">
|
||||||
<value>Справка по командам:</value>
|
<value>Справка по командам:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="YouWereKicked" xml:space="preserve">
|
<data name="YouWereKicked" xml:space="preserve">
|
||||||
<value>Тебя кикнул {0} на сервере {1} за {2}</value>
|
<value>Тебя кикнул {0} на сервере `{1}` за {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Milliseconds" xml:space="preserve">
|
<data name="Milliseconds" xml:space="preserve">
|
||||||
<value>мс</value>
|
<value>мс</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MemberAlreadyMuted" xml:space="preserve">
|
<data name="MemberAlreadyMuted" xml:space="preserve">
|
||||||
<value>Участник уже заглушен!</value>
|
<value>Участник уже заглушен!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChannelNotSpecified" xml:space="preserve">
|
<data name="ChannelNotSpecified" xml:space="preserve">
|
||||||
<value>Не указан</value>
|
<value>Не указан</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RoleNotSpecified" xml:space="preserve">
|
<data name="RoleNotSpecified" xml:space="preserve">
|
||||||
<value>Не указана</value>
|
<value>Не указана</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CurrentSettings" xml:space="preserve">
|
<data name="CurrentSettings" xml:space="preserve">
|
||||||
|
@ -192,10 +192,7 @@
|
||||||
<data name="SettingsMuteRole" xml:space="preserve">
|
<data name="SettingsMuteRole" xml:space="preserve">
|
||||||
<value>Роль мута</value>
|
<value>Роль мута</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsBotLogChannel" xml:space="preserve">
|
<data name="LanguageNotSupported" xml:space="preserve">
|
||||||
<value>Канал бот-уведомлений</value>
|
|
||||||
</data>
|
|
||||||
<data name="LanguageNotSupported" xml:space="preserve">
|
|
||||||
<value>Язык не поддерживается! Поддерживаемые языки:</value>
|
<value>Язык не поддерживается! Поддерживаемые языки:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Yes" xml:space="preserve">
|
<data name="Yes" xml:space="preserve">
|
||||||
|
@ -210,10 +207,7 @@
|
||||||
<data name="MemberNotMuted" xml:space="preserve">
|
<data name="MemberNotMuted" xml:space="preserve">
|
||||||
<value>Участник не заглушен!</value>
|
<value>Участник не заглушен!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RolesReturned" xml:space="preserve">
|
<data name="SettingsWelcomeMessage" xml:space="preserve">
|
||||||
<value>Кто-то убрал роль мута самостоятельно! Я вернул все роли, которые забрал при муте</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsWelcomeMessage" xml:space="preserve">
|
|
||||||
<value>Приветствие</value>
|
<value>Приветствие</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearAmountInvalid" xml:space="preserve">
|
<data name="ClearAmountInvalid" xml:space="preserve">
|
||||||
|
@ -222,9 +216,6 @@
|
||||||
<data name="FeedbackUserBanned" xml:space="preserve">
|
<data name="FeedbackUserBanned" xml:space="preserve">
|
||||||
<value>Забанен {0} на{1}: {2}</value>
|
<value>Забанен {0} на{1}: {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserNotInGuild" xml:space="preserve">
|
|
||||||
<value>Указанный пользователь не является участником этого сервера!</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingDoesntExist" xml:space="preserve">
|
<data name="SettingDoesntExist" xml:space="preserve">
|
||||||
<value>Такая настройка не существует!</value>
|
<value>Такая настройка не существует!</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -240,19 +231,13 @@
|
||||||
<data name="InvalidChannel" xml:space="preserve">
|
<data name="InvalidChannel" xml:space="preserve">
|
||||||
<value>Этот канал не существует!</value>
|
<value>Этот канал не существует!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RoleRemovalFailed" xml:space="preserve">
|
<data name="DurationRequiredForTimeOuts" xml:space="preserve">
|
||||||
<value>Я не смог забрать роль {0} в связи с ошибкой! {1}</value>
|
|
||||||
</data>
|
|
||||||
<data name="DurationRequiredForTimeOuts" xml:space="preserve">
|
|
||||||
<value>Я не могу заглушить кого-то на более чем 28 дней, используя тайм-ауты! Или укажи продолжительность менее 28 дней, или установи роль мута в настройках</value>
|
<value>Я не могу заглушить кого-то на более чем 28 дней, используя тайм-ауты! Или укажи продолжительность менее 28 дней, или установи роль мута в настройках</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CannotTimeOutBot" xml:space="preserve">
|
<data name="CannotTimeOutBot" xml:space="preserve">
|
||||||
<value>Я не могу использовать тайм-ауты на других ботах! Попробуй указать роль мута в настройках</value>
|
<value>Я не могу использовать тайм-ауты на других ботах! Попробуй указать роль мута в настройках</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsStarterRole" xml:space="preserve">
|
<data name="EventCreated" xml:space="preserve">
|
||||||
<value>Начальная роль</value>
|
|
||||||
</data>
|
|
||||||
<data name="EventCreated" xml:space="preserve">
|
|
||||||
<value>{0} создал событие {1}! Оно пройдёт в {2} и начнётся <t:{3}:R>!{4}</value>
|
<value>{0} создал событие {1}! Оно пройдёт в {2} и начнётся <t:{3}:R>!{4}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsEventNotificationRole" xml:space="preserve">
|
<data name="SettingsEventNotificationRole" xml:space="preserve">
|
||||||
|
@ -333,28 +318,28 @@
|
||||||
<data name="MissingNumber" xml:space="preserve">
|
<data name="MissingNumber" xml:space="preserve">
|
||||||
<value>Надо указать целое число от {0} до {1}!</value>
|
<value>Надо указать целое число от {0} до {1}!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingUser" xml:space="preserve">
|
<data name="MissingUser" xml:space="preserve">
|
||||||
<value>Надо указать пользователя!</value>
|
<value>Надо указать пользователя!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidUser" xml:space="preserve">
|
<data name="InvalidUser" xml:space="preserve">
|
||||||
<value>Надо указать пользователя вместо {0}!</value>
|
<value>Надо указать пользователя вместо {0}!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingMember" xml:space="preserve">
|
<data name="MissingMember" xml:space="preserve">
|
||||||
<value>Надо указать участника сервера!</value>
|
<value>Надо указать участника сервера!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidMember" xml:space="preserve">
|
<data name="InvalidMember" xml:space="preserve">
|
||||||
<value>Надо указать участника сервера вместо {0}!</value>
|
<value>Тебе надо указать участника этого сервера!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotBanMembers" xml:space="preserve">
|
<data name="UserCannotBanMembers" xml:space="preserve">
|
||||||
<value>Ты не можешь банить пользователей на этом сервере!</value>
|
<value>Ты не можешь банить пользователей на этом сервере!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotManageMessages" xml:space="preserve">
|
<data name="UserCannotManageMessages" xml:space="preserve">
|
||||||
<value>Ты не можешь управлять сообщениями этого сервера!</value>
|
<value>Ты не можешь управлять сообщениями этого сервера!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotKickMembers" xml:space="preserve">
|
<data name="UserCannotKickMembers" xml:space="preserve">
|
||||||
<value>Ты не можешь выгонять участников с этого сервера!</value>
|
<value>Ты не можешь выгонять участников с этого сервера!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotModerateMembers" xml:space="preserve">
|
<data name="UserCannotModerateMembers" xml:space="preserve">
|
||||||
<value>Ты не можешь модерировать участников этого сервера!</value>
|
<value>Ты не можешь модерировать участников этого сервера!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotManageGuild" xml:space="preserve">
|
<data name="UserCannotManageGuild" xml:space="preserve">
|
||||||
|
@ -384,10 +369,7 @@
|
||||||
<data name="MissingMuteReason" xml:space="preserve">
|
<data name="MissingMuteReason" xml:space="preserve">
|
||||||
<value>Надо указать причину для мута этого участника!</value>
|
<value>Надо указать причину для мута этого участника!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingSetting" xml:space="preserve">
|
<data name="MissingUnbanReason" xml:space="preserve">
|
||||||
<value>Надо указать настройку, которую нужно изменить!</value>
|
|
||||||
</data>
|
|
||||||
<data name="MissingUnbanReason" xml:space="preserve">
|
|
||||||
<value>Надо указать причину для разбана этого пользователя!</value>
|
<value>Надо указать причину для разбана этого пользователя!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingUnmuteReason" xml:space="preserve">
|
<data name="MissingUnmuteReason" xml:space="preserve">
|
||||||
|
@ -450,13 +432,37 @@
|
||||||
<data name="UserCannotUnmuteTarget" xml:space="preserve">
|
<data name="UserCannotUnmuteTarget" xml:space="preserve">
|
||||||
<value>Ты не можешь вернуть из мута этого пользователя!</value>
|
<value>Ты не можешь вернуть из мута этого пользователя!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="BotCannotUnmuteTarget" xml:space="preserve">
|
<data name="BotCannotUnmuteTarget" xml:space="preserve">
|
||||||
<value>Я не могу вернуть из мута этого пользователя!</value>
|
<value>Я не могу вернуть из мута этого пользователя!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EventEarlyNotification" xml:space="preserve">
|
<data name="EventEarlyNotification" xml:space="preserve">
|
||||||
<value>{0}Событие {1} начнется <t:{2}:R>!</value>
|
<value>{0}Событие {1} начнется <t:{2}:R>!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsEventEarlyNotificationOffset" xml:space="preserve">
|
<data name="SettingsEventEarlyNotificationOffset" xml:space="preserve">
|
||||||
<value>Офсет отправки преждевременного уведомления о начале события</value>
|
<value>Офсет отправки преждевременного уведомления о начале события</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="UserNotFound" xml:space="preserve">
|
||||||
|
<value>Я не смог найти этого пользователя ни в одном из серверов, в которых я есть. Проверь правильность ID и нахождение пользователя на этом сервере максимум 30 дней назад</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsStarterRole" xml:space="preserve">
|
||||||
|
<value>Начальная роль</value>
|
||||||
|
</data>
|
||||||
|
<data name="CommandDescriptionRemind" xml:space="preserve">
|
||||||
|
<value>Добавляет напоминание</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPublicFeedbackChannel" xml:space="preserve">
|
||||||
|
<value>Канал для публичных уведомлений</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPrivateFeedbackChannel" xml:space="preserve">
|
||||||
|
<value>Канал для приватных уведомлений</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsReturnRolesOnRejoin" xml:space="preserve">
|
||||||
|
<value>Возвращать роли при перезаходе</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsAutoStartEvents" xml:space="preserve">
|
||||||
|
<value>Автоматически начинать события</value>
|
||||||
|
</data>
|
||||||
|
<data name="MissingReminderText" xml:space="preserve">
|
||||||
|
<value>Тебе нужно указать текст напоминания!</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -1,64 +1,64 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
<resheader name="writer">System.Resources.ResXResourceWriter, 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="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="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
<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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: 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: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:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
@ -135,43 +135,43 @@
|
||||||
<data name="Beep2" xml:space="preserve">
|
<data name="Beep2" xml:space="preserve">
|
||||||
<value>брох! </value>
|
<value>брох! </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Beep3" xml:space="preserve">
|
<data name="Beep3" xml:space="preserve">
|
||||||
<value>брух! </value>
|
<value>брух! </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandNoPermissionBot" xml:space="preserve">
|
<data name="CommandNoPermissionBot" xml:space="preserve">
|
||||||
<value>у меня прав нету, сделай что нибудь.</value>
|
<value>у меня прав нету, сделай что нибудь.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandNoPermissionUser" xml:space="preserve">
|
<data name="CommandNoPermissionUser" xml:space="preserve">
|
||||||
<value>у тебя прав нету, твои проблемы.</value>
|
<value>у тебя прав нету, твои проблемы.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="YouWereBanned" xml:space="preserve">
|
<data name="YouWereBanned" xml:space="preserve">
|
||||||
<value>здарова, тебя крч забанил {0} на сервере {1} за {2}</value>
|
<value>здарова, тебя крч забанил {0} на сервере `{1}` за {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PunishmentExpired" xml:space="preserve">
|
<data name="PunishmentExpired" xml:space="preserve">
|
||||||
<value>время бана закончиловсь</value>
|
<value>время бана закончиловсь</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearAmountTooSmall" xml:space="preserve">
|
<data name="ClearAmountTooSmall" xml:space="preserve">
|
||||||
<value>ты выбрал менее {0} сообщений</value>
|
<value>ты выбрал менее {0} сообщений</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearAmountTooLarge" xml:space="preserve">
|
<data name="ClearAmountTooLarge" xml:space="preserve">
|
||||||
<value>ты выбрал более {0} сообщений</value>
|
<value>ты выбрал более {0} сообщений</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CommandHelp" xml:space="preserve">
|
<data name="CommandHelp" xml:space="preserve">
|
||||||
<value>туториал по приколам:</value>
|
<value>туториал по приколам:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="YouWereKicked" xml:space="preserve">
|
<data name="YouWereKicked" xml:space="preserve">
|
||||||
<value>здарова, тебя крч кикнул {0} на сервере {1} за {2}</value>
|
<value>здарова, тебя крч кикнул {0} на сервере `{1}` за {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Milliseconds" xml:space="preserve">
|
<data name="Milliseconds" xml:space="preserve">
|
||||||
<value>мс</value>
|
<value>мс</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MemberAlreadyMuted" xml:space="preserve">
|
<data name="MemberAlreadyMuted" xml:space="preserve">
|
||||||
<value>шизоид уже замучен!</value>
|
<value>шизоид уже замучен!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChannelNotSpecified" xml:space="preserve">
|
<data name="ChannelNotSpecified" xml:space="preserve">
|
||||||
<value>*тут ничего нет*</value>
|
<value>*тут ничего нет*</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RoleNotSpecified" xml:space="preserve">
|
<data name="RoleNotSpecified" xml:space="preserve">
|
||||||
<value>*тут ничего нет*</value>
|
<value>*тут ничего нет*</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CurrentSettings" xml:space="preserve">
|
<data name="CurrentSettings" xml:space="preserve">
|
||||||
|
@ -192,10 +192,7 @@
|
||||||
<data name="SettingsMuteRole" xml:space="preserve">
|
<data name="SettingsMuteRole" xml:space="preserve">
|
||||||
<value>роль замученного</value>
|
<value>роль замученного</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsBotLogChannel" xml:space="preserve">
|
<data name="LanguageNotSupported" xml:space="preserve">
|
||||||
<value>канал бот-уведомлений</value>
|
|
||||||
</data>
|
|
||||||
<data name="LanguageNotSupported" xml:space="preserve">
|
|
||||||
<value>такого языка нету, ты шо, есть только такие:</value>
|
<value>такого языка нету, ты шо, есть только такие:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Yes" xml:space="preserve">
|
<data name="Yes" xml:space="preserve">
|
||||||
|
@ -210,10 +207,7 @@
|
||||||
<data name="MemberNotMuted" xml:space="preserve">
|
<data name="MemberNotMuted" xml:space="preserve">
|
||||||
<value>шизоид не замучен!</value>
|
<value>шизоид не замучен!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RolesReturned" xml:space="preserve">
|
<data name="SettingsWelcomeMessage" xml:space="preserve">
|
||||||
<value>кто-то решил поумничать и обошел роль мута. я ее вернул.</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsWelcomeMessage" xml:space="preserve">
|
|
||||||
<value>приветствие</value>
|
<value>приветствие</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearAmountInvalid" xml:space="preserve">
|
<data name="ClearAmountInvalid" xml:space="preserve">
|
||||||
|
@ -222,9 +216,6 @@
|
||||||
<data name="FeedbackUserBanned" xml:space="preserve">
|
<data name="FeedbackUserBanned" xml:space="preserve">
|
||||||
<value>забанен {0} на{1}: {2}</value>
|
<value>забанен {0} на{1}: {2}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserNotInGuild" xml:space="preserve">
|
|
||||||
<value>шизик не на этом сервере</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingDoesntExist" xml:space="preserve">
|
<data name="SettingDoesntExist" xml:space="preserve">
|
||||||
<value>такой прикол не существует</value>
|
<value>такой прикол не существует</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -240,19 +231,13 @@
|
||||||
<data name="InvalidChannel" xml:space="preserve">
|
<data name="InvalidChannel" xml:space="preserve">
|
||||||
<value>этого канала нету, ты шо</value>
|
<value>этого канала нету, ты шо</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RoleRemovalFailed" xml:space="preserve">
|
<data name="DurationRequiredForTimeOuts" xml:space="preserve">
|
||||||
<value>я не украл звание {0} в связи с ошибкой! {1}</value>
|
|
||||||
</data>
|
|
||||||
<data name="DurationRequiredForTimeOuts" xml:space="preserve">
|
|
||||||
<value>ты шо, мутить больше чем на 28 дней таймаут не разрешает, вот настроишь роль мута, тогда поговорим</value>
|
<value>ты шо, мутить больше чем на 28 дней таймаут не разрешает, вот настроишь роль мута, тогда поговорим</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CannotTimeOutBot" xml:space="preserve">
|
<data name="CannotTimeOutBot" xml:space="preserve">
|
||||||
<value>я не могу замутить ботов, сделай что нибудь</value>
|
<value>я не могу замутить ботов, сделай что нибудь</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsStarterRole" xml:space="preserve">
|
<data name="EventCreated" xml:space="preserve">
|
||||||
<value>базовое звание</value>
|
|
||||||
</data>
|
|
||||||
<data name="EventCreated" xml:space="preserve">
|
|
||||||
<value>{0} приготовил новый квест {1}! он пройдёт в {2} и начнётся <t:{3}:R>!{4}</value>
|
<value>{0} приготовил новый квест {1}! он пройдёт в {2} и начнётся <t:{3}:R>!{4}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsEventNotificationRole" xml:space="preserve">
|
<data name="SettingsEventNotificationRole" xml:space="preserve">
|
||||||
|
@ -333,28 +318,28 @@
|
||||||
<data name="MissingNumber" xml:space="preserve">
|
<data name="MissingNumber" xml:space="preserve">
|
||||||
<value>укажи целое число от {0} до {1}</value>
|
<value>укажи целое число от {0} до {1}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingUser" xml:space="preserve">
|
<data name="MissingUser" xml:space="preserve">
|
||||||
<value>укажи самого шизика</value>
|
<value>укажи самого шизика</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidUser" xml:space="preserve">
|
<data name="InvalidUser" xml:space="preserve">
|
||||||
<value>надо указать юзверя вместо {0}!</value>
|
<value>надо указать юзверя вместо {0}!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingMember" xml:space="preserve">
|
<data name="MissingMember" xml:space="preserve">
|
||||||
<value>укажи самого шизика</value>
|
<value>укажи самого шизика</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidMember" xml:space="preserve">
|
<data name="InvalidMember" xml:space="preserve">
|
||||||
<value>укажи шизоида сервера вместо {0}!</value>
|
<value>укажи шизоида сервера!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotBanMembers" xml:space="preserve">
|
<data name="UserCannotBanMembers" xml:space="preserve">
|
||||||
<value>бан</value>
|
<value>бан</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotManageMessages" xml:space="preserve">
|
<data name="UserCannotManageMessages" xml:space="preserve">
|
||||||
<value>тебе нельзя иметь власть над сообщениями шизоидов</value>
|
<value>тебе нельзя иметь власть над сообщениями шизоидов</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotKickMembers" xml:space="preserve">
|
<data name="UserCannotKickMembers" xml:space="preserve">
|
||||||
<value>кик шизиков нельзя</value>
|
<value>кик шизиков нельзя</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotModerateMembers" xml:space="preserve">
|
<data name="UserCannotModerateMembers" xml:space="preserve">
|
||||||
<value>тебе нельзя управлять шизоидами</value>
|
<value>тебе нельзя управлять шизоидами</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UserCannotManageGuild" xml:space="preserve">
|
<data name="UserCannotManageGuild" xml:space="preserve">
|
||||||
|
@ -384,10 +369,7 @@
|
||||||
<data name="MissingMuteReason" xml:space="preserve">
|
<data name="MissingMuteReason" xml:space="preserve">
|
||||||
<value>укажи зачем мутить шизика</value>
|
<value>укажи зачем мутить шизика</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingSetting" xml:space="preserve">
|
<data name="MissingUnbanReason" xml:space="preserve">
|
||||||
<value>укажи настройку которую менять нужно</value>
|
|
||||||
</data>
|
|
||||||
<data name="MissingUnbanReason" xml:space="preserve">
|
|
||||||
<value>укажи зачем раззабанивать шизика</value>
|
<value>укажи зачем раззабанивать шизика</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MissingUnmuteReason" xml:space="preserve">
|
<data name="MissingUnmuteReason" xml:space="preserve">
|
||||||
|
@ -450,13 +432,37 @@
|
||||||
<data name="UserCannotUnmuteTarget" xml:space="preserve">
|
<data name="UserCannotUnmuteTarget" xml:space="preserve">
|
||||||
<value>тебе нельзя раззамучивать</value>
|
<value>тебе нельзя раззамучивать</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="BotCannotUnmuteTarget" xml:space="preserve">
|
<data name="BotCannotUnmuteTarget" xml:space="preserve">
|
||||||
<value>я не могу его раззамутить...</value>
|
<value>я не могу его раззамутить...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EventEarlyNotification" xml:space="preserve">
|
<data name="EventEarlyNotification" xml:space="preserve">
|
||||||
<value>{0}квест {1} начнется <t:{2}:R>!</value>
|
<value>{0}квест {1} начнется <t:{2}:R>!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsEventEarlyNotificationOffset" xml:space="preserve">
|
<data name="SettingsEventEarlyNotificationOffset" xml:space="preserve">
|
||||||
<value>заранее пнуть в минутах до начала квеста</value>
|
<value>заранее пнуть в минутах до начала квеста</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="UserNotFound" xml:space="preserve">
|
||||||
|
<value>у нас такого шизоида нету, проверь, валиден ли ID уважаемого (я забываю о шизоидах если они ливнули минимум месяц назад)</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsStarterRole" xml:space="preserve">
|
||||||
|
<value>базовое звание</value>
|
||||||
|
</data>
|
||||||
|
<data name="CommandDescriptionRemind" xml:space="preserve">
|
||||||
|
<value>крафтит напоминалку</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPrivateFeedbackChannel" xml:space="preserve">
|
||||||
|
<value>канал для секретных уведомлений</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPublicFeedbackChannel" xml:space="preserve">
|
||||||
|
<value>канал для не секретных уведомлений</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsReturnRolesOnRejoin" xml:space="preserve">
|
||||||
|
<value>вернуть звания при переподключении в дурку</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsAutoStartEvents" xml:space="preserve">
|
||||||
|
<value>автоматом стартить квесты</value>
|
||||||
|
</data>
|
||||||
|
<data name="MissingReminderText" xml:space="preserve">
|
||||||
|
<value>для крафта напоминалки нужен текст</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -2,7 +2,6 @@ namespace Boyfriend;
|
||||||
|
|
||||||
public static class ReplyEmojis {
|
public static class ReplyEmojis {
|
||||||
public const string Success = ":white_check_mark:";
|
public const string Success = ":white_check_mark:";
|
||||||
public const string Warning = ":warning:";
|
|
||||||
public const string Error = ":x:";
|
public const string Error = ":x:";
|
||||||
public const string MissingArgument = ":keyboard:";
|
public const string MissingArgument = ":keyboard:";
|
||||||
public const string InvalidArgument = ":construction:";
|
public const string InvalidArgument = ":construction:";
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Globalization;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Boyfriend.Commands;
|
using Boyfriend.Data;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Net;
|
using Discord.Net;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
@ -12,15 +13,13 @@ using Humanizer.Localisation;
|
||||||
namespace Boyfriend;
|
namespace Boyfriend;
|
||||||
|
|
||||||
public static partial class Utils {
|
public static partial class Utils {
|
||||||
private static readonly Dictionary<string, string> ReflectionMessageCache = new();
|
|
||||||
|
|
||||||
public static readonly Dictionary<string, CultureInfo> CultureInfoCache = new() {
|
public static readonly Dictionary<string, CultureInfo> CultureInfoCache = new() {
|
||||||
{ "ru", new CultureInfo("ru-RU") },
|
{ "ru", new CultureInfo("ru-RU") },
|
||||||
{ "en", new CultureInfo("en-US") },
|
{ "en", new CultureInfo("en-US") },
|
||||||
{ "mctaylors-ru", new CultureInfo("tt-RU") }
|
{ "mctaylors-ru", new CultureInfo("tt-RU") }
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<ulong, SocketRole> MuteRoleCache = new();
|
private static readonly Dictionary<string, string> ReflectionMessageCache = new();
|
||||||
|
|
||||||
private static readonly AllowedMentions AllowRoles = new() {
|
private static readonly AllowedMentions AllowRoles = new() {
|
||||||
AllowedTypes = AllowedMentionTypes.Roles
|
AllowedTypes = AllowedMentionTypes.Roles
|
||||||
|
@ -30,11 +29,6 @@ public static partial class Utils {
|
||||||
return GetMessage($"Beep{(i < 0 ? Random.Shared.Next(3) + 1 : ++i)}");
|
return GetMessage($"Beep{(i < 0 ? Random.Shared.Next(3) + 1 : ++i)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SocketTextChannel? GetBotLogChannel(ulong id) {
|
|
||||||
return Boyfriend.Client.GetGuild(id)
|
|
||||||
.GetTextChannel(ParseMention(Boyfriend.GetGuildConfig(id)["BotLogChannel"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string? Wrap(string? original, bool limitedSpace = false) {
|
public static string? Wrap(string? original, bool limitedSpace = false) {
|
||||||
if (original is null) return null;
|
if (original is null) return null;
|
||||||
var maxChars = limitedSpace ? 970 : 1940;
|
var maxChars = limitedSpace ? 970 : 1940;
|
||||||
|
@ -57,26 +51,10 @@ public static partial class Utils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SocketRole? GetMuteRole(SocketGuild guild) {
|
|
||||||
var id = ulong.Parse(Boyfriend.GetGuildConfig(guild.Id)["MuteRole"]);
|
|
||||||
if (MuteRoleCache.TryGetValue(id, out var cachedMuteRole)) return cachedMuteRole;
|
|
||||||
foreach (var x in guild.Roles) {
|
|
||||||
if (x.Id != id) continue;
|
|
||||||
MuteRoleCache.Add(id, x);
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void RemoveMuteRoleFromCache(ulong id) {
|
|
||||||
MuteRoleCache.Remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SilentSendAsync(SocketTextChannel? channel, string text, bool allowRoles = false) {
|
public static async Task SilentSendAsync(SocketTextChannel? channel, string text, bool allowRoles = false) {
|
||||||
try {
|
try {
|
||||||
if (channel is null || text.Length is 0 or > 2000)
|
if (channel is null || text.Length is 0 or > 2000)
|
||||||
throw new Exception($"Message length is out of range: {text.Length}");
|
throw new UnreachableException($"Message length is out of range: {text.Length}");
|
||||||
|
|
||||||
await channel.SendMessageAsync(text, false, null, null, allowRoles ? AllowRoles : AllowedMentions.None);
|
await channel.SendMessageAsync(text, false, null, null, allowRoles ? AllowRoles : AllowedMentions.None);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -109,22 +87,23 @@ public static partial class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task
|
public static async Task
|
||||||
SendFeedbackAsync(string feedback, ulong guildId, string mention, bool sendPublic = false) {
|
SendFeedbackAsync(string feedback, SocketGuild guild, string mention, bool sendPublic = false) {
|
||||||
var adminChannel = GetBotLogChannel(guildId);
|
var data = GuildData.Get(guild);
|
||||||
var systemChannel = Boyfriend.Client.GetGuild(guildId).SystemChannel;
|
var adminChannel = data.PrivateFeedbackChannel;
|
||||||
|
var systemChannel = data.PublicFeedbackChannel;
|
||||||
var toSend = $"*[{mention}: {feedback}]*";
|
var toSend = $"*[{mention}: {feedback}]*";
|
||||||
if (adminChannel is not null) await SilentSendAsync(adminChannel, toSend);
|
if (adminChannel is not null) await SilentSendAsync(adminChannel, toSend);
|
||||||
if (sendPublic && systemChannel is not null) await SilentSendAsync(systemChannel, toSend);
|
if (sendPublic && systemChannel is not null) await SilentSendAsync(systemChannel, toSend);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetHumanizedTimeOffset(TimeSpan span) {
|
public static string GetHumanizedTimeSpan(TimeSpan span) {
|
||||||
return span.TotalSeconds > 0
|
return span.TotalSeconds < 1
|
||||||
? $" {span.Humanize(2, minUnit: TimeUnit.Second, maxUnit: TimeUnit.Month, culture: Messages.Culture.Name.Contains("RU") ? CultureInfoCache["ru"] : Messages.Culture)}"
|
? Messages.Ever
|
||||||
: Messages.Ever;
|
: $" {span.Humanize(2, minUnit: TimeUnit.Second, maxUnit: TimeUnit.Month, culture: Messages.Culture.Name.Contains("RU") ? CultureInfoCache["ru"] : Messages.Culture)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetCurrentLanguage(ulong guildId) {
|
public static void SetCurrentLanguage(SocketGuild guild) {
|
||||||
Messages.Culture = CultureInfoCache[Boyfriend.GetGuildConfig(guildId)["Lang"]];
|
Messages.Culture = CultureInfoCache[GuildData.Get(guild).Preferences["Lang"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SafeAppendToBuilder(StringBuilder appendTo, string appendWhat, SocketTextChannel? channel) {
|
public static void SafeAppendToBuilder(StringBuilder appendTo, string appendWhat, SocketTextChannel? channel) {
|
||||||
|
@ -146,48 +125,37 @@ public static partial class Utils {
|
||||||
appendTo.AppendLine(appendWhat);
|
appendTo.AppendLine(appendWhat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task DelayedUnbanAsync(CommandProcessor cmd, ulong banned, string reason, TimeSpan duration) {
|
|
||||||
await Task.Delay(duration);
|
|
||||||
SetCurrentLanguage(cmd.Context.Guild.Id);
|
|
||||||
await UnbanCommand.UnbanUserAsync(cmd, banned, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task DelayedUnmuteAsync(CommandProcessor cmd, SocketGuildUser muted, string reason,
|
|
||||||
TimeSpan duration) {
|
|
||||||
await Task.Delay(duration);
|
|
||||||
SetCurrentLanguage(cmd.Context.Guild.Id);
|
|
||||||
await UnmuteCommand.UnmuteMemberAsync(cmd, muted, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SendEarlyEventStartNotificationAsync(SocketTextChannel? channel,
|
|
||||||
SocketGuildEvent scheduledEvent, int minuteOffset) {
|
|
||||||
try {
|
|
||||||
await Task.Delay(scheduledEvent.StartTime.Subtract(DateTimeOffset.Now)
|
|
||||||
.Subtract(TimeSpan.FromMinutes(minuteOffset)));
|
|
||||||
var guild = scheduledEvent.Guild;
|
|
||||||
if (guild.GetEvent(scheduledEvent.Id) is null) return;
|
|
||||||
var eventConfig = Boyfriend.GetGuildConfig(guild.Id);
|
|
||||||
SetCurrentLanguage(guild.Id);
|
|
||||||
|
|
||||||
var receivers = eventConfig["EventStartedReceivers"];
|
|
||||||
var role = guild.GetRole(ulong.Parse(eventConfig["EventNotificationRole"]));
|
|
||||||
var mentions = Boyfriend.StringBuilder;
|
|
||||||
|
|
||||||
if (receivers.Contains("role") && role is not null) mentions.Append($"{role.Mention} ");
|
|
||||||
if (receivers.Contains("users") || receivers.Contains("interested"))
|
|
||||||
mentions = (await scheduledEvent.GetUsersAsync(15)).Aggregate(mentions,
|
|
||||||
(current, user) => current.Append($"{user.Mention} "));
|
|
||||||
await channel?.SendMessageAsync(string.Format(Messages.EventEarlyNotification, mentions,
|
|
||||||
Wrap(scheduledEvent.Name), scheduledEvent.StartTime.ToUnixTimeSeconds().ToString()))!;
|
|
||||||
mentions.Clear();
|
|
||||||
} catch (Exception e) {
|
|
||||||
await Boyfriend.Log(new LogMessage(LogSeverity.Error, nameof(Utils),
|
|
||||||
"Exception while sending early event start notification", e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SocketTextChannel? GetEventNotificationChannel(SocketGuild guild) {
|
public static SocketTextChannel? GetEventNotificationChannel(SocketGuild guild) {
|
||||||
return guild.GetTextChannel(ParseMention(Boyfriend.GetGuildConfig(guild.Id)["EventNotificationChannel"]));
|
return guild.GetTextChannel(ParseMention(GuildData.Get(guild)
|
||||||
|
.Preferences["EventNotificationChannel"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool UserExists(ulong id) {
|
||||||
|
return Boyfriend.Client.GetUser(id) is not null || UserInMemberData(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool UserInMemberData(ulong id) {
|
||||||
|
return GuildData.GuildDataDictionary.Values.Any(gData => gData.MemberData.Values.Any(mData => mData.Id == id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<bool> UnmuteMemberAsync(GuildData data, string modDiscrim, SocketGuildUser toUnmute,
|
||||||
|
string reason) {
|
||||||
|
var requestOptions = GetRequestOptions($"({modDiscrim}) {reason}");
|
||||||
|
var role = data.MuteRole;
|
||||||
|
|
||||||
|
if (role is not null) {
|
||||||
|
if (!toUnmute.Roles.Contains(role)) return false;
|
||||||
|
if (data.Preferences["RemoveRolesOnMute"] is "true")
|
||||||
|
await toUnmute.AddRolesAsync(data.MemberData[toUnmute.Id].Roles, requestOptions);
|
||||||
|
await toUnmute.RemoveRoleAsync(role, requestOptions);
|
||||||
|
data.MemberData[toUnmute.Id].MutedUntil = null;
|
||||||
|
} else {
|
||||||
|
if (toUnmute.TimedOutUntil is null || toUnmute.TimedOutUntil.Value < DateTimeOffset.Now) return false;
|
||||||
|
|
||||||
|
await toUnmute.RemoveTimeOutAsync(requestOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[GeneratedRegex("[^0-9]")]
|
[GeneratedRegex("[^0-9]")]
|
||||||
|
|
Reference in a new issue