chore: code cleanup (mostly whitespace / remove unused imports)

This commit is contained in:
spiral 2022-03-30 04:36:22 -04:00
parent 56155782c3
commit 7afba4ea95
No known key found for this signature in database
GPG Key ID: 244A11E4B0BCF40E
29 changed files with 84 additions and 105 deletions

View File

@ -11,5 +11,4 @@ public record MessageRequest
public Embed[]? Embeds { get; set; } public Embed[]? Embeds { get; set; }
public MessageComponent[]? Components { get; set; } public MessageComponent[]? Components { get; set; }
public Message.Reference? MessageReference { get; set; } public Message.Reference? MessageReference { get; set; }
} }

View File

@ -19,6 +19,7 @@ namespace PluralKit.API;
public class PrivateController: PKControllerBase public class PrivateController: PKControllerBase
{ {
private readonly RedisService _redis; private readonly RedisService _redis;
public PrivateController(IServiceProvider svc) : base(svc) public PrivateController(IServiceProvider svc) : base(svc)
{ {
_redis = svc.GetRequiredService<RedisService>(); _redis = svc.GetRequiredService<RedisService>();

View File

@ -2,12 +2,9 @@ using System.Reflection;
using Autofac; using Autofac;
using App.Metrics.AspNetCore;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -100,7 +97,9 @@ public class Startup
.As<IConfiguration>(); .As<IConfiguration>();
builder.RegisterModule(new ConfigModule<ApiConfig>("API")); builder.RegisterModule(new ConfigModule<ApiConfig>("API"));
builder.RegisterModule(new LoggingModule("api", builder.RegisterModule(new LoggingModule("api",
cfg: new LoggerConfiguration().Filter.ByExcluding(exc => exc.Exception is PKError || exc.Exception.IsUserError()))); cfg: new LoggerConfiguration().Filter.ByExcluding(
exc => exc.Exception is PKError || exc.Exception.IsUserError()
)));
// builder.RegisterModule(new MetricsModule("API")); // builder.RegisterModule(new MetricsModule("API"));
builder.RegisterModule<DataStoreModule>(); builder.RegisterModule<DataStoreModule>();
builder.RegisterModule<APIModule>(); builder.RegisterModule<APIModule>();

View File

@ -1,5 +1,3 @@
using System.Net.WebSockets;
using App.Metrics; using App.Metrics;
using Autofac; using Autofac;
@ -123,7 +121,11 @@ public class Bot
{ {
Activities = new[] Activities = new[]
{ {
new Activity {Name = "Restarting... (please wait)", Type = ActivityType.Game} new Activity
{
Name = "Restarting... (please wait)",
Type = ActivityType.Game
}
}, },
Status = GatewayStatusUpdate.UserStatus.Idle Status = GatewayStatusUpdate.UserStatus.Idle
}))); })));

View File

@ -1,5 +1,4 @@
using App.Metrics; using App.Metrics;
using App.Metrics.Gauge;
using App.Metrics.Meter; using App.Metrics.Meter;
using App.Metrics.Timer; using App.Metrics.Timer;

View File

@ -12,6 +12,7 @@ public partial class CommandTree
await ctx.Reply( await ctx.Reply(
$"{Emojis.Error} Unknown command `pk;{ctx.FullCommand().Truncate(100)}`. Perhaps you meant to use one of the following commands?\n{commandListStr}\n\nFor a full list of possible commands, see <https://pluralkit.me/commands>."); $"{Emojis.Error} Unknown command `pk;{ctx.FullCommand().Truncate(100)}`. Perhaps you meant to use one of the following commands?\n{commandListStr}\n\nFor a full list of possible commands, see <https://pluralkit.me/commands>.");
} }
private async Task PrintCommandExpectedError(Context ctx, params Command[] potentialCommands) private async Task PrintCommandExpectedError(Context ctx, params Command[] potentialCommands)
{ {
var commandListStr = CreatePotentialCommandList(potentialCommands); var commandListStr = CreatePotentialCommandList(potentialCommands);

View File

@ -1,10 +1,6 @@
using Humanizer;
using Myriad.Builders; using Myriad.Builders;
using Myriad.Types; using Myriad.Types;
using NodaTime;
using PluralKit.Core; using PluralKit.Core;
namespace PluralKit.Bot; namespace PluralKit.Bot;

View File

@ -1,15 +1,11 @@
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Humanizer;
using Myriad.Builders; using Myriad.Builders;
using Myriad.Types; using Myriad.Types;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NodaTime;
using PluralKit.Core; using PluralKit.Core;
namespace PluralKit.Bot; namespace PluralKit.Bot;

View File

@ -1,6 +1,5 @@
using System.Text; using System.Text;
using Myriad.Extensions;
using Myriad.Rest.Exceptions; using Myriad.Rest.Exceptions;
using Myriad.Rest.Types; using Myriad.Rest.Types;
using Myriad.Rest.Types.Requests; using Myriad.Rest.Types.Requests;

View File

@ -5,8 +5,6 @@ using Humanizer;
using Myriad.Builders; using Myriad.Builders;
using Myriad.Types; using Myriad.Types;
using NodaTime;
using PluralKit.Core; using PluralKit.Core;
namespace PluralKit.Bot; namespace PluralKit.Bot;

View File

@ -323,6 +323,7 @@ public class MemberEdit
} }
private string boldIf(string str, bool condition) => condition ? $"**{str}**" : str; private string boldIf(string str, bool condition) => condition ? $"**{str}**" : str;
private async Task<EmbedBuilder> CreateMemberNameInfoEmbed(Context ctx, PKMember target) private async Task<EmbedBuilder> CreateMemberNameInfoEmbed(Context ctx, PKMember target)
{ {
var lcx = ctx.LookupContextFor(target.System); var lcx = ctx.LookupContextFor(target.System);

View File

@ -20,6 +20,7 @@ namespace PluralKit.Bot;
public class ProxiedMessage public class ProxiedMessage
{ {
private static readonly Duration EditTimeout = Duration.FromMinutes(10); private static readonly Duration EditTimeout = Duration.FromMinutes(10);
// private readonly IDiscordCache _cache; // private readonly IDiscordCache _cache;
private readonly IClock _clock; private readonly IClock _clock;

View File

@ -3,10 +3,6 @@ using System.Text.RegularExpressions;
using Myriad.Builders; using Myriad.Builders;
using Myriad.Types; using Myriad.Types;
using NodaTime;
using NodaTime.Text;
using NodaTime.TimeZones;
using PluralKit.Core; using PluralKit.Core;
namespace PluralKit.Bot; namespace PluralKit.Bot;
@ -479,9 +475,10 @@ public class SystemEdit
} }
else else
{ {
throw new PKSyntaxError( throw new PKSyntaxError("This system does not have a banner image set."
"This system does not have a banner image set." + (isOwnSystem ? "Set one by attaching an image to this command, or by passing an image URL or @mention." : "")); + (isOwnSystem ? "Set one by attaching an image to this command, or by passing an image URL or @mention." : ""));
} }
return; return;
} }
@ -514,7 +511,6 @@ public class SystemEdit
? ctx.Reply(msg, new EmbedBuilder().Image(new Embed.EmbedImage(img.Url)).Build()) ? ctx.Reply(msg, new EmbedBuilder().Image(new Embed.EmbedImage(img.Url)).Build())
: ctx.Reply(msg)); : ctx.Reply(msg));
} }
} }
public async Task Delete(Context ctx, PKSystem target) public async Task Delete(Context ctx, PKSystem target)

View File

@ -52,12 +52,7 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
_dmCache = dmCache; _dmCache = dmCache;
} }
// for now, only return error messages for explicit commands public ulong? ErrorChannelFor(MessageCreateEvent evt, ulong userId) => evt.ChannelId;
public ulong? ErrorChannelFor(MessageCreateEvent evt, ulong userId)
{
return evt.ChannelId;
}
private bool IsDuplicateMessage(Message msg) => private bool IsDuplicateMessage(Message msg) =>
// We consider a message duplicate if it has the same ID as the previous message that hit the gateway // We consider a message duplicate if it has the same ID as the previous message that hit the gateway
_lastMessageCache.GetLastMessage(msg.ChannelId)?.Current.Id == msg.Id; _lastMessageCache.GetLastMessage(msg.ChannelId)?.Current.Id == msg.Id;

View File

@ -63,7 +63,8 @@ public class ProxyService
if (autoproxySettings.AutoproxyMode == AutoproxyMode.Latch && IsUnlatch(message)) if (autoproxySettings.AutoproxyMode == AutoproxyMode.Latch && IsUnlatch(message))
{ {
// "unlatch" // "unlatch"
await _repo.UpdateAutoproxy(ctx.SystemId.Value, guild.Id, null, new() { await _repo.UpdateAutoproxy(ctx.SystemId.Value, guild.Id, null, new()
{
AutoproxyMember = null AutoproxyMember = null
}); });
return false; return false;

View File

@ -18,8 +18,8 @@ public class ErrorMessageService
// globally rate limit errors for now, don't want to spam users when something breaks // globally rate limit errors for now, don't want to spam users when something breaks
private static readonly Duration MinErrorInterval = Duration.FromSeconds(10); private static readonly Duration MinErrorInterval = Duration.FromSeconds(10);
private static readonly Duration IntervalFromStartup = Duration.FromMinutes(2); private static readonly Duration IntervalFromStartup = Duration.FromMinutes(2);
private readonly ILogger _logger;
private readonly ILogger _logger;
private readonly BotConfig _botConfig; private readonly BotConfig _botConfig;
private readonly IMetrics _metrics; private readonly IMetrics _metrics;
private readonly DiscordApiClient _rest; private readonly DiscordApiClient _rest;

View File

@ -1,6 +1,5 @@
using Serilog; using Serilog;
using Myriad.Cache;
using Myriad.Gateway; using Myriad.Gateway;
using Myriad.Rest; using Myriad.Rest;
@ -10,11 +9,11 @@ namespace PluralKit.Bot;
public class PrivateChannelService public class PrivateChannelService
{ {
private static readonly Dictionary<ulong, ulong> _channelsCache = new();
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ModelRepository _repo; private readonly ModelRepository _repo;
private readonly DiscordApiClient _rest; private readonly DiscordApiClient _rest;
private static Dictionary<ulong, ulong> _channelsCache = new();
public PrivateChannelService(ILogger logger, ModelRepository repo, DiscordApiClient rest) public PrivateChannelService(ILogger logger, ModelRepository repo, DiscordApiClient rest)
{ {
_logger = logger; _logger = logger;

View File

@ -169,7 +169,8 @@ public static class ContextUtils
if (currentPage < 0) currentPage += pageCount; if (currentPage < 0) currentPage += pageCount;
// If we can, remove the user's reaction (so they can press again quickly) // If we can, remove the user's reaction (so they can press again quickly)
if ((await ctx.BotPermissions).HasFlag(PermissionSet.ManageMessages)) try if ((await ctx.BotPermissions).HasFlag(PermissionSet.ManageMessages))
try
{ {
await ctx.Rest.DeleteUserReaction(msg.ChannelId, msg.Id, reaction.Emoji, reaction.UserId); await ctx.Rest.DeleteUserReaction(msg.ChannelId, msg.Id, reaction.Emoji, reaction.UserId);
} }

View File

@ -5,8 +5,6 @@ using App.Metrics;
using Dapper; using Dapper;
using Npgsql;
using SqlKata; using SqlKata;
namespace PluralKit.Core; namespace PluralKit.Core;
@ -82,8 +80,10 @@ internal partial class Database: IDatabase
var query = _compiler.Compile(q); var query = _compiler.Compile(q);
using var conn = await Obtain(); using var conn = await Obtain();
using (_metrics.Measure.Timer.Time(CoreMetrics.DatabaseQuery, new MetricTags("Query", queryName))) using (_metrics.Measure.Timer.Time(CoreMetrics.DatabaseQuery, new MetricTags("Query", queryName)))
{
await foreach (var val in conn.QueryStreamAsync<T>(query.Sql, query.NamedBindings)) await foreach (var val in conn.QueryStreamAsync<T>(query.Sql, query.NamedBindings))
yield return val; yield return val;
}
} }
// the procedures (message_context and proxy_members, as of writing) have their own metrics tracking elsewhere // the procedures (message_context and proxy_members, as of writing) have their own metrics tracking elsewhere

View File

@ -27,22 +27,22 @@ as $$
where accounts.uid = account_id), where accounts.uid = account_id),
guild as (select * from servers where id = guild_id) guild as (select * from servers where id = guild_id)
select select
system.id as system_id, system.id as system_id,
system.is_deleting, system.is_deleting,
guild.log_channel, guild.log_channel,
(channel_id = any(guild.blacklist)) as in_blacklist, (channel_id = any (guild.blacklist)) as in_blacklist,
(channel_id = any(guild.log_blacklist)) as in_log_blacklist, (channel_id = any (guild.log_blacklist)) as in_log_blacklist,
coalesce(guild.log_cleanup_enabled, false), coalesce(guild.log_cleanup_enabled, false),
coalesce(system_guild.proxy_enabled, true) as proxy_enabled, coalesce(system_guild.proxy_enabled, true) as proxy_enabled,
system_last_switch.switch as last_switch, system_last_switch.switch as last_switch,
system_last_switch.members as last_switch_members, system_last_switch.members as last_switch_members,
system_last_switch.timestamp as last_switch_timestamp, system_last_switch.timestamp as last_switch_timestamp,
system.tag as system_tag, system.tag as system_tag,
system.guild_tag as system_guild_tag, system.guild_tag as system_guild_tag,
coalesce(system.tag_enabled, true) as tag_enabled, coalesce(system.tag_enabled, true) as tag_enabled,
system.avatar_url as system_avatar, system.avatar_url as system_avatar,
system.account_autoproxy as allow_autoproxy, system.account_autoproxy as allow_autoproxy,
system.latch_timeout as latch_timeout system.latch_timeout as latch_timeout
-- We need a "from" clause, so we just use some bogus data that's always present -- We need a "from" clause, so we just use some bogus data that's always present
-- This ensure we always have exactly one row going forward, so we can left join afterwards and still get data -- This ensure we always have exactly one row going forward, so we can left join afterwards and still get data
from (select 1) as _placeholder from (select 1) as _placeholder
@ -60,7 +60,7 @@ create function proxy_members(account_id bigint, guild_id bigint)
id int, id int,
proxy_tags proxy_tag[], proxy_tags proxy_tag[],
keep_proxy bool, keep_proxy bool,
server_name text, server_name text,
display_name text, display_name text,
name text, name text,
@ -75,22 +75,22 @@ create function proxy_members(account_id bigint, guild_id bigint)
as $$ as $$
select select
-- Basic data -- Basic data
members.id as id, members.id as id,
members.proxy_tags as proxy_tags, members.proxy_tags as proxy_tags,
members.keep_proxy as keep_proxy, members.keep_proxy as keep_proxy,
-- Name info -- Name info
member_guild.display_name as server_name, member_guild.display_name as server_name,
members.display_name as display_name, members.display_name as display_name,
members.name as name, members.name as name,
-- Avatar info -- Avatar info
member_guild.avatar_url as server_avatar, member_guild.avatar_url as server_avatar,
members.avatar_url as avatar, members.avatar_url as avatar,
members.color as color, members.color as color,
members.allow_autoproxy as allow_autoproxy members.allow_autoproxy as allow_autoproxy
from accounts from accounts
inner join systems on systems.id = accounts.system inner join systems on systems.id = accounts.system
inner join members on members.system = systems.id inner join members on members.system = systems.id

View File

@ -1,6 +1,6 @@
-- Returns one row per system, containing info about latest switch + array of member IDs (for future joins) -- Returns one row per system, containing info about latest switch + array of member IDs (for future joins)
create view system_last_switch as create view system_last_switch as
select systems.id as system, select systems.id as system,
last_switch.id as switch, last_switch.id as switch,
last_switch.timestamp as timestamp, last_switch.timestamp as timestamp,
array(select member from switch_members where switch_members.switch = last_switch.id order by switch_members.id) as members array(select member from switch_members where switch_members.switch = last_switch.id order by switch_members.id) as members
@ -10,16 +10,16 @@ from systems
-- Returns one row for every current fronter in a system, w/ some member info -- Returns one row for every current fronter in a system, w/ some member info
create view system_fronters as create view system_fronters as
select select
systems.id as system_id, systems.id as system_id,
last_switch.id as switch_id, last_switch.id as switch_id,
last_switch.timestamp as switch_timestamp, last_switch.timestamp as switch_timestamp,
members.id as member_id, members.id as member_id,
members.hid as member_hid, members.hid as member_hid,
members.name as member_name members.name as member_name
from systems from systems
-- TODO: is there a more efficient way of doing this search? might need to index on timestamp if we haven't in prod -- TODO: is there a more efficient way of doing this search? might need to index on timestamp if we haven't in prod
inner join lateral (select * from switches where switches.system = systems.id order by timestamp desc limit 1) as last_switch on true inner join lateral (select * from switches where switches.system = systems.id order by timestamp desc limit 1) as last_switch on true
-- change to left join to handle memberless switches? -- change to left join to handle memberless switches?
inner join switch_members on switch_members.switch = last_switch.system inner join switch_members on switch_members.switch = last_switch.system
inner join members on members.id = switch_members.member inner join members on members.id = switch_members.member
@ -30,8 +30,8 @@ create view member_list as
select members.*, select members.*,
-- Find last message ID -- Find last message ID
-- max(mid) does full table scan, order by/limit uses index (dunno why, but it works!) -- max(mid) does full table scan, order by/limit uses index (dunno why, but it works!)
-- (select mid from messages where messages.member = members.id order by mid desc nulls last limit 1) as last_message, -- (select mid from messages where messages.member = members.id order by mid desc nulls last limit 1) as last_message,
-- Find last switch timestamp -- Find last switch timestamp
( (
select max(switches.timestamp) select max(switches.timestamp)
@ -39,20 +39,20 @@ select members.*,
inner join switches on switches.id = switch_members.switch inner join switches on switches.id = switch_members.switch
where switch_members.member = members.id where switch_members.member = members.id
) as last_switch_time, ) as last_switch_time,
-- Extract month/day from birthday and "force" the year identical (just using 4) -> month/day only sorting! -- Extract month/day from birthday and "force" the year identical (just using 4) -> month/day only sorting!
case when members.birthday is not null then case when members.birthday is not null then
make_date( make_date(
4, 4,
extract(month from members.birthday)::integer, extract(month from members.birthday)::integer,
extract(day from members.birthday)::integer extract(day from members.birthday)::integer
) end as birthday_md, ) end as birthday_md,
-- Extract member description as seen by "the public" -- Extract member description as seen by "the public"
case case
-- Privacy '1' = public; just return description as normal -- Privacy '1' = public; just return description as normal
when members.description_privacy = 1 then members.description when members.description_privacy = 1 then members.description
-- Any other privacy (rn just '2'), return null description (missing case = null in SQL) -- Any other privacy (rn just '2'), return null description (missing case = null in SQL)
end as public_description end as public_description
from members; from members;
@ -60,16 +60,16 @@ create view group_list as
select groups.*, select groups.*,
-- Find public group member count -- Find public group member count
( (
select count(*) from group_members select count(*) from group_members
inner join members on group_members.member_id = members.id inner join members on group_members.member_id = members.id
where where
group_members.group_id = groups.id and members.member_visibility = 1 group_members.group_id = groups.id and members.member_visibility = 1
) as public_member_count, ) as public_member_count,
-- Find private group member count -- Find private group member count
( (
select count(*) from group_members select count(*) from group_members
inner join members on group_members.member_id = members.id inner join members on group_members.member_id = members.id
where where
group_members.group_id = groups.id group_members.group_id = groups.id
) as total_member_count ) as total_member_count
from groups; from groups;

View File

@ -1,8 +1,6 @@
#nullable enable #nullable enable
using System.Collections; using System.Collections;
using Dapper;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace PluralKit.Core; namespace PluralKit.Core;

View File

@ -1,6 +1,5 @@
using Dapper.Contrib.Extensions; using Dapper.Contrib.Extensions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
@ -90,7 +89,8 @@ public static class PKSystemExt
ctx == LookupContext.ByOwner ? system.DescriptionPrivacy.ToJsonString() : null); ctx == LookupContext.ByOwner ? system.DescriptionPrivacy.ToJsonString() : null);
o.Add("member_list_privacy", o.Add("member_list_privacy",
ctx == LookupContext.ByOwner ? system.MemberListPrivacy.ToJsonString() : null); ctx == LookupContext.ByOwner ? system.MemberListPrivacy.ToJsonString() : null);
o.Add("group_list_privacy", ctx == LookupContext.ByOwner ? system.GroupListPrivacy.ToJsonString() : null); o.Add("group_list_privacy",
ctx == LookupContext.ByOwner ? system.GroupListPrivacy.ToJsonString() : null);
o.Add("front_privacy", ctx == LookupContext.ByOwner ? system.FrontPrivacy.ToJsonString() : null); o.Add("front_privacy", ctx == LookupContext.ByOwner ? system.FrontPrivacy.ToJsonString() : null);
o.Add("front_history_privacy", o.Add("front_history_privacy",
ctx == LookupContext.ByOwner ? system.FrontHistoryPrivacy.ToJsonString() : null); ctx == LookupContext.ByOwner ? system.FrontHistoryPrivacy.ToJsonString() : null);

View File

@ -1,5 +1,3 @@
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using SqlKata; using SqlKata;

View File

@ -1,8 +1,6 @@
#nullable enable #nullable enable
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NodaTime;
using SqlKata; using SqlKata;
namespace PluralKit.Core; namespace PluralKit.Core;

View File

@ -24,6 +24,7 @@ public class LoggingModule: Module
public LoggingModule(string component, Action<LoggerConfiguration> fn = null, LoggerConfiguration cfg = null) public LoggingModule(string component, Action<LoggerConfiguration> fn = null, LoggerConfiguration cfg = null)
{ {
_component = component; _component = component;
// todo: this is messy and not really used anywhere...?
_fn = fn ?? (_ => { }); _fn = fn ?? (_ => { });
_cfg = cfg ?? new LoggerConfiguration(); _cfg = cfg ?? new LoggerConfiguration();
} }

View File

@ -9,6 +9,7 @@ public static class Limits
public static readonly int MaxMemberCount = 1000; public static readonly int MaxMemberCount = 1000;
public static readonly int MaxGroupCount = 250; public static readonly int MaxGroupCount = 250;
public static int WarnThreshold(int limit) => limit - 50; public static int WarnThreshold(int limit) => limit - 50;
public static readonly int MaxDescriptionLength = 1000; public static readonly int MaxDescriptionLength = 1000;
public static readonly int MaxProxyTagLength = 100; public static readonly int MaxProxyTagLength = 100;
public static readonly int MaxSwitchMemberCount = 150; public static readonly int MaxSwitchMemberCount = 150;

View File

@ -4,7 +4,7 @@ namespace PluralKit.Core;
public static class Proto public static class Proto
{ {
private static Dictionary<string, MessageParser> _parser = new(); private static readonly Dictionary<string, MessageParser> _parser = new();
public static byte[] Marshal(this IMessage message) => message.ToByteArray(); public static byte[] Marshal(this IMessage message) => message.ToByteArray();
@ -12,11 +12,11 @@ public static class Proto
{ {
var type = typeof(T).ToString(); var type = typeof(T).ToString();
if (_parser.ContainsKey(type)) if (_parser.ContainsKey(type))
return (T)_parser[type].ParseFrom(message);
else
{ {
_parser.Add(type, new MessageParser<T>(() => new T())); return (T)_parser[type].ParseFrom(message);
return Unmarshal<T>(message);
} }
_parser.Add(type, new MessageParser<T>(() => new T()));
return Unmarshal<T>(message);
} }
} }

View File

@ -22,5 +22,4 @@ public static class Metrics
Context = "Bot", Context = "Bot",
MeasurementUnit = Unit.Items MeasurementUnit = Unit.Items
}; };
} }