- Removed NadekoCommand and Aliases attribute from all commands

- All commands must be marked as partial
- Added [Cmd] Attribute to all commands
- Cmd Attribute comes from the source generator which adds [NadekoCommand] and [Aliases] Attribute to each command
- Should be updated in the future probably to be more performant and maybe add extra data to the commands
- Started reorganizing modules and submodules
This commit is contained in:
Kwoth
2021-12-31 16:04:12 +01:00
parent 6eee161b6b
commit 25eeffa163
107 changed files with 1620 additions and 3236 deletions

View File

@@ -0,0 +1,510 @@
#nullable disable
using NadekoBot.Modules.Administration.Services;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
[Group]
public partial class SelfCommands : NadekoSubmodule<SelfService>
{
public enum SettableUserStatus
{
Online,
Invisible,
Idle,
Dnd
}
private readonly DiscordSocketClient _client;
private readonly IBotStrings _strings;
private readonly ICoordinator _coord;
public SelfCommands(DiscordSocketClient client, IBotStrings strings, ICoordinator coord)
{
_client = client;
_strings = strings;
_coord = coord;
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async partial Task StartupCommandAdd([Leftover] string cmdText)
{
if (cmdText.StartsWith(Prefix + "die", StringComparison.InvariantCulture))
return;
var guser = (IGuildUser)ctx.User;
var cmd = new AutoCommand
{
CommandText = cmdText,
ChannelId = ctx.Channel.Id,
ChannelName = ctx.Channel.Name,
GuildId = ctx.Guild?.Id,
GuildName = ctx.Guild?.Name,
VoiceChannelId = guser.VoiceChannel?.Id,
VoiceChannelName = guser.VoiceChannel?.Name,
Interval = 0
};
_service.AddNewAutoCommand(cmd);
await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.scadd))
.AddField(GetText(strs.server),
cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}",
true)
.AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true)
.AddField(GetText(strs.command_text), cmdText));
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async partial Task AutoCommandAdd(int interval, [Leftover] string cmdText)
{
if (cmdText.StartsWith(Prefix + "die", StringComparison.InvariantCulture))
return;
if (interval < 5)
return;
var guser = (IGuildUser)ctx.User;
var cmd = new AutoCommand
{
CommandText = cmdText,
ChannelId = ctx.Channel.Id,
ChannelName = ctx.Channel.Name,
GuildId = ctx.Guild?.Id,
GuildName = ctx.Guild?.Name,
VoiceChannelId = guser.VoiceChannel?.Id,
VoiceChannelName = guser.VoiceChannel?.Name,
Interval = interval
};
_service.AddNewAutoCommand(cmd);
await ReplyConfirmLocalizedAsync(strs.autocmd_add(Format.Code(Format.Sanitize(cmdText)), cmd.Interval));
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async partial Task StartupCommandsList(int page = 1)
{
if (page-- < 1)
return;
var scmds = _service.GetStartupCommands().Skip(page * 5).Take(5).ToList();
if (scmds.Count == 0)
{
await ReplyErrorLocalizedAsync(strs.startcmdlist_none);
}
else
{
var i = 0;
await SendConfirmAsync(text: string.Join("\n",
scmds.Select(x => $@"```css
#{++i + (page * 5)}
[{GetText(strs.server)}]: {(x.GuildId.HasValue ? $"{x.GuildName} #{x.GuildId}" : "-")}
[{GetText(strs.channel)}]: {x.ChannelName} #{x.ChannelId}
[{GetText(strs.command_text)}]: {x.CommandText}```")),
title: string.Empty,
footer: GetText(strs.page(page + 1)));
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async partial Task AutoCommandsList(int page = 1)
{
if (page-- < 1)
return;
var scmds = _service.GetAutoCommands().Skip(page * 5).Take(5).ToList();
if (!scmds.Any())
{
await ReplyErrorLocalizedAsync(strs.autocmdlist_none);
}
else
{
var i = 0;
await SendConfirmAsync(text: string.Join("\n",
scmds.Select(x => $@"```css
#{++i + (page * 5)}
[{GetText(strs.server)}]: {(x.GuildId.HasValue ? $"{x.GuildName} #{x.GuildId}" : "-")}
[{GetText(strs.channel)}]: {x.ChannelName} #{x.ChannelId}
{GetIntervalText(x.Interval)}
[{GetText(strs.command_text)}]: {x.CommandText}```")),
title: string.Empty,
footer: GetText(strs.page(page + 1)));
}
}
private string GetIntervalText(int interval)
=> $"[{GetText(strs.interval)}]: {interval}";
[Cmd]
[OwnerOnly]
public async partial Task Wait(int miliseconds)
{
if (miliseconds <= 0)
return;
ctx.Message.DeleteAfter(0);
try
{
var msg = await SendConfirmAsync($"⏲ {miliseconds}ms");
msg.DeleteAfter(miliseconds / 1000);
}
catch { }
await Task.Delay(miliseconds);
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async partial Task AutoCommandRemove([Leftover] int index)
{
if (!_service.RemoveAutoCommand(--index, out _))
{
await ReplyErrorLocalizedAsync(strs.acrm_fail);
return;
}
await ctx.OkAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async partial Task StartupCommandRemove([Leftover] int index)
{
if (!_service.RemoveStartupCommand(--index, out _))
await ReplyErrorLocalizedAsync(strs.scrm_fail);
else
await ReplyConfirmLocalizedAsync(strs.scrm);
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async partial Task StartupCommandsClear()
{
_service.ClearStartupCommands();
await ReplyConfirmLocalizedAsync(strs.startcmds_cleared);
}
[Cmd]
[OwnerOnly]
public async partial Task ForwardMessages()
{
var enabled = _service.ForwardMessages();
if (enabled)
await ReplyConfirmLocalizedAsync(strs.fwdm_start);
else
await ReplyPendingLocalizedAsync(strs.fwdm_stop);
}
[Cmd]
[OwnerOnly]
public async partial Task ForwardToAll()
{
var enabled = _service.ForwardToAll();
if (enabled)
await ReplyConfirmLocalizedAsync(strs.fwall_start);
else
await ReplyPendingLocalizedAsync(strs.fwall_stop);
}
[Cmd]
public async partial Task ShardStats(int page = 1)
{
if (--page < 0)
return;
var statuses = _coord.GetAllShardStatuses();
var status = string.Join(" : ",
statuses.Select(x => (ConnectionStateToEmoji(x), x))
.GroupBy(x => x.Item1)
.Select(x => $"`{x.Count()} {x.Key}`")
.ToArray());
var allShardStrings = statuses.Select(st =>
{
var stateStr = ConnectionStateToEmoji(st);
var timeDiff = DateTime.UtcNow - st.LastUpdate;
var maxGuildCountLength =
statuses.Max(x => x.GuildCount).ToString().Length;
return $"`{stateStr} "
+ $"| #{st.ShardId.ToString().PadBoth(3)} "
+ $"| {timeDiff:mm\\:ss} "
+ $"| {st.GuildCount.ToString().PadBoth(maxGuildCountLength)} `";
})
.ToArray();
await ctx.SendPaginatedConfirmAsync(page,
curPage =>
{
var str = string.Join("\n", allShardStrings.Skip(25 * curPage).Take(25));
if (string.IsNullOrWhiteSpace(str))
str = GetText(strs.no_shards_on_page);
return _eb.Create().WithOkColor().WithDescription($"{status}\n\n{str}");
},
allShardStrings.Length,
25);
}
private static string ConnectionStateToEmoji(ShardStatus status)
{
var timeDiff = DateTime.UtcNow - status.LastUpdate;
return status.ConnectionState switch
{
ConnectionState.Connected => "✅",
ConnectionState.Disconnected => "🔻",
_ when timeDiff > TimeSpan.FromSeconds(30) => " ❗ ",
_ => " ⏳"
};
}
[Cmd]
[OwnerOnly]
public async partial Task RestartShard(int shardId)
{
var success = _coord.RestartShard(shardId);
if (success)
await ReplyConfirmLocalizedAsync(strs.shard_reconnecting(Format.Bold("#" + shardId)));
else
await ReplyErrorLocalizedAsync(strs.no_shard_id);
}
[Cmd]
[OwnerOnly]
public partial Task Leave([Leftover] string guildStr)
=> _service.LeaveGuild(guildStr);
[Cmd]
[OwnerOnly]
public async partial Task Die(bool graceful = false)
{
try
{
await ReplyConfirmLocalizedAsync(strs.shutting_down);
}
catch
{
// ignored
}
await Task.Delay(2000);
_coord.Die(graceful);
}
[Cmd]
[OwnerOnly]
public async partial Task Restart()
{
var success = _coord.RestartBot();
if (!success)
{
await ReplyErrorLocalizedAsync(strs.restart_fail);
return;
}
try { await ReplyConfirmLocalizedAsync(strs.restarting); }
catch { }
}
[Cmd]
[OwnerOnly]
public async partial Task SetName([Leftover] string newName)
{
if (string.IsNullOrWhiteSpace(newName))
return;
try
{
await _client.CurrentUser.ModifyAsync(u => u.Username = newName);
}
catch (RateLimitedException)
{
Log.Warning("You've been ratelimited. Wait 2 hours to change your name");
}
await ReplyConfirmLocalizedAsync(strs.bot_name(Format.Bold(newName)));
}
[Cmd]
[UserPerm(GuildPerm.ManageNicknames)]
[BotPerm(GuildPerm.ChangeNickname)]
[Priority(0)]
public async partial Task SetNick([Leftover] string newNick = null)
{
if (string.IsNullOrWhiteSpace(newNick))
return;
var curUser = await ctx.Guild.GetCurrentUserAsync();
await curUser.ModifyAsync(u => u.Nickname = newNick);
await ReplyConfirmLocalizedAsync(strs.bot_nick(Format.Bold(newNick) ?? "-"));
}
[Cmd]
[BotPerm(GuildPerm.ManageNicknames)]
[UserPerm(GuildPerm.ManageNicknames)]
[Priority(1)]
public async partial Task SetNick(IGuildUser gu, [Leftover] string newNick = null)
{
var sg = (SocketGuild)ctx.Guild;
if (sg.OwnerId == gu.Id
|| gu.GetRoles().Max(r => r.Position) >= sg.CurrentUser.GetRoles().Max(r => r.Position))
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_i);
return;
}
await gu.ModifyAsync(u => u.Nickname = newNick);
await ReplyConfirmLocalizedAsync(strs.user_nick(Format.Bold(gu.ToString()), Format.Bold(newNick) ?? "-"));
}
[Cmd]
[OwnerOnly]
public async partial Task SetStatus([Leftover] SettableUserStatus status)
{
await _client.SetStatusAsync(SettableUserStatusToUserStatus(status));
await ReplyConfirmLocalizedAsync(strs.bot_status(Format.Bold(status.ToString())));
}
[Cmd]
[OwnerOnly]
public async partial Task SetAvatar([Leftover] string img = null)
{
var success = await _service.SetAvatar(img);
if (success) await ReplyConfirmLocalizedAsync(strs.set_avatar);
}
[Cmd]
[OwnerOnly]
public async partial Task SetGame(ActivityType type, [Leftover] string game = null)
{
var rep = new ReplacementBuilder().WithDefault(Context).Build();
await _service.SetGameAsync(game is null ? game : rep.Replace(game), type);
await ReplyConfirmLocalizedAsync(strs.set_game);
}
[Cmd]
[OwnerOnly]
public async partial Task SetStream(string url, [Leftover] string name = null)
{
name ??= "";
await _service.SetStreamAsync(name, url);
await ReplyConfirmLocalizedAsync(strs.set_stream);
}
[Cmd]
[OwnerOnly]
public async partial Task Send(string where, [Leftover] SmartText text = null)
{
var ids = where.Split('|');
if (ids.Length != 2)
return;
var sid = ulong.Parse(ids[0]);
var server = _client.Guilds.FirstOrDefault(s => s.Id == sid);
if (server is null)
return;
var rep = new ReplacementBuilder().WithDefault(Context).Build();
if (ids[1].ToUpperInvariant().StartsWith("C:", StringComparison.InvariantCulture))
{
var cid = ulong.Parse(ids[1][2..]);
var ch = server.TextChannels.FirstOrDefault(c => c.Id == cid);
if (ch is null)
return;
text = rep.Replace(text);
await ch.SendAsync(text);
}
else if (ids[1].ToUpperInvariant().StartsWith("U:", StringComparison.InvariantCulture))
{
var uid = ulong.Parse(ids[1][2..]);
var user = server.Users.FirstOrDefault(u => u.Id == uid);
if (user is null)
return;
var ch = await user.CreateDMChannelAsync();
text = rep.Replace(text);
await ch.SendAsync(text);
}
else
{
await ReplyErrorLocalizedAsync(strs.invalid_format);
return;
}
await ReplyConfirmLocalizedAsync(strs.message_sent);
}
[Cmd]
[OwnerOnly]
public async partial Task ImagesReload()
{
await _service.ReloadImagesAsync();
await ReplyConfirmLocalizedAsync(strs.images_loading);
}
[Cmd]
[OwnerOnly]
public async partial Task StringsReload()
{
_strings.Reload();
await ReplyConfirmLocalizedAsync(strs.bot_strings_reloaded);
}
[Cmd]
[OwnerOnly]
public async partial Task CoordReload()
{
await _coord.Reload();
await ctx.OkAsync();
}
private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)
{
switch (sus)
{
case SettableUserStatus.Online:
return UserStatus.Online;
case SettableUserStatus.Invisible:
return UserStatus.Invisible;
case SettableUserStatus.Idle:
return UserStatus.AFK;
case SettableUserStatus.Dnd:
return UserStatus.DoNotDisturb;
}
return UserStatus.Online;
}
}
}

View File

@@ -0,0 +1,351 @@
#nullable disable
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Services.Database.Models;
using System.Collections.Immutable;
namespace NadekoBot.Modules.Administration.Services;
public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
{
private readonly CommandHandler _cmdHandler;
private readonly DbService _db;
private readonly IBotStrings _strings;
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private ImmutableDictionary<ulong, IDMChannel> ownerChannels =
new Dictionary<ulong, IDMChannel>().ToImmutableDictionary();
private ConcurrentDictionary<ulong?, ConcurrentDictionary<int, Timer>> _autoCommands = new();
private readonly IImageCache _imgs;
private readonly IHttpClientFactory _httpFactory;
private readonly BotConfigService _bss;
private readonly IPubSub _pubSub;
private readonly IEmbedBuilderService _eb;
//keys
private readonly TypedKey<ActivityPubData> _activitySetKey;
private readonly TypedKey<bool> _imagesReloadKey;
private readonly TypedKey<string> _guildLeaveKey;
public SelfService(
DiscordSocketClient client,
CommandHandler cmdHandler,
DbService db,
IBotStrings strings,
IBotCredentials creds,
IDataCache cache,
IHttpClientFactory factory,
BotConfigService bss,
IPubSub pubSub,
IEmbedBuilderService eb)
{
_cmdHandler = cmdHandler;
_db = db;
_strings = strings;
_client = client;
_creds = creds;
_imgs = cache.LocalImages;
_httpFactory = factory;
_bss = bss;
_pubSub = pubSub;
_eb = eb;
_activitySetKey = new("activity.set");
_imagesReloadKey = new("images.reload");
_guildLeaveKey = new("guild.leave");
HandleStatusChanges();
if (_client.ShardId == 0) _pubSub.Sub(_imagesReloadKey, async _ => await _imgs.Reload());
_pubSub.Sub(_guildLeaveKey,
async input =>
{
var guildStr = input.ToString().Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(guildStr))
return;
var server = _client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr
|| g.Name.Trim().ToUpperInvariant() == guildStr);
if (server is null) return;
if (server.OwnerId != _client.CurrentUser.Id)
{
await server.LeaveAsync();
Log.Information($"Left server {server.Name} [{server.Id}]");
}
else
{
await server.DeleteAsync();
Log.Information($"Deleted server {server.Name} [{server.Id}]");
}
});
}
public async Task OnReadyAsync()
{
await using var uow = _db.GetDbContext();
_autoCommands = uow.AutoCommands.AsNoTracking()
.Where(x => x.Interval >= 5)
.AsEnumerable()
.GroupBy(x => x.GuildId)
.ToDictionary(x => x.Key,
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
.ToConcurrent();
var startupCommands = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
foreach (var cmd in startupCommands)
try
{
await ExecuteCommand(cmd);
}
catch
{
}
if (_client.ShardId == 0) await LoadOwnerChannels();
}
private Timer TimerFromAutoCommand(AutoCommand x)
=> new(async obj => await ExecuteCommand((AutoCommand)obj), x, x.Interval * 1000, x.Interval * 1000);
private async Task ExecuteCommand(AutoCommand cmd)
{
try
{
if (cmd.GuildId is null)
return;
var guildShard = (int)((cmd.GuildId.Value >> 22) % (ulong)_creds.TotalShards);
if (guildShard != _client.ShardId)
return;
var prefix = _cmdHandler.GetPrefix(cmd.GuildId);
//if someone already has .die as their startup command, ignore it
if (cmd.CommandText.StartsWith(prefix + "die", StringComparison.InvariantCulture))
return;
await _cmdHandler.ExecuteExternal(cmd.GuildId, cmd.ChannelId, cmd.CommandText);
}
catch (Exception ex)
{
Log.Warning(ex, "Error in SelfService ExecuteCommand");
}
}
public void AddNewAutoCommand(AutoCommand cmd)
{
using (var uow = _db.GetDbContext())
{
uow.AutoCommands.Add(cmd);
uow.SaveChanges();
}
if (cmd.Interval >= 5)
{
var autos = _autoCommands.GetOrAdd(cmd.GuildId, new ConcurrentDictionary<int, Timer>());
autos.AddOrUpdate(cmd.Id,
key => TimerFromAutoCommand(cmd),
(key, old) =>
{
old.Change(Timeout.Infinite, Timeout.Infinite);
return TimerFromAutoCommand(cmd);
});
}
}
public IEnumerable<AutoCommand> GetStartupCommands()
{
using var uow = _db.GetDbContext();
return uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0).OrderBy(x => x.Id).ToList();
}
public IEnumerable<AutoCommand> GetAutoCommands()
{
using var uow = _db.GetDbContext();
return uow.AutoCommands.AsNoTracking().Where(x => x.Interval >= 5).OrderBy(x => x.Id).ToList();
}
private async Task LoadOwnerChannels()
{
var channels = await Task.WhenAll(_creds.OwnerIds.Select(id =>
{
var user = _client.GetUser(id);
if (user is null)
return Task.FromResult<IDMChannel>(null);
return user.CreateDMChannelAsync();
}));
ownerChannels = channels.Where(x => x != null)
.ToDictionary(x => x.Recipient.Id, x => x)
.ToImmutableDictionary();
if (!ownerChannels.Any())
Log.Warning(
"No owner channels created! Make sure you've specified the correct OwnerId in the creds.yml file and invited the bot to a Discord server.");
else
Log.Information($"Created {ownerChannels.Count} out of {_creds.OwnerIds.Count} owner message channels.");
}
public Task LeaveGuild(string guildStr)
=> _pubSub.Pub(_guildLeaveKey, guildStr);
// forwards dms
public async Task LateExecute(IGuild guild, IUserMessage msg)
{
var bs = _bss.Data;
if (msg.Channel is IDMChannel && bs.ForwardMessages && ownerChannels.Any())
{
var title = _strings.GetText(strs.dm_from) + $" [{msg.Author}]({msg.Author.Id})";
var attachamentsTxt = _strings.GetText(strs.attachments);
var toSend = msg.Content;
if (msg.Attachments.Count > 0)
toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n"
+ string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl));
if (bs.ForwardToAllOwners)
{
var allOwnerChannels = ownerChannels.Values;
foreach (var ownerCh in allOwnerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id))
try
{
await ownerCh.SendConfirmAsync(_eb, title, toSend);
}
catch
{
Log.Warning("Can't contact owner with id {0}", ownerCh.Recipient.Id);
}
}
else
{
var firstOwnerChannel = ownerChannels.Values.First();
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
try
{
await firstOwnerChannel.SendConfirmAsync(_eb, title, toSend);
}
catch
{
// ignored
}
}
}
}
public bool RemoveStartupCommand(int index, out AutoCommand cmd)
{
using var uow = _db.GetDbContext();
cmd = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0).Skip(index).FirstOrDefault();
if (cmd != null)
{
uow.Remove(cmd);
uow.SaveChanges();
return true;
}
return false;
}
public bool RemoveAutoCommand(int index, out AutoCommand cmd)
{
using var uow = _db.GetDbContext();
cmd = uow.AutoCommands.AsNoTracking().Where(x => x.Interval >= 5).Skip(index).FirstOrDefault();
if (cmd != null)
{
uow.Remove(cmd);
if (_autoCommands.TryGetValue(cmd.GuildId, out var autos))
if (autos.TryRemove(cmd.Id, out var timer))
timer.Change(Timeout.Infinite, Timeout.Infinite);
uow.SaveChanges();
return true;
}
return false;
}
public async Task<bool> SetAvatar(string img)
{
if (string.IsNullOrWhiteSpace(img))
return false;
if (!Uri.IsWellFormedUriString(img, UriKind.Absolute))
return false;
var uri = new Uri(img);
using var http = _httpFactory.CreateClient();
using var sr = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
if (!sr.IsImage())
return false;
// i can't just do ReadAsStreamAsync because dicord.net's image poops itself
var imgData = await sr.Content.ReadAsByteArrayAsync();
await using var imgStream = imgData.ToStream();
await _client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream));
return true;
}
public void ClearStartupCommands()
{
using var uow = _db.GetDbContext();
var toRemove = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
uow.AutoCommands.RemoveRange(toRemove);
uow.SaveChanges();
}
public Task ReloadImagesAsync()
=> _pubSub.Pub(_imagesReloadKey, true);
public bool ForwardMessages()
{
var isForwarding = false;
_bss.ModifyConfig(config => { isForwarding = config.ForwardMessages = !config.ForwardMessages; });
return isForwarding;
}
public bool ForwardToAll()
{
var isToAll = false;
_bss.ModifyConfig(config => { isToAll = config.ForwardToAllOwners = !config.ForwardToAllOwners; });
return isToAll;
}
private void HandleStatusChanges()
=> _pubSub.Sub(_activitySetKey,
async data =>
{
try
{
await _client.SetGameAsync(data.Name, data.Link, data.Type);
}
catch (Exception ex)
{
Log.Warning(ex, "Error setting activity");
}
});
public Task SetGameAsync(string game, ActivityType type)
=> _pubSub.Pub(_activitySetKey, new() { Name = game, Link = null, Type = type });
public Task SetStreamAsync(string name, string link)
=> _pubSub.Pub(_activitySetKey, new() { Name = name, Link = link, Type = ActivityType.Streaming });
private sealed class ActivityPubData
{
public string Name { get; init; }
public string Link { get; init; }
public ActivityType Type { get; init; }
}
}