elliebot/src/EllieBot/Modules/Administration/Self/SelfCommands.cs
2024-08-06 22:52:02 +12:00

586 lines
No EOL
19 KiB
C#

#nullable disable
using Discord.Rest;
using EllieBot.Modules.Administration.Services;
using EllieBot.Db.Models;
using Ellie.Common.Marmalade;
namespace EllieBot.Modules.Administration;
public partial class Administration
{
[Group]
public partial class SelfCommands : EllieModule<SelfService>
{
public enum SettableUserStatus
{
Online,
Invisible,
Idle,
Dnd
}
private readonly DiscordSocketClient _client;
private readonly IBotStrings _strings;
private readonly IMarmaladeLoaderService _marmaladeLoader;
private readonly ICoordinator _coord;
private readonly DbService _db;
public SelfCommands(
DiscordSocketClient client,
DbService db,
IBotStrings strings,
ICoordinator coord,
IMarmaladeLoaderService marmaladeLoader)
{
_client = client;
_db = db;
_strings = strings;
_coord = coord;
_marmaladeLoader = marmaladeLoader;
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public Task CacheUsers()
=> CacheUsers(ctx.Guild);
[Cmd]
[OwnerOnly]
public async Task CacheUsers(IGuild guild)
{
var downloadUsersTask = guild.DownloadUsersAsync();
var message = await Response().Pending(strs.cache_users_pending).SendAsync();
await downloadUsersTask;
var users = (await guild.GetUsersAsync(CacheMode.CacheOnly))
.Cast<IUser>()
.ToList();
var (added, updated) = await _service.RefreshUsersAsync(users);
await message.ModifyAsync(x =>
x.Embed = _sender.CreateEmbed()
.WithDescription(GetText(strs.cache_users_done(added, updated)))
.WithOkColor()
.Build()
);
}
[Cmd]
[OwnerOnly]
public async Task DoAs(IUser user, [Leftover] string message)
{
if (ctx.User is not IGuildUser { GuildPermissions.Administrator: true })
return;
if (ctx.Guild is SocketGuild sg
&& ctx.Channel is ISocketMessageChannel ch
&& ctx.Message is SocketUserMessage msg)
{
var fakeMessage = new DoAsUserMessage(msg, user, message);
await _cmdHandler.TryRunCommand(sg, ch, fakeMessage);
}
else
{
await Response().Error(strs.error_occured).SendAsync();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async 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 Response()
.Embed(_sender.CreateEmbed()
.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))
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async 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 Response().Confirm(strs.autocmd_add(Format.Code(Format.Sanitize(cmdText)), cmd.Interval)).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommandsList(int page = 1)
{
if (page-- < 1)
return;
var scmds = _service.GetStartupCommands().Skip(page * 5).Take(5).ToList();
if (scmds.Count == 0)
await Response().Error(strs.startcmdlist_none).SendAsync();
else
{
var i = 0;
await Response()
.Confirm(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)))
.SendAsync();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task AutoCommandsList(int page = 1)
{
if (page-- < 1)
return;
var scmds = _service.GetAutoCommands().Skip(page * 5).Take(5).ToList();
if (!scmds.Any())
await Response().Error(strs.autocmdlist_none).SendAsync();
else
{
var i = 0;
await Response()
.Confirm(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)))
.SendAsync();
}
}
private string GetIntervalText(int interval)
=> $"[{GetText(strs.interval)}]: {interval}";
[Cmd]
[OwnerOnly]
public async Task Wait(int miliseconds)
{
if (miliseconds <= 0)
return;
ctx.Message.DeleteAfter(0);
try
{
var msg = await Response().Confirm($"⏲ {miliseconds}ms").SendAsync();
msg.DeleteAfter(miliseconds / 1000);
}
catch { }
await Task.Delay(miliseconds);
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task AutoCommandRemove([Leftover] int index)
{
if (!_service.RemoveAutoCommand(--index, out _))
{
await Response().Error(strs.acrm_fail).SendAsync();
return;
}
await ctx.OkAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task StartupCommandRemove([Leftover] int index)
{
if (!_service.RemoveStartupCommand(--index, out _))
await Response().Error(strs.scrm_fail).SendAsync();
else
await Response().Confirm(strs.scrm).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[OwnerOnly]
public async Task StartupCommandsClear()
{
_service.ClearStartupCommands();
await Response().Confirm(strs.startcmds_cleared).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task ForwardMessages()
{
var enabled = _service.ForwardMessages();
if (enabled)
await Response().Confirm(strs.fwdm_start).SendAsync();
else
await Response().Pending(strs.fwdm_stop).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task ForwardToAll()
{
var enabled = _service.ForwardToAll();
if (enabled)
await Response().Confirm(strs.fwall_start).SendAsync();
else
await Response().Pending(strs.fwall_stop).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task ForwardToChannel()
{
var enabled = _service.ForwardToChannel(ctx.Channel.Id);
if (enabled)
await Response().Confirm(strs.fwch_start).SendAsync();
else
await Response().Pending(strs.fwch_stop).SendAsync();
}
[Cmd]
public async 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 timeDiff = DateTime.UtcNow - st.LastUpdate;
var stateStr = ConnectionStateToEmoji(st);
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 Response()
.Paginated()
.Items(allShardStrings)
.PageSize(25)
.CurrentPage(page)
.Page((items, _) =>
{
var str = string.Join("\n", items);
if (string.IsNullOrWhiteSpace(str))
str = GetText(strs.no_shards_on_page);
return _sender.CreateEmbed().WithOkColor().WithDescription($"{status}\n\n{str}");
})
.SendAsync();
}
private static string ConnectionStateToEmoji(ShardStatus status)
{
var timeDiff = DateTime.UtcNow - status.LastUpdate;
return status.ConnectionState switch
{
ConnectionState.Disconnected => "🔻",
_ when timeDiff > TimeSpan.FromSeconds(30) => " ❗ ",
ConnectionState.Connected => "✅",
_ => " ⏳"
};
}
[Cmd]
[OwnerOnly]
public async Task RestartShard(int shardId)
{
var success = _coord.RestartShard(shardId);
if (success)
await Response().Confirm(strs.shard_reconnecting(Format.Bold("#" + shardId))).SendAsync();
else
await Response().Error(strs.no_shard_id).SendAsync();
}
[Cmd]
[OwnerOnly]
public Task Leave([Leftover] string guildStr)
=> _service.LeaveGuild(guildStr);
[Cmd]
[OwnerOnly]
public async Task DeleteEmptyServers()
{
await ctx.Channel.TriggerTypingAsync();
var toLeave = _client.Guilds
.Where(s => s.MemberCount == 1 && s.Users.Count == 1)
.ToList();
foreach (var server in toLeave)
{
try
{
await server.DeleteAsync();
Log.Information("Deleted server {ServerName} [{ServerId}]",
server.Name,
server.Id);
}
catch (Exception ex)
{
Log.Warning(ex,
"Error leaving server {ServerName} [{ServerId}]",
server.Name,
server.Id);
}
}
await Response().Confirm(strs.deleted_x_servers(toLeave.Count)).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task Die(bool graceful = false)
{
try
{
await _client.SetStatusAsync(UserStatus.Invisible);
_ = _client.StopAsync();
await Response().Confirm(strs.shutting_down).SendAsync();
}
catch
{
// ignored
}
await Task.Delay(2000);
_coord.Die(graceful);
}
[Cmd]
[OwnerOnly]
public async Task Restart()
{
var success = _coord.RestartBot();
if (!success)
{
await Response().Error(strs.restart_fail).SendAsync();
return;
}
try { await Response().Confirm(strs.restarting).SendAsync(); }
catch { }
}
[Cmd]
[OwnerOnly]
public async 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 Response().Confirm(strs.bot_name(Format.Bold(newName))).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task SetStatus([Leftover] SettableUserStatus status)
{
await _client.SetStatusAsync(SettableUserStatusToUserStatus(status));
await Response().Confirm(strs.bot_status(Format.Bold(status.ToString()))).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task SetAvatar([Leftover] string img = null)
{
var success = await _service.SetAvatar(img);
if (success)
await Response().Confirm(strs.set_avatar).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task SetBanner([Leftover] string img = null)
{
var success = await _service.SetBanner(img);
if (success)
await Response().Confirm(strs.set_banner).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task SetGame(ActivityType type, [Leftover] string game = null)
{
// var rep = new ReplacementBuilder().WithDefault(Context).Build();
var repCtx = new ReplacementContext(ctx);
await _service.SetGameAsync(game is null ? game : await repSvc.ReplaceAsync(game, repCtx), type);
await Response().Confirm(strs.set_game).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task SetStream(string url, [Leftover] string name = null)
{
name ??= "";
await _service.SetStreamAsync(name, url);
await Response().Confirm(strs.set_stream).SendAsync();
}
public enum SendWhere
{
User = 0,
U = 0,
Usr = 0,
Channel = 1,
Ch = 1,
Chan = 1,
}
[Cmd]
[OwnerOnly]
public async Task Send(SendWhere to, ulong id, [Leftover] SmartText text)
{
var ch = to switch
{
SendWhere.User => await ((await _client.Rest.GetUserAsync(id))?.CreateDMChannelAsync()
?? Task.FromResult<RestDMChannel>(null)),
SendWhere.Channel => await _client.Rest.GetChannelAsync(id) as IMessageChannel,
_ => null
};
if (ch is null)
{
await Response().Error(strs.invalid_format).SendAsync();
return;
}
var repCtx = new ReplacementContext(ctx);
text = await repSvc.ReplaceAsync(text, repCtx);
await Response().Channel(ch).Text(text).SendAsync();
await ctx.OkAsync();
}
[Cmd]
[OwnerOnly]
public async Task StringsReload()
{
_strings.Reload();
await _marmaladeLoader.ReloadStrings();
await Response().Confirm(strs.bot_strings_reloaded).SendAsync();
}
[Cmd]
[OwnerOnly]
public async 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;
}
}
}