diff --git a/Myriad/Extensions/PermissionExtensions.cs b/Myriad/Extensions/PermissionExtensions.cs index 60f4f52b..d78288a3 100644 --- a/Myriad/Extensions/PermissionExtensions.cs +++ b/Myriad/Extensions/PermissionExtensions.cs @@ -143,5 +143,11 @@ namespace Myriad.Extensions PermissionSet.SendTtsMessages | PermissionSet.AttachFiles | PermissionSet.EmbedLinks; + + public static string ToPermissionString(this PermissionSet perms) + { + // TODO: clean string + return perms.ToString(); + } } } \ No newline at end of file diff --git a/Myriad/Gateway/Shard.cs b/Myriad/Gateway/Shard.cs index cb00fb81..1ace1b91 100644 --- a/Myriad/Gateway/Shard.cs +++ b/Myriad/Gateway/Shard.cs @@ -27,6 +27,7 @@ namespace Myriad.Gateway private Task _worker; public ShardInfo? ShardInfo { get; private set; } + public int ShardId => ShardInfo?.ShardId ?? 0; public GatewaySettings Settings { get; } public ShardSessionInfo SessionInfo { get; private set; } public ShardState State { get; private set; } diff --git a/Myriad/Rest/DiscordApiClient.cs b/Myriad/Rest/DiscordApiClient.cs index 11bdddb9..257b3be6 100644 --- a/Myriad/Rest/DiscordApiClient.cs +++ b/Myriad/Rest/DiscordApiClient.cs @@ -46,8 +46,8 @@ namespace Myriad.Rest _client.Get($"/guilds/{guildId}/members/{userId}", ("GetGuildMember", guildId)); - public Task CreateMessage(ulong channelId, MessageRequest request) => - _client.Post($"/channels/{channelId}/messages", ("CreateMessage", channelId), request)!; + public Task CreateMessage(ulong channelId, MessageRequest request, MultipartFile[]? files = null) => + _client.PostMultipart($"/channels/{channelId}/messages", ("CreateMessage", channelId), request, files)!; public Task EditMessage(ulong channelId, ulong messageId, MessageEditRequest request) => _client.Patch($"/channels/{channelId}/messages/{messageId}", ("EditMessage", channelId), request)!; diff --git a/PluralKit.Bot/CommandSystem/Context.cs b/PluralKit.Bot/CommandSystem/Context.cs index 1ed55bb0..d0135ced 100644 --- a/PluralKit.Bot/CommandSystem/Context.cs +++ b/PluralKit.Bot/CommandSystem/Context.cs @@ -1,39 +1,31 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using App.Metrics; using Autofac; -using DSharpPlus; -using DSharpPlus.Entities; - using Myriad.Cache; using Myriad.Extensions; using Myriad.Gateway; +using Myriad.Rest; using Myriad.Rest.Types; using Myriad.Rest.Types.Requests; using Myriad.Types; using PluralKit.Core; -using DiscordApiClient = Myriad.Rest.DiscordApiClient; - namespace PluralKit.Bot { public class Context { private readonly ILifetimeScope _provider; - private readonly DiscordRestClient _rest; private readonly DiscordApiClient _newRest; - private readonly DiscordShardedClient _client; - private readonly DiscordClient _shard = null; + private readonly Cluster _cluster; private readonly Shard _shardNew; private readonly Guild? _guild; private readonly Channel _channel; - private readonly DiscordMessage _message = null; private readonly MessageCreateEvent _messageNew; private readonly Parameters _parameters; private readonly MessageContext _messageContext; @@ -52,8 +44,6 @@ namespace PluralKit.Bot public Context(ILifetimeScope provider, Shard shard, Guild? guild, Channel channel, MessageCreateEvent message, int commandParseOffset, PKSystem senderSystem, MessageContext messageContext, PermissionSet botPermissions) { - _rest = provider.Resolve(); - _client = provider.Resolve(); _messageNew = message; _shardNew = shard; _guild = guild; @@ -66,8 +56,9 @@ namespace PluralKit.Bot _metrics = provider.Resolve(); _provider = provider; _commandMessageService = provider.Resolve(); - _parameters = new Parameters(message.Content.Substring(commandParseOffset)); + _parameters = new Parameters(message.Content?.Substring(commandParseOffset)); _newRest = provider.Resolve(); + _cluster = provider.Resolve(); _botPermissions = botPermissions; _userPermissions = _cache.PermissionsFor(message); @@ -75,23 +66,19 @@ namespace PluralKit.Bot public IDiscordCache Cache => _cache; - public DiscordUser Author => _message.Author; - public DiscordChannel Channel => _message.Channel; public Channel ChannelNew => _channel; public User AuthorNew => _messageNew.Author; public GuildMemberPartial MemberNew => _messageNew.Member; - public DiscordMessage Message => _message; + public Message MessageNew => _messageNew; - public DiscordGuild Guild => _message.Channel.Guild; public Guild GuildNew => _guild; - public DiscordClient Shard => _shard; - public DiscordShardedClient Client => _client; + public Shard ShardNew => _shardNew; + public Cluster Cluster => _cluster; public MessageContext MessageContext => _messageContext; public PermissionSet BotPermissions => _botPermissions; public PermissionSet UserPermissions => _userPermissions; - public DiscordRestClient Rest => _rest; public DiscordApiClient RestNew => _newRest; public PKSystem System => _senderSystem; diff --git a/PluralKit.Bot/Commands/Autoproxy.cs b/PluralKit.Bot/Commands/Autoproxy.cs index d7f51975..15abc6fc 100644 --- a/PluralKit.Bot/Commands/Autoproxy.cs +++ b/PluralKit.Bot/Commands/Autoproxy.cs @@ -129,7 +129,7 @@ namespace PluralKit.Bot } if (!ctx.MessageContext.AllowAutoproxy) - eb.Field(new("\u200b", $"{Emojis.Note} Autoproxy is currently **disabled** for your account (<@{ctx.Author.Id}>). To enable it, use `pk;autoproxy account enable`.")); + eb.Field(new("\u200b", $"{Emojis.Note} Autoproxy is currently **disabled** for your account (<@{ctx.AuthorNew.Id}>). To enable it, use `pk;autoproxy account enable`.")); return eb.Build(); } @@ -191,7 +191,7 @@ namespace PluralKit.Bot else { var statusString = ctx.MessageContext.AllowAutoproxy ? "enabled" : "disabled"; - await ctx.Reply($"Autoproxy is currently **{statusString}** for account <@{ctx.Author.Id}>."); + await ctx.Reply($"Autoproxy is currently **{statusString}** for account <@{ctx.AuthorNew.Id}>."); } } @@ -200,12 +200,12 @@ namespace PluralKit.Bot var statusString = allow ? "enabled" : "disabled"; if (ctx.MessageContext.AllowAutoproxy == allow) { - await ctx.Reply($"{Emojis.Note} Autoproxy is already {statusString} for account <@{ctx.Author.Id}>."); + await ctx.Reply($"{Emojis.Note} Autoproxy is already {statusString} for account <@{ctx.AuthorNew.Id}>."); return; } var patch = new AccountPatch { AllowAutoproxy = allow }; - await _db.Execute(conn => _repo.UpdateAccount(conn, ctx.Author.Id, patch)); - await ctx.Reply($"{Emojis.Success} Autoproxy {statusString} for account <@{ctx.Author.Id}>."); + await _db.Execute(conn => _repo.UpdateAccount(conn, ctx.AuthorNew.Id, patch)); + await ctx.Reply($"{Emojis.Success} Autoproxy {statusString} for account <@{ctx.AuthorNew.Id}>."); } private Task UpdateAutoproxy(Context ctx, AutoproxyMode autoproxyMode, MemberId? autoproxyMember) diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index a5868184..a8132c5e 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -524,7 +524,7 @@ namespace PluralKit.Bot { // Try to resolve the user ID to find the associated account, // so we can print their username. - var user = await ctx.Shard.GetUser(id); + var user = await ctx.RestNew.GetUser(id); if (user != null) return $"Account **{user.Username}#{user.Discriminator}** does not have a system registered."; else diff --git a/PluralKit.Bot/Commands/Help.cs b/PluralKit.Bot/Commands/Help.cs index dff1bf33..d4bd9e9f 100644 --- a/PluralKit.Bot/Commands/Help.cs +++ b/PluralKit.Bot/Commands/Help.cs @@ -20,7 +20,7 @@ namespace PluralKit.Bot .Field(new("More information", "For a full list of commands, see [the command list](https://pluralkit.me/commands).\nFor a more in-depth explanation of message proxying, see [the documentation](https://pluralkit.me/guide#proxying).\nIf you're an existing user of Tupperbox, type `pk;import` and attach a Tupperbox export file (from `tul!export`) to import your data from there.")) .Field(new("Support server", "We also have a Discord server for support, discussion, suggestions, announcements, etc: https://discord.gg/PczBt78")) .Footer(new($"By @Ske#6201 | Myriad by @Layl#8888 | GitHub: https://github.com/xSke/PluralKit/ | Website: https://pluralkit.me/")) - .Color((uint?) DiscordUtils.Blue.Value) + .Color(DiscordUtils.Blue) .Build()); } diff --git a/PluralKit.Bot/Commands/ImportExport.cs b/PluralKit.Bot/Commands/ImportExport.cs index eb546e19..fda3afe7 100644 --- a/PluralKit.Bot/Commands/ImportExport.cs +++ b/PluralKit.Bot/Commands/ImportExport.cs @@ -6,6 +6,8 @@ using System.Text; using System.Threading.Tasks; using Myriad.Rest.Exceptions; +using Myriad.Rest.Types; +using Myriad.Rest.Types.Requests; using Myriad.Types; using Newtonsoft.Json; @@ -32,7 +34,7 @@ namespace PluralKit.Bot public async Task Import(Context ctx) { - var url = ctx.RemainderOrNull() ?? ctx.Message.Attachments.FirstOrDefault()?.Url; + var url = ctx.RemainderOrNull() ?? ctx.MessageNew.Attachments.FirstOrDefault()?.Url; if (url == null) throw Errors.NoImportFilePassed; await ctx.BusyIndicator(async () => @@ -67,7 +69,7 @@ namespace PluralKit.Bot if (!data.Valid) throw Errors.InvalidImportFile; - if (data.LinkedAccounts != null && !data.LinkedAccounts.Contains(ctx.Author.Id)) + if (data.LinkedAccounts != null && !data.LinkedAccounts.Contains(ctx.AuthorNew.Id)) { var msg = $"{Emojis.Warn} You seem to importing a system profile belonging to another account. Are you sure you want to proceed?"; if (!await ctx.PromptYesNo(msg)) throw Errors.ImportCancelled; @@ -75,7 +77,7 @@ namespace PluralKit.Bot // If passed system is null, it'll create a new one // (and that's okay!) - var result = await _dataFiles.ImportSystem(data, ctx.System, ctx.Author.Id); + var result = await _dataFiles.ImportSystem(data, ctx.System, ctx.AuthorNew.Id); if (!result.Success) await ctx.Reply($"{Emojis.Error} The provided system profile could not be imported. {result.Message}"); else if (ctx.System == null) @@ -141,13 +143,16 @@ namespace PluralKit.Bot try { - var dm = await ctx.Rest.CreateDmAsync(ctx.AuthorNew.Id); + var dm = await ctx.RestNew.CreateDm(ctx.AuthorNew.Id); // TODO: send file - var msg = await dm.SendFileAsync("system.json", stream, $"{Emojis.Success} Here you go!"); - await dm.SendMessageAsync($"<{msg.Attachments[0].Url}>"); + + var msg = await ctx.RestNew.CreateMessage(dm.Id, + new MessageRequest {Content = $"{Emojis.Success} Here you go!"}, + new[] {new MultipartFile("system.json", stream)}); + await ctx.RestNew.CreateMessage(dm.Id, new MessageRequest { Content = $"<{msg.Attachments[0].Url}>" }); // If the original message wasn't posted in DMs, send a public reminder - if (ctx.ChannelNew.Type == Channel.ChannelType.Dm) + if (ctx.ChannelNew.Type != Channel.ChannelType.Dm) await ctx.Reply($"{Emojis.Success} Check your DMs!"); } catch (UnauthorizedException) diff --git a/PluralKit.Bot/Commands/Member.cs b/PluralKit.Bot/Commands/Member.cs index 2a914090..20229368 100644 --- a/PluralKit.Bot/Commands/Member.cs +++ b/PluralKit.Bot/Commands/Member.cs @@ -5,8 +5,6 @@ using System.Web; using Dapper; -using DSharpPlus.Entities; - using Myriad.Builders; using Newtonsoft.Json.Linq; @@ -92,7 +90,7 @@ namespace PluralKit.Bot var scream = data["soulscream"]!.Value(); var eb = new EmbedBuilder() - .Color((uint?) DiscordColor.Red.Value) + .Color(DiscordUtils.Red) .Title(name) .Url($"https://onomancer.sibr.dev/reflect?name={encoded}") .Description($"*{scream}*"); diff --git a/PluralKit.Bot/Commands/MemberEdit.cs b/PluralKit.Bot/Commands/MemberEdit.cs index 017441d0..6dc1f1c6 100644 --- a/PluralKit.Bot/Commands/MemberEdit.cs +++ b/PluralKit.Bot/Commands/MemberEdit.cs @@ -160,7 +160,7 @@ namespace PluralKit.Bot else await ctx.Reply(embed: new EmbedBuilder() .Title("Member color") - .Color((uint?) target.Color.ToDiscordColor()!.Value.Value) + .Color(target.Color.ToDiscordColor()) .Thumbnail(new($"https://fakeimg.pl/256x256/{target.Color}/?text=%20")) .Description($"This member's color is **#{target.Color}**." + (ctx.System?.Id == target.System ? $" To clear it, type `pk;member {target.Reference()} color -clear`." : "")) @@ -178,7 +178,7 @@ namespace PluralKit.Bot await ctx.Reply(embed: new EmbedBuilder() .Title($"{Emojis.Success} Member color changed.") - .Color((uint?) color.ToDiscordColor()!.Value.Value) + .Color(color.ToDiscordColor()) .Thumbnail(new($"https://fakeimg.pl/256x256/{color}/?text=%20")) .Build()); } diff --git a/PluralKit.Bot/Commands/Misc.cs b/PluralKit.Bot/Commands/Misc.cs index db4bc36e..92ea99e4 100644 --- a/PluralKit.Bot/Commands/Misc.cs +++ b/PluralKit.Bot/Commands/Misc.cs @@ -6,8 +6,6 @@ using System.Threading.Tasks; using App.Metrics; -using DSharpPlus; - using Humanizer; using NodaTime; @@ -22,8 +20,6 @@ using Myriad.Rest; using Myriad.Rest.Types.Requests; using Myriad.Types; -using Permissions = DSharpPlus.Permissions; - namespace PluralKit.Bot { public class Misc { @@ -89,10 +85,10 @@ namespace PluralKit.Bot { var totalMessages = _metrics.Snapshot.GetForContext("Application").Gauges.FirstOrDefault(m => m.MultidimensionalName == CoreMetrics.MessageCount.Name)?.Value ?? 0; // TODO: shard stuff - var shardId = ctx.Shard.ShardId; - var shardTotal = ctx.Client.ShardClients.Count; + var shardId = ctx.ShardNew.ShardInfo?.ShardId ?? -1; + var shardTotal = ctx.Cluster.Shards.Count; var shardUpTotal = _shards.Shards.Where(x => x.Connected).Count(); - var shardInfo = _shards.GetShardInfo(ctx.Shard); + var shardInfo = _shards.GetShardInfo(ctx.ShardNew); var process = Process.GetCurrentProcess(); var memoryUsage = process.WorkingSet64; @@ -188,7 +184,7 @@ namespace PluralKit.Bot { if (permissionsMissing.Count == 0) { - eb.Description($"No errors found, all channels proxyable :)").Color((uint?) DiscordUtils.Green.Value); + eb.Description($"No errors found, all channels proxyable :)").Color(DiscordUtils.Green); } else { @@ -196,14 +192,13 @@ namespace PluralKit.Bot { { // Each missing permission field can have multiple missing channels // so we extract them all and generate a comma-separated list - // TODO: port ToPermissionString? - var missingPermissionNames = ((Permissions)missingPermissionField).ToPermissionString(); + var missingPermissionNames = ((PermissionSet) missingPermissionField).ToPermissionString(); var channelsList = string.Join("\n", channels .OrderBy(c => c.Position) .Select(c => $"#{c.Name}")); eb.Field(new($"Missing *{missingPermissionNames}*", channelsList.Truncate(1000))); - eb.Color((uint?) DiscordUtils.Red.Value); + eb.Color(DiscordUtils.Red); } } diff --git a/PluralKit.Bot/Commands/System.cs b/PluralKit.Bot/Commands/System.cs index d531196d..b1575c95 100644 --- a/PluralKit.Bot/Commands/System.cs +++ b/PluralKit.Bot/Commands/System.cs @@ -34,7 +34,7 @@ namespace PluralKit.Bot var system = _db.Execute(async c => { var system = await _repo.CreateSystem(c, systemName); - await _repo.AddAccount(c, system.Id, ctx.Author.Id); + await _repo.AddAccount(c, system.Id, ctx.AuthorNew.Id); return system; }); diff --git a/PluralKit.Bot/Commands/SystemLink.cs b/PluralKit.Bot/Commands/SystemLink.cs index 0ebc0d83..24042094 100644 --- a/PluralKit.Bot/Commands/SystemLink.cs +++ b/PluralKit.Bot/Commands/SystemLink.cs @@ -49,7 +49,7 @@ namespace PluralKit.Bot ulong id; if (!ctx.HasNext()) - id = ctx.Author.Id; + id = ctx.AuthorNew.Id; else if (!ctx.MatchUserRaw(out id)) throw new PKSyntaxError("You must pass an account to link with (either ID or @mention)."); diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 5b33c70b..1efc3506 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using DSharpPlus.Entities; - using Humanizer; using Myriad.Builders; @@ -58,7 +56,7 @@ namespace PluralKit.Bot { .Title(system.Name) .Thumbnail(new(system.AvatarUrl)) .Footer(new($"System ID: {system.Hid} | Created on {system.Created.FormatZoned(system)}")) - .Color((uint) DiscordUtils.Gray.Value); + .Color(DiscordUtils.Gray); var latestSwitch = await _repo.GetLatestSwitch(conn, system.Id); if (latestSwitch != null && system.FrontPrivacy.CanAccess(ctx)) @@ -107,7 +105,7 @@ namespace PluralKit.Bot { var name = member.NameFor(ctx); if (system.Name != null) name = $"{name} ({system.Name})"; - DiscordColor color; + uint color; try { color = member.Color?.ToDiscordColor() ?? DiscordUtils.Gray; @@ -135,7 +133,7 @@ namespace PluralKit.Bot { // TODO: add URL of website when that's up .Author(new(name, IconUrl: DiscordUtils.WorkaroundForUrlBug(avatar))) // .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : DiscordUtils.Gray) - .Color((uint?) color.Value) + .Color(color) .Footer(new( $"System ID: {system.Hid} | Member ID: {member.Hid} {(member.MetadataPrivacy.CanAccess(ctx) ? $"| Created on {member.Created.FormatZoned(system)}" : "")}")); @@ -218,7 +216,7 @@ namespace PluralKit.Bot { var members = await _db.Execute(c => _repo.GetSwitchMembers(c, sw.Id).ToListAsync().AsTask()); var timeSinceSwitch = SystemClock.Instance.GetCurrentInstant() - sw.Timestamp; return new EmbedBuilder() - .Color((uint?) (members.FirstOrDefault()?.Color?.ToDiscordColor()?.Value ?? DiscordUtils.Gray.Value)) + .Color(members.FirstOrDefault()?.Color?.ToDiscordColor() ?? DiscordUtils.Gray) .Field(new($"Current {"fronter".ToQuantity(members.Count, ShowQuantityAs.None)}", members.Count > 0 ? string.Join(", ", members.Select(m => m.NameFor(ctx))) : "*(no fronter)*")) .Field(new("Since", $"{sw.Timestamp.FormatZoned(zone)} ({timeSinceSwitch.FormatDuration()} ago)")) .Build(); @@ -280,7 +278,7 @@ namespace PluralKit.Bot { { var actualPeriod = breakdown.RangeEnd - breakdown.RangeStart; var eb = new EmbedBuilder() - .Color((uint?) DiscordUtils.Gray.Value) + .Color(DiscordUtils.Gray) .Footer(new($"Since {breakdown.RangeStart.FormatZoned(tz)} ({actualPeriod.FormatDuration()} ago)")); var maxEntriesToDisplay = 24; // max 25 fields allowed in embed - reserve 1 for "others" diff --git a/PluralKit.Bot/Services/ShardInfoService.cs b/PluralKit.Bot/Services/ShardInfoService.cs index 7bbbe2a1..a89a0bd2 100644 --- a/PluralKit.Bot/Services/ShardInfoService.cs +++ b/PluralKit.Bot/Services/ShardInfoService.cs @@ -7,6 +7,8 @@ using App.Metrics; using DSharpPlus; using DSharpPlus.EventArgs; +using Myriad.Gateway; + using NodaTime; using NodaTime.Extensions; @@ -144,7 +146,7 @@ namespace PluralKit.Bot return Task.CompletedTask; } - public ShardInfo GetShardInfo(DiscordClient shard) => _shardInfo[shard.ShardId]; + public ShardInfo GetShardInfo(Shard shard) => _shardInfo[shard.ShardId]; public ICollection Shards => _shardInfo.Values; } diff --git a/PluralKit.Bot/Utils/ContextUtils.cs b/PluralKit.Bot/Utils/ContextUtils.cs index c245d549..58b281c5 100644 --- a/PluralKit.Bot/Utils/ContextUtils.cs +++ b/PluralKit.Bot/Utils/ContextUtils.cs @@ -88,7 +88,7 @@ namespace PluralKit.Bot { public static async Task ConfirmWithReply(this Context ctx, string expectedReply) { bool Predicate(MessageCreateEvent e) => - e.Author.Id == ctx.AuthorNew.Id && e.ChannelId == ctx.Channel.Id; + e.Author.Id == ctx.AuthorNew.Id && e.ChannelId == ctx.ChannelNew.Id; var msg = await ctx.Services.Resolve>() .WaitFor(Predicate, Duration.FromMinutes(1)); @@ -217,7 +217,7 @@ namespace PluralKit.Bot { if (idx < items.Count) return items[idx]; } - var __ = ctx.RestNew.DeleteUserReaction(msg.ChannelId, msg.Id, reaction.Emoji, ctx.Author.Id); + var __ = ctx.RestNew.DeleteUserReaction(msg.ChannelId, msg.Id, reaction.Emoji, ctx.AuthorNew.Id); await ctx.RestNew.EditMessage(msg.ChannelId, msg.Id, new() { diff --git a/PluralKit.Bot/Utils/DiscordUtils.cs b/PluralKit.Bot/Utils/DiscordUtils.cs index 281324e8..1581f689 100644 --- a/PluralKit.Bot/Utils/DiscordUtils.cs +++ b/PluralKit.Bot/Utils/DiscordUtils.cs @@ -28,10 +28,10 @@ namespace PluralKit.Bot { public static class DiscordUtils { - public static DiscordColor Blue = new DiscordColor(0x1f99d8); - public static DiscordColor Green = new DiscordColor(0x00cc78); - public static DiscordColor Red = new DiscordColor(0xef4b3d); - public static DiscordColor Gray = new DiscordColor(0x979c9f); + public const uint Blue = 0x1f99d8; + public const uint Green = 0x00cc78; + public const uint Red = 0xef4b3d; + public const uint Gray = 0x979c9f; public static Permissions DM_PERMISSIONS = (Permissions) 0b00000_1000110_1011100110000_000000; @@ -154,10 +154,10 @@ namespace PluralKit.Bot return cache != null && cache.TryGetValue(id, out user); } - public static DiscordColor? ToDiscordColor(this string color) + public static uint? ToDiscordColor(this string color) { - if (int.TryParse(color, NumberStyles.HexNumber, null, out var colorInt)) - return new DiscordColor(colorInt); + if (uint.TryParse(color, NumberStyles.HexNumber, null, out var colorInt)) + return colorInt; throw new ArgumentException($"Invalid color string '{color}'."); }