1
0
Fork 1
mirror of https://github.com/TeamOctolings/Octobot.git synced 2025-04-20 00:43: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:
Octol1ttle 2023-01-12 22:00:52 +05:00
parent fe2cfb3b3c
commit 4b2d98c440
Signed by: Octol1ttle
GPG key ID: B77C34313AEE1FFF
16 changed files with 152 additions and 131 deletions

View file

@ -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"

View file

@ -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

View file

@ -112,19 +112,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, (SocketUser?)Boyfriend.Client.GetUser(mention));
} }
public bool HasPermission(GuildPermission permission) { public bool HasPermission(GuildPermission permission) {
@ -135,7 +146,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}")}",
@ -146,8 +157,12 @@ 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(ulong id) {
return Context.Guild.GetUser(id);
} }
public SocketGuildUser? GetMember(string[] args, string[] cleanArgs, int index, string? argument) { public SocketGuildUser? GetMember(string[] args, string[] cleanArgs, int index, string? argument) {
@ -165,10 +180,6 @@ public sealed class CommandProcessor {
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}",

View file

@ -8,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);
@ -19,21 +19,23 @@ 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 memberData = GuildData.FromSocketGuild(guild).MemberData[toBan.Id]; var memberData = GuildData.FromSocketGuild(guild).MemberData[toBan.Item1];
memberData.BannedUntil memberData.BannedUntil
= duration.TotalSeconds < 1 ? -1 : DateTimeOffset.Now.Add(duration).ToUnixTimeSeconds(); = duration.TotalSeconds < 1 ? DateTimeOffset.MaxValue : DateTimeOffset.Now.Add(duration);
memberData.Roles.Clear(); 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)); Utils.GetHumanizedTimeOffset(duration), Utils.Wrap(reason));
cmd.Reply(feedback, ReplyEmojis.Banned); cmd.Reply(feedback, ReplyEmojis.Banned);
cmd.Audit(feedback); cmd.Audit(feedback);

View file

@ -19,8 +19,8 @@ public sealed class MuteCommand : ICommand {
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;
} }
@ -41,7 +41,7 @@ public sealed class MuteCommand : ICommand {
await toMute.AddRoleAsync(role, requestOptions); 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 { } else {
if (!hasDuration || duration.TotalDays > 28) { if (!hasDuration || duration.TotalDays > 28) {
cmd.Reply(Messages.DurationRequiredForTimeOuts, ReplyEmojis.Error); cmd.Reply(Messages.DurationRequiredForTimeOuts, ReplyEmojis.Error);

View file

@ -10,7 +10,7 @@ public sealed class RemindCommand : ICommand {
var reminderText = cmd.GetRemaining(args, 1, "ReminderText"); var reminderText = cmd.GetRemaining(args, 1, "ReminderText");
if (reminderText is not null) if (reminderText is not null)
GuildData.FromSocketGuild(cmd.Context.Guild).MemberData[cmd.Context.User.Id].Reminders.Add(new Reminder { 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 ReminderText = reminderText
}); });

View file

@ -27,8 +27,7 @@ public sealed class UnmuteCommand : ICommand {
await toUnmute.AddRolesAsync(data.MemberData[toUnmute.Id].Roles, requestOptions); await toUnmute.AddRolesAsync(data.MemberData[toUnmute.Id].Roles, requestOptions);
await toUnmute.RemoveRoleAsync(role, requestOptions); await toUnmute.RemoveRoleAsync(role, requestOptions);
} else { } else {
if (toUnmute.TimedOutUntil is null || toUnmute.TimedOutUntil.Value.ToUnixTimeSeconds() < if (toUnmute.TimedOutUntil is null || toUnmute.TimedOutUntil.Value < DateTimeOffset.Now) {
DateTimeOffset.Now.ToUnixTimeSeconds()) {
cmd.Reply(Messages.MemberNotMuted, ReplyEmojis.Error); cmd.Reply(Messages.MemberNotMuted, ReplyEmojis.Error);
return; return;
} }

View file

@ -24,7 +24,7 @@ public record GuildData {
// TODO: { "AutoStartEvents", "false" } // 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; public readonly Dictionary<ulong, MemberData> MemberData;
@ -44,15 +44,12 @@ public record GuildData {
= JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText($"{_id}/Configuration.json")) ?? = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText($"{_id}/Configuration.json")) ??
new Dictionary<string, string>(); new Dictionary<string, string>();
// ReSharper disable twice ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
if (Preferences.Keys.Count < DefaultPreferences.Keys.Count) if (Preferences.Keys.Count < DefaultPreferences.Keys.Count)
foreach (var key in DefaultPreferences.Keys) foreach (var key in DefaultPreferences.Keys.Where(key => !Preferences.ContainsKey(key)))
if (!Preferences.ContainsKey(key)) Preferences.Add(key, DefaultPreferences[key]);
Preferences.Add(key, DefaultPreferences[key]);
if (Preferences.Keys.Count > DefaultPreferences.Keys.Count) if (Preferences.Keys.Count > DefaultPreferences.Keys.Count)
foreach (var key in Preferences.Keys) foreach (var key in Preferences.Keys.Where(key => !DefaultPreferences.ContainsKey(key)))
if (!DefaultPreferences.ContainsKey(key)) Preferences.Remove(key);
Preferences.Remove(key);
Preferences.TrimExcess(); Preferences.TrimExcess();
MemberData = new Dictionary<ulong, MemberData>(); MemberData = new Dictionary<ulong, MemberData>();
@ -67,7 +64,7 @@ public record GuildData {
if (MemberData.TryGetValue(member.Id, out var memberData)) { if (MemberData.TryGetValue(member.Id, out var memberData)) {
if (!memberData.IsInGuild && if (!memberData.IsInGuild &&
DateTimeOffset.Now.ToUnixTimeSeconds() - DateTimeOffset.Now.ToUnixTimeSeconds() -
Math.Max(memberData.LeftAt.Last(), memberData.BannedUntil) > Math.Max(memberData.LeftAt.Last().ToUnixTimeSeconds(), memberData.BannedUntil.ToUnixTimeSeconds()) >
60 * 60 * 24 * 30) { 60 * 60 * 24 * 30) {
File.Delete($"{_id}/MemberData/{memberData.Id}.json"); File.Delete($"{_id}/MemberData/{memberData.Id}.json");
MemberData.Remove(memberData.Id); MemberData.Remove(memberData.Id);
@ -83,8 +80,11 @@ public record GuildData {
} }
public SocketRole? MuteRole { public SocketRole? MuteRole {
get => _cachedMuteRole ??= Boyfriend.Client.GetGuild(_id).Roles get {
.Single(x => x.Id == ulong.Parse(Preferences["MuteRole"])); 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; set => _cachedMuteRole = value;
} }

View file

@ -3,23 +3,23 @@
namespace Boyfriend.Data; namespace Boyfriend.Data;
public record MemberData { public record MemberData {
public long BannedUntil; public DateTimeOffset BannedUntil;
public ulong Id; public ulong Id;
public bool IsInGuild; public bool IsInGuild;
public List<long> JoinedAt; public List<DateTimeOffset> JoinedAt;
public List<long> LeftAt; public List<DateTimeOffset> LeftAt;
public long MutedUntil; public DateTimeOffset MutedUntil;
public List<Reminder> Reminders; public List<Reminder> Reminders;
public List<ulong> Roles; public List<ulong> Roles;
public MemberData(IGuildUser user) { public MemberData(IGuildUser user) {
Id = user.Id; Id = user.Id;
IsInGuild = true; IsInGuild = true;
JoinedAt = new List<long> { user.JoinedAt!.Value.ToUnixTimeSeconds() }; JoinedAt = new List<DateTimeOffset> { user.JoinedAt!.Value };
LeftAt = new List<long>(); LeftAt = new List<DateTimeOffset>();
Roles = user.RoleIds.ToList(); Roles = user.RoleIds.ToList();
Reminders = new List<Reminder>(); Reminders = new List<Reminder>();
MutedUntil = 0; MutedUntil = DateTimeOffset.MinValue;
BannedUntil = 0; BannedUntil = DateTimeOffset.MinValue;
} }
} }

View file

@ -1,6 +1,6 @@
namespace Boyfriend.Data; namespace Boyfriend.Data;
public struct Reminder { public struct Reminder {
public long RemindAt; public DateTimeOffset RemindAt;
public string ReminderText; public string ReminderText;
} }

View file

@ -110,19 +110,18 @@ public static class EventHandler {
if (!data.MemberData.ContainsKey(user.Id)) data.MemberData.Add(user.Id, new MemberData(user)); if (!data.MemberData.ContainsKey(user.Id)) data.MemberData.Add(user.Id, new MemberData(user));
var memberData = data.MemberData[user.Id]; var memberData = data.MemberData[user.Id];
memberData.IsInGuild = true; memberData.IsInGuild = true;
memberData.BannedUntil = 0; memberData.BannedUntil = DateTimeOffset.MinValue;
if (memberData.LeftAt.Count > 0) { if (memberData.LeftAt.Count > 0) {
if (memberData.JoinedAt.Contains(user.JoinedAt!.Value.ToUnixTimeSeconds())) if (memberData.JoinedAt.Contains(user.JoinedAt!.Value))
throw new UnreachableException(); 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) if (data.MuteRole is not null)
await user.AddRoleAsync(data.MuteRole); await user.AddRoleAsync(data.MuteRole);
else else
await user.SetTimeOutAsync( await user.SetTimeOutAsync(DateTimeOffset.Now - memberData.MutedUntil);
TimeSpan.FromSeconds(DateTimeOffset.Now.ToUnixTimeSeconds() - memberData.MutedUntil));
if (config["RemoveRolesOnMute"] is "false" && config["ReturnRolesOnRejoin"] is "true") if (config["RemoveRolesOnMute"] is "false" && config["ReturnRolesOnRejoin"] is "true")
await user.AddRolesAsync(memberData.Roles); await user.AddRolesAsync(memberData.Roles);
@ -132,11 +131,10 @@ public static class EventHandler {
private static Task UserLeftEvent(SocketGuild guild, SocketUser user) { private static Task UserLeftEvent(SocketGuild guild, SocketUser user) {
var data = GuildData.FromSocketGuild(guild).MemberData[user.Id]; var data = GuildData.FromSocketGuild(guild).MemberData[user.Id];
data.IsInGuild = false; data.IsInGuild = false;
data.LeftAt.Add(DateTimeOffset.Now.ToUnixTimeSeconds()); data.LeftAt.Add(DateTimeOffset.Now);
return Task.CompletedTask; return Task.CompletedTask;
} }
// TODO: store data about event (early) notifications
private static async Task ScheduledEventCreatedEvent(SocketGuildEvent scheduledEvent) { private static async Task ScheduledEventCreatedEvent(SocketGuildEvent scheduledEvent) {
var guild = scheduledEvent.Guild; var guild = scheduledEvent.Guild;
var eventConfig = GuildData.FromSocketGuild(guild).Preferences; var eventConfig = GuildData.FromSocketGuild(guild).Preferences;

View file

@ -1050,11 +1050,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 .
/// </summary> /// </summary>
internal static string UserNotInGuild { internal static string UserNotFound {
get { get {
return ResourceManager.GetString("UserNotInGuild", resourceCulture); return ResourceManager.GetString("UserNotFound", resourceCulture);
} }
} }

View file

@ -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">
@ -225,9 +225,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>
@ -450,13 +447,16 @@
<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 &lt;t:{2}:R&gt;!</value> <value>{0}Event {1} will start &lt;t:{2}:R&gt;!</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/>
</data>
</root> </root>

View file

@ -222,9 +222,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>

View file

@ -222,9 +222,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>

View file

@ -49,6 +49,11 @@ public static partial class Utils {
return ulong.TryParse(NumbersOnlyRegex().Replace(mention, ""), out var id) ? id : 0; 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) { public static async Task SendDirectMessage(SocketUser user, string toSend) {
try { await user.SendMessageAsync(toSend); } catch (HttpException e) { try { await user.SendMessageAsync(toSend); } catch (HttpException e) {
if (e.DiscordCode is not DiscordErrorCode.CannotSendMessageToUser) throw; if (e.DiscordCode is not DiscordErrorCode.CannotSendMessageToUser) throw;
@ -133,6 +138,14 @@ public static partial class Utils {
.Preferences["EventNotificationChannel"])); .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]")] [GeneratedRegex("[^0-9]")]
private static partial Regex NumbersOnlyRegex(); private static partial Regex NumbersOnlyRegex();
} }