mirror of
https://github.com/TeamOctolings/Octobot.git
synced 2025-04-19 16:33:36 +03:00
Keep adapting code to new guild data storage...
Fix #15, update InspectCode, make Dependabot use the correct label
This commit is contained in:
parent
fe2cfb3b3c
commit
4b2d98c440
16 changed files with 152 additions and 131 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
|
||||
- dependency-type: "all"
|
||||
assignees:
|
||||
- "l1ttleO"
|
||||
- "Octol1ttle"
|
||||
labels:
|
||||
- "type: dependencies"
|
||||
|
||||
- package-ecosystem: "nuget" # See documentation for possible values
|
||||
directory: "/Boyfriend" # Location of package manifests
|
||||
|
@ -24,4 +26,6 @@ updates:
|
|||
- dependency-type: "all"
|
||||
# Add 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
|
||||
|
||||
- name: ReSharper CLI InspectCode
|
||||
uses: muno92/resharper_inspectcode@1.6.0
|
||||
uses: muno92/resharper_inspectcode@1.6.6
|
||||
with:
|
||||
solutionPath: ./Boyfriend-CSharp.sln
|
||||
ignoreIssueType: InvertIf
|
||||
|
|
|
@ -112,19 +112,30 @@ public sealed class CommandProcessor {
|
|||
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) {
|
||||
Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingUser}",
|
||||
Context.Message);
|
||||
return null;
|
||||
}
|
||||
|
||||
var user = Boyfriend.Client.GetUser(Utils.ParseMention(args[index]));
|
||||
if (user is null && argument is not null)
|
||||
var mention = Utils.ParseMention(args[index]);
|
||||
if (mention is 0) {
|
||||
Utils.SafeAppendToBuilder(_stackedReplyMessage,
|
||||
$"{ReplyEmojis.InvalidArgument} {string.Format(Messages.InvalidUser, Utils.Wrap(cleanArgs[index]))}",
|
||||
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, (SocketUser?)Boyfriend.Client.GetUser(mention));
|
||||
}
|
||||
|
||||
public bool HasPermission(GuildPermission permission) {
|
||||
|
@ -135,7 +146,7 @@ public sealed class CommandProcessor {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!Context.Guild.GetUser(Context.User.Id).GuildPermissions.Has(permission)
|
||||
if (!GetMember().GuildPermissions.Has(permission)
|
||||
&& Context.Guild.OwnerId != Context.User.Id) {
|
||||
Utils.SafeAppendToBuilder(_stackedReplyMessage,
|
||||
$"{ReplyEmojis.NoPermission} {Utils.GetMessage($"UserCannot{permission}")}",
|
||||
|
@ -146,8 +157,12 @@ public sealed class CommandProcessor {
|
|||
return true;
|
||||
}
|
||||
|
||||
public SocketGuildUser? GetMember(SocketUser user) {
|
||||
return Context.Guild.GetUser(user.Id);
|
||||
private SocketGuildUser GetMember() {
|
||||
return GetMember(Context.User.Id)!;
|
||||
}
|
||||
|
||||
public SocketGuildUser? GetMember(ulong id) {
|
||||
return Context.Guild.GetUser(id);
|
||||
}
|
||||
|
||||
public SocketGuildUser? GetMember(string[] args, string[] cleanArgs, int index, string? argument) {
|
||||
|
@ -165,10 +180,6 @@ public sealed class CommandProcessor {
|
|||
return member;
|
||||
}
|
||||
|
||||
private SocketGuildUser GetMember() {
|
||||
return Context.Guild.GetUser(Context.User.Id);
|
||||
}
|
||||
|
||||
public ulong? GetBan(string[] args, int index) {
|
||||
if (index >= args.Length) {
|
||||
Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingUser}",
|
||||
|
|
|
@ -8,10 +8,10 @@ public sealed class BanCommand : ICommand {
|
|||
public string[] Aliases { get; } = { "ban", "бан" };
|
||||
|
||||
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;
|
||||
|
||||
var memberToBan = cmd.GetMember(toBan);
|
||||
var memberToBan = cmd.GetMember(toBan.Item1);
|
||||
if (memberToBan is not null && !cmd.CanInteractWith(memberToBan, "Ban")) return;
|
||||
|
||||
var duration = CommandProcessor.GetTimeSpan(args, 1);
|
||||
|
@ -19,21 +19,23 @@ public sealed class BanCommand : ICommand {
|
|||
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 guild = cmd.Context.Guild;
|
||||
await Utils.SendDirectMessage(toBan,
|
||||
string.Format(Messages.YouWereBanned, author.Mention, guild.Name, Utils.Wrap(reason)));
|
||||
if (toBan.Item2 is not null)
|
||||
await Utils.SendDirectMessage(toBan.Item2,
|
||||
string.Format(Messages.YouWereBanned, author.Mention, guild.Name, Utils.Wrap(reason)));
|
||||
|
||||
var guildBanMessage = $"({author}) {reason}";
|
||||
await guild.AddBanAsync(toBan, 0, guildBanMessage);
|
||||
await guild.AddBanAsync(toBan.Item1, 0, guildBanMessage);
|
||||
|
||||
var memberData = GuildData.FromSocketGuild(guild).MemberData[toBan.Id];
|
||||
var memberData = GuildData.FromSocketGuild(guild).MemberData[toBan.Item1];
|
||||
memberData.BannedUntil
|
||||
= duration.TotalSeconds < 1 ? -1 : DateTimeOffset.Now.Add(duration).ToUnixTimeSeconds();
|
||||
= duration.TotalSeconds < 1 ? DateTimeOffset.MaxValue : DateTimeOffset.Now.Add(duration);
|
||||
memberData.Roles.Clear();
|
||||
|
||||
var feedback = string.Format(Messages.FeedbackUserBanned, toBan.Mention,
|
||||
var feedback = string.Format(Messages.FeedbackUserBanned, $"<@{toBan.Item1.ToString()}>",
|
||||
Utils.GetHumanizedTimeOffset(duration), Utils.Wrap(reason));
|
||||
cmd.Reply(feedback, ReplyEmojis.Banned);
|
||||
cmd.Audit(feedback);
|
||||
|
|
|
@ -19,8 +19,8 @@ public sealed class MuteCommand : ICommand {
|
|||
|
||||
if ((role is not null && toMute.Roles.Contains(role))
|
||||
|| (toMute.TimedOutUntil is not null
|
||||
&& toMute.TimedOutUntil.Value.ToUnixTimeSeconds()
|
||||
> DateTimeOffset.Now.ToUnixTimeSeconds())) {
|
||||
&& toMute.TimedOutUntil.Value
|
||||
> DateTimeOffset.Now)) {
|
||||
cmd.Reply(Messages.MemberAlreadyMuted, ReplyEmojis.Error);
|
||||
return;
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ public sealed class MuteCommand : ICommand {
|
|||
|
||||
await toMute.AddRoleAsync(role, requestOptions);
|
||||
|
||||
data.MemberData[toMute.Id].MutedUntil = DateTimeOffset.Now.Add(duration).ToUnixTimeSeconds();
|
||||
data.MemberData[toMute.Id].MutedUntil = DateTimeOffset.Now.Add(duration);
|
||||
} else {
|
||||
if (!hasDuration || duration.TotalDays > 28) {
|
||||
cmd.Reply(Messages.DurationRequiredForTimeOuts, ReplyEmojis.Error);
|
||||
|
|
|
@ -10,7 +10,7 @@ public sealed class RemindCommand : ICommand {
|
|||
var reminderText = cmd.GetRemaining(args, 1, "ReminderText");
|
||||
if (reminderText is not null)
|
||||
GuildData.FromSocketGuild(cmd.Context.Guild).MemberData[cmd.Context.User.Id].Reminders.Add(new Reminder {
|
||||
RemindAt = DateTimeOffset.Now.Add(remindIn).ToUnixTimeSeconds(),
|
||||
RemindAt = DateTimeOffset.Now.Add(remindIn),
|
||||
ReminderText = reminderText
|
||||
});
|
||||
|
||||
|
|
|
@ -27,8 +27,7 @@ public sealed class UnmuteCommand : ICommand {
|
|||
await toUnmute.AddRolesAsync(data.MemberData[toUnmute.Id].Roles, requestOptions);
|
||||
await toUnmute.RemoveRoleAsync(role, requestOptions);
|
||||
} else {
|
||||
if (toUnmute.TimedOutUntil is null || toUnmute.TimedOutUntil.Value.ToUnixTimeSeconds() <
|
||||
DateTimeOffset.Now.ToUnixTimeSeconds()) {
|
||||
if (toUnmute.TimedOutUntil is null || toUnmute.TimedOutUntil.Value < DateTimeOffset.Now) {
|
||||
cmd.Reply(Messages.MemberNotMuted, ReplyEmojis.Error);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ public record GuildData {
|
|||
// TODO: { "AutoStartEvents", "false" }
|
||||
};
|
||||
|
||||
private static readonly Dictionary<ulong, GuildData> GuildDataDictionary = new();
|
||||
public static readonly Dictionary<ulong, GuildData> GuildDataDictionary = new();
|
||||
|
||||
public readonly Dictionary<ulong, MemberData> MemberData;
|
||||
|
||||
|
@ -44,15 +44,12 @@ public record GuildData {
|
|||
= JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText($"{_id}/Configuration.json")) ??
|
||||
new Dictionary<string, string>();
|
||||
|
||||
// ReSharper disable twice ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
|
||||
if (Preferences.Keys.Count < DefaultPreferences.Keys.Count)
|
||||
foreach (var key in DefaultPreferences.Keys)
|
||||
if (!Preferences.ContainsKey(key))
|
||||
Preferences.Add(key, DefaultPreferences[key]);
|
||||
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)
|
||||
if (!DefaultPreferences.ContainsKey(key))
|
||||
Preferences.Remove(key);
|
||||
foreach (var key in Preferences.Keys.Where(key => !DefaultPreferences.ContainsKey(key)))
|
||||
Preferences.Remove(key);
|
||||
Preferences.TrimExcess();
|
||||
|
||||
MemberData = new Dictionary<ulong, MemberData>();
|
||||
|
@ -67,7 +64,7 @@ public record GuildData {
|
|||
if (MemberData.TryGetValue(member.Id, out var memberData)) {
|
||||
if (!memberData.IsInGuild &&
|
||||
DateTimeOffset.Now.ToUnixTimeSeconds() -
|
||||
Math.Max(memberData.LeftAt.Last(), memberData.BannedUntil) >
|
||||
Math.Max(memberData.LeftAt.Last().ToUnixTimeSeconds(), memberData.BannedUntil.ToUnixTimeSeconds()) >
|
||||
60 * 60 * 24 * 30) {
|
||||
File.Delete($"{_id}/MemberData/{memberData.Id}.json");
|
||||
MemberData.Remove(memberData.Id);
|
||||
|
@ -83,8 +80,11 @@ public record GuildData {
|
|||
}
|
||||
|
||||
public SocketRole? MuteRole {
|
||||
get => _cachedMuteRole ??= Boyfriend.Client.GetGuild(_id).Roles
|
||||
.Single(x => x.Id == ulong.Parse(Preferences["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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,23 +3,23 @@
|
|||
namespace Boyfriend.Data;
|
||||
|
||||
public record MemberData {
|
||||
public long BannedUntil;
|
||||
public DateTimeOffset BannedUntil;
|
||||
public ulong Id;
|
||||
public bool IsInGuild;
|
||||
public List<long> JoinedAt;
|
||||
public List<long> LeftAt;
|
||||
public long MutedUntil;
|
||||
public List<DateTimeOffset> JoinedAt;
|
||||
public List<DateTimeOffset> LeftAt;
|
||||
public DateTimeOffset MutedUntil;
|
||||
public List<Reminder> Reminders;
|
||||
public List<ulong> Roles;
|
||||
|
||||
public MemberData(IGuildUser user) {
|
||||
Id = user.Id;
|
||||
IsInGuild = true;
|
||||
JoinedAt = new List<long> { user.JoinedAt!.Value.ToUnixTimeSeconds() };
|
||||
LeftAt = new List<long>();
|
||||
JoinedAt = new List<DateTimeOffset> { user.JoinedAt!.Value };
|
||||
LeftAt = new List<DateTimeOffset>();
|
||||
Roles = user.RoleIds.ToList();
|
||||
Reminders = new List<Reminder>();
|
||||
MutedUntil = 0;
|
||||
BannedUntil = 0;
|
||||
MutedUntil = DateTimeOffset.MinValue;
|
||||
BannedUntil = DateTimeOffset.MinValue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
namespace Boyfriend.Data;
|
||||
|
||||
public struct Reminder {
|
||||
public long RemindAt;
|
||||
public DateTimeOffset RemindAt;
|
||||
public string ReminderText;
|
||||
}
|
||||
|
|
|
@ -110,19 +110,18 @@ public static class EventHandler {
|
|||
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 = 0;
|
||||
memberData.BannedUntil = DateTimeOffset.MinValue;
|
||||
if (memberData.LeftAt.Count > 0) {
|
||||
if (memberData.JoinedAt.Contains(user.JoinedAt!.Value.ToUnixTimeSeconds()))
|
||||
if (memberData.JoinedAt.Contains(user.JoinedAt!.Value))
|
||||
throw new UnreachableException();
|
||||
memberData.JoinedAt.Add(user.JoinedAt!.Value.ToUnixTimeSeconds());
|
||||
memberData.JoinedAt.Add(user.JoinedAt!.Value);
|
||||
}
|
||||
|
||||
if (memberData.MutedUntil < DateTimeOffset.Now.ToUnixTimeSeconds()) {
|
||||
if (memberData.MutedUntil < DateTimeOffset.Now) {
|
||||
if (data.MuteRole is not null)
|
||||
await user.AddRoleAsync(data.MuteRole);
|
||||
else
|
||||
await user.SetTimeOutAsync(
|
||||
TimeSpan.FromSeconds(DateTimeOffset.Now.ToUnixTimeSeconds() - memberData.MutedUntil));
|
||||
await user.SetTimeOutAsync(DateTimeOffset.Now - memberData.MutedUntil);
|
||||
|
||||
if (config["RemoveRolesOnMute"] is "false" && config["ReturnRolesOnRejoin"] is "true")
|
||||
await user.AddRolesAsync(memberData.Roles);
|
||||
|
@ -132,11 +131,10 @@ public static class EventHandler {
|
|||
private static Task UserLeftEvent(SocketGuild guild, SocketUser user) {
|
||||
var data = GuildData.FromSocketGuild(guild).MemberData[user.Id];
|
||||
data.IsInGuild = false;
|
||||
data.LeftAt.Add(DateTimeOffset.Now.ToUnixTimeSeconds());
|
||||
data.LeftAt.Add(DateTimeOffset.Now);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// TODO: store data about event (early) notifications
|
||||
private static async Task ScheduledEventCreatedEvent(SocketGuildEvent scheduledEvent) {
|
||||
var guild = scheduledEvent.Guild;
|
||||
var eventConfig = GuildData.FromSocketGuild(guild).Preferences;
|
||||
|
|
6
Boyfriend/Messages.Designer.cs
generated
6
Boyfriend/Messages.Designer.cs
generated
|
@ -1050,11 +1050,11 @@ namespace Boyfriend {
|
|||
}
|
||||
|
||||
/// <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 .
|
||||
/// </summary>
|
||||
internal static string UserNotInGuild {
|
||||
internal static string UserNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("UserNotInGuild", resourceCulture);
|
||||
return ResourceManager.GetString("UserNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
@ -225,9 +225,6 @@
|
|||
<data name="FeedbackUserBanned" xml:space="preserve">
|
||||
<value>Banned {0} for{1}: {2}</value>
|
||||
</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">
|
||||
<value>That setting doesn't exist!</value>
|
||||
</data>
|
||||
|
@ -450,13 +447,16 @@
|
|||
<data name="BotCannotUnmuteTarget" xml:space="preserve">
|
||||
<value>I cannot unmute this member!</value>
|
||||
</data>
|
||||
<data name="UserCannotUnmuteTarget" xml:space="preserve">
|
||||
<data name="UserCannotUnmuteTarget" xml:space="preserve">
|
||||
<value>You cannot unmute this user!</value>
|
||||
</data>
|
||||
<data name="EventEarlyNotification" xml:space="preserve">
|
||||
<data name="EventEarlyNotification" xml:space="preserve">
|
||||
<value>{0}Event {1} will start <t:{2}:R>!</value>
|
||||
</data>
|
||||
<data name="SettingsEventEarlyNotificationOffset" xml:space="preserve">
|
||||
<data name="SettingsEventEarlyNotificationOffset" xml:space="preserve">
|
||||
<value>Early event start notification offset</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="UserNotFound" xml:space="preserve">
|
||||
<value/>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -222,9 +222,6 @@
|
|||
<data name="FeedbackUserBanned" xml:space="preserve">
|
||||
<value>Забанен {0} на{1}: {2}</value>
|
||||
</data>
|
||||
<data name="UserNotInGuild" xml:space="preserve">
|
||||
<value>Указанный пользователь не является участником этого сервера!</value>
|
||||
</data>
|
||||
<data name="SettingDoesntExist" xml:space="preserve">
|
||||
<value>Такая настройка не существует!</value>
|
||||
</data>
|
||||
|
|
|
@ -222,9 +222,6 @@
|
|||
<data name="FeedbackUserBanned" xml:space="preserve">
|
||||
<value>забанен {0} на{1}: {2}</value>
|
||||
</data>
|
||||
<data name="UserNotInGuild" xml:space="preserve">
|
||||
<value>шизик не на этом сервере</value>
|
||||
</data>
|
||||
<data name="SettingDoesntExist" xml:space="preserve">
|
||||
<value>такой прикол не существует</value>
|
||||
</data>
|
||||
|
|
|
@ -49,6 +49,11 @@ public static partial class Utils {
|
|||
return ulong.TryParse(NumbersOnlyRegex().Replace(mention, ""), out var id) ? id : 0;
|
||||
}
|
||||
|
||||
public static async Task SendDirectMessage(ulong id, string toSend) {
|
||||
if (await Boyfriend.Client.GetUserAsync(id) is SocketUser user)
|
||||
await SendDirectMessage(user, toSend);
|
||||
}
|
||||
|
||||
public static async Task SendDirectMessage(SocketUser user, string toSend) {
|
||||
try { await user.SendMessageAsync(toSend); } catch (HttpException e) {
|
||||
if (e.DiscordCode is not DiscordErrorCode.CannotSendMessageToUser) throw;
|
||||
|
@ -133,6 +138,14 @@ public static partial class Utils {
|
|||
.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));
|
||||
}
|
||||
|
||||
[GeneratedRegex("[^0-9]")]
|
||||
private static partial Regex NumbersOnlyRegex();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue