mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
More restructuring
This commit is contained in:
@@ -1,6 +1,29 @@
|
||||
namespace Nadeko.Bot.Common;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Nadeko.Bot.Common;
|
||||
|
||||
public interface ICurrencyProvider
|
||||
{
|
||||
string GetCurrencySign();
|
||||
}
|
||||
|
||||
public static class CurrencyHelper
|
||||
{
|
||||
public static string N<T>(T cur, IFormatProvider format)
|
||||
where T : INumber<T>
|
||||
=> cur.ToString("C0", format);
|
||||
|
||||
public static string N<T>(T cur, CultureInfo culture, string currencySign)
|
||||
where T : INumber<T>
|
||||
=> N(cur, GetCurrencyFormat(culture, currencySign));
|
||||
|
||||
private static IFormatProvider GetCurrencyFormat(CultureInfo culture, string currencySign)
|
||||
{
|
||||
var flowersCurrencyCulture = (CultureInfo)culture.Clone();
|
||||
flowersCurrencyCulture.NumberFormat.CurrencySymbol = currencySign;
|
||||
flowersCurrencyCulture.NumberFormat.CurrencyNegativePattern = 5;
|
||||
|
||||
return flowersCurrencyCulture;
|
||||
}
|
||||
}
|
@@ -25,6 +25,9 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<Publish>True</Publish>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||
|
||||
<ProjectReference Include="..\Nadeko.Medusa\Nadeko.Medusa.csproj" />
|
||||
|
||||
<ProjectReference Include="..\NadekoBot.Generators\NadekoBot.Generators.csproj" OutputItemType="Analyzer" />
|
||||
|
6
src/Nadeko.Bot.Common/Services/ITimezoneService.cs
Normal file
6
src/Nadeko.Bot.Common/Services/ITimezoneService.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public interface ITimezoneService
|
||||
{
|
||||
TimeZoneInfo GetTimeZoneOrUtc(ulong? guildId);
|
||||
}
|
188
src/Nadeko.Bot.Common/Services/Impl/StatsService.cs
Normal file
188
src/Nadeko.Bot.Common/Services/Impl/StatsService.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
#nullable disable
|
||||
using Humanizer.Localisation;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public sealed class StatsService : IStatsService, IReadyExecutor, INService
|
||||
{
|
||||
public const string BOT_VERSION = "5.0.0-alpha1";
|
||||
|
||||
public string Author
|
||||
=> "Kwoth#2452";
|
||||
|
||||
public double MessagesPerSecond
|
||||
=> MessageCounter / GetUptime().TotalSeconds;
|
||||
|
||||
public long TextChannels
|
||||
=> Interlocked.Read(ref textChannels);
|
||||
|
||||
public long VoiceChannels
|
||||
=> Interlocked.Read(ref voiceChannels);
|
||||
|
||||
public long MessageCounter
|
||||
=> Interlocked.Read(ref messageCounter);
|
||||
|
||||
public long CommandsRan
|
||||
=> Interlocked.Read(ref commandsRan);
|
||||
|
||||
private readonly Process _currentProcess = Process.GetCurrentProcess();
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly DateTime _started;
|
||||
|
||||
private long textChannels;
|
||||
private long voiceChannels;
|
||||
private long messageCounter;
|
||||
private long commandsRan;
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public StatsService(
|
||||
DiscordSocketClient client,
|
||||
CommandHandler cmdHandler,
|
||||
IBotCredentials creds,
|
||||
IHttpClientFactory factory)
|
||||
{
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
_httpFactory = factory;
|
||||
|
||||
_started = DateTime.UtcNow;
|
||||
_client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref messageCounter));
|
||||
cmdHandler.CommandExecuted += (_, _) => Task.FromResult(Interlocked.Increment(ref commandsRan));
|
||||
|
||||
_client.ChannelCreated += c =>
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
if (c is ITextChannel)
|
||||
Interlocked.Increment(ref textChannels);
|
||||
else if (c is IVoiceChannel)
|
||||
Interlocked.Increment(ref voiceChannels);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_client.ChannelDestroyed += c =>
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
if (c is ITextChannel)
|
||||
Interlocked.Decrement(ref textChannels);
|
||||
else if (c is IVoiceChannel)
|
||||
Interlocked.Decrement(ref voiceChannels);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_client.GuildAvailable += g =>
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
var tc = g.Channels.Count(cx => cx is ITextChannel);
|
||||
var vc = g.Channels.Count - tc;
|
||||
Interlocked.Add(ref textChannels, tc);
|
||||
Interlocked.Add(ref voiceChannels, vc);
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_client.JoinedGuild += g =>
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
var tc = g.Channels.Count(cx => cx is ITextChannel);
|
||||
var vc = g.Channels.Count - tc;
|
||||
Interlocked.Add(ref textChannels, tc);
|
||||
Interlocked.Add(ref voiceChannels, vc);
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_client.GuildUnavailable += g =>
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
var tc = g.Channels.Count(cx => cx is ITextChannel);
|
||||
var vc = g.Channels.Count - tc;
|
||||
Interlocked.Add(ref textChannels, -tc);
|
||||
Interlocked.Add(ref voiceChannels, -vc);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_client.LeftGuild += g =>
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
var tc = g.Channels.Count(cx => cx is ITextChannel);
|
||||
var vc = g.Channels.Count - tc;
|
||||
Interlocked.Add(ref textChannels, -tc);
|
||||
Interlocked.Add(ref voiceChannels, -vc);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeChannelCount()
|
||||
{
|
||||
var guilds = _client.Guilds;
|
||||
textChannels = guilds.Sum(static g => g.Channels.Count(static cx => cx is ITextChannel));
|
||||
voiceChannels = guilds.Sum(static g => g.Channels.Count(static cx => cx is IVoiceChannel));
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
InitializeChannelCount();
|
||||
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
||||
do
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_creds.BotListToken))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
using var http = _httpFactory.CreateClient();
|
||||
using var content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "shard_count", _creds.TotalShards.ToString() },
|
||||
{ "shard_id", _client.ShardId.ToString() },
|
||||
{ "server_count", _client.Guilds.Count().ToString() }
|
||||
});
|
||||
content.Headers.Clear();
|
||||
content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
|
||||
http.DefaultRequestHeaders.Add("Authorization", _creds.BotListToken);
|
||||
|
||||
using var res = await http.PostAsync(
|
||||
new Uri($"https://discordbots.org/api/bots/{_client.CurrentUser.Id}/stats"),
|
||||
content);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error in botlist post");
|
||||
}
|
||||
} while (await timer.WaitForNextTickAsync());
|
||||
}
|
||||
|
||||
public TimeSpan GetUptime()
|
||||
=> DateTime.UtcNow - _started;
|
||||
|
||||
public string GetUptimeString(string separator = ", ")
|
||||
{
|
||||
var time = GetUptime();
|
||||
return time.Humanize(3, maxUnit: TimeUnit.Day, minUnit: TimeUnit.Minute);
|
||||
}
|
||||
|
||||
public double GetPrivateMemoryMegabytes()
|
||||
{
|
||||
_currentProcess.Refresh();
|
||||
return _currentProcess.PrivateMemorySize64 / 1.Megabytes().Bytes;
|
||||
}
|
||||
}
|
49
src/Nadeko.Bot.Common/TypeReaders/GuildDateTimeTypeReader.cs
Normal file
49
src/Nadeko.Bot.Common/TypeReaders/GuildDateTimeTypeReader.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Common.TypeReaders;
|
||||
|
||||
public sealed class GuildDateTimeTypeReader : NadekoTypeReader<GuildDateTime>
|
||||
{
|
||||
private readonly ITimezoneService _gts;
|
||||
|
||||
public GuildDateTimeTypeReader(ITimezoneService gts)
|
||||
=> _gts = gts;
|
||||
|
||||
public override ValueTask<TypeReaderResult<GuildDateTime>> ReadAsync(ICommandContext context, string input)
|
||||
{
|
||||
var gdt = Parse(context.Guild.Id, input);
|
||||
if (gdt is null)
|
||||
{
|
||||
return new(TypeReaderResult.FromError<GuildDateTime>(CommandError.ParseFailed,
|
||||
"Input string is in an incorrect format."));
|
||||
}
|
||||
|
||||
return new(TypeReaderResult.FromSuccess(gdt));
|
||||
}
|
||||
|
||||
private GuildDateTime Parse(ulong guildId, string input)
|
||||
{
|
||||
if (!DateTime.TryParse(input, out var dt))
|
||||
return null;
|
||||
|
||||
var tz = _gts.GetTimeZoneOrUtc(guildId);
|
||||
|
||||
return new(tz, dt);
|
||||
}
|
||||
}
|
||||
|
||||
public class GuildDateTime
|
||||
{
|
||||
public TimeZoneInfo Timezone { get; }
|
||||
public DateTime CurrentGuildTime { get; }
|
||||
public DateTime InputTime { get; }
|
||||
public DateTime InputTimeUtc { get; }
|
||||
|
||||
public GuildDateTime(TimeZoneInfo guildTimezone, DateTime inputTime)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
Timezone = guildTimezone;
|
||||
CurrentGuildTime = TimeZoneInfo.ConvertTime(now, TimeZoneInfo.Utc, Timezone);
|
||||
InputTime = inputTime;
|
||||
InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user