* dev: Greet stuff moved to its own table in the database. GreetSettings

* fix: Fixed placeholders not working
* fix: Fixed some countries in countries.yml for hangman game
* add: Added custom status overload for \`.adpl\`
* dev: Removed some unused strings
* fix: Fixed postgres support in Nadeko
* remove: Removed mysql support, it was broken for a while and some queries weren't compiling.
* dev: Updated image library
* fix: Some command strings fixed and clarified
This commit is contained in:
Kwoth
2024-09-15 22:44:37 +00:00
parent 28ad6db2de
commit 021e7978da
86 changed files with 4899 additions and 82742 deletions

View File

@@ -46,7 +46,7 @@ public partial class Administration
[Cmd]
[OwnerOnly]
public async Task LeaveUnkeptServers(int startShardId)
public async Task LeaveUnkeptServers(int startShardId, int shardMultiplier = 3000)
{
var keptGuildCount = await _svc.GetKeptGuildCount();
@@ -65,7 +65,7 @@ public partial class Administration
for (var shardId = startShardId; shardId < _creds.GetCreds().TotalShards; shardId++)
{
await _svc.StartLeavingUnkeptServers(shardId);
await Task.Delay(3000 * 1000);
await Task.Delay(shardMultiplier * 1000);
}
await ctx.OkAsync();

View File

@@ -72,10 +72,10 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
dontDelete = dontDeleteList.ToHashSet();
}
Log.Information("Leaving {RemainingCount} guilds every {Delay} seconds, {DontDeleteCount} will remain",
Log.Information("Leaving {RemainingCount} guilds, 1 every second. {DontDeleteCount} will remain",
allGuildIds.Length - dontDelete.Count,
shardId,
dontDelete.Count);
foreach (var guildId in allGuildIds)
{
if (dontDelete.Contains(guildId))

View File

@@ -8,235 +8,218 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Boost()
{
var enabled = await _service.ToggleBoost(ctx.Guild.Id, ctx.Channel.Id);
if (enabled)
await Response().Confirm(strs.boost_on).SendAsync();
else
await Response().Pending(strs.boost_off).SendAsync();
}
public Task Boost()
=> Toggle(GreetType.Boost);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task BoostDel(int timer = 30)
public Task BoostDel(int timer = 30)
=> SetDel(GreetType.Boost, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task BoostMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Boost, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task Greet()
=> Toggle(GreetType.Greet);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDel(int timer = 30)
=> SetDel(GreetType.Greet, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Greet, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDm()
=> Toggle(GreetType.GreetDm);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDmMsg([Leftover] string? text = null)
=> SetMsg(GreetType.GreetDm, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task Bye()
=> Toggle(GreetType.Bye);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeDel(int timer = 30)
=> SetDel(GreetType.Bye, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Bye, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Greet, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDmTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.GreetDm, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public Task ByeTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Bye, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public Task BoostTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Boost, user);
public async Task Toggle(GreetType type)
{
var enabled = await _service.SetGreet(ctx.Guild.Id, ctx.Channel.Id, type);
if (enabled)
await Response()
.Confirm(
type switch
{
GreetType.Boost => strs.boost_on,
GreetType.Greet => strs.greet_on,
GreetType.Bye => strs.bye_on,
GreetType.GreetDm => strs.greetdm_on,
_ => strs.error
}
)
.SendAsync();
else
await Response()
.Pending(
type switch
{
GreetType.Boost => strs.boost_off,
GreetType.Greet => strs.greet_off,
GreetType.Bye => strs.bye_off,
GreetType.GreetDm => strs.greetdm_off,
_ => strs.error
}
)
.SendAsync();
}
public async Task SetDel(GreetType type, int timer)
{
if (timer is < 0 or > 600)
return;
await _service.SetBoostDel(ctx.Guild.Id, timer);
await _service.SetDeleteTimer(ctx.Guild.Id, type, timer);
if (timer > 0)
await Response().Confirm(strs.boostdel_on(timer)).SendAsync();
await Response()
.Confirm(
type switch
{
GreetType.Boost => strs.boostdel_on(timer),
GreetType.Greet => strs.greetdel_on(timer),
GreetType.Bye => strs.byedel_on(timer),
_ => strs.error
}
)
.SendAsync();
else
await Response().Pending(strs.boostdel_off).SendAsync();
await Response()
.Pending(
type switch
{
GreetType.Boost => strs.boostdel_off,
GreetType.Greet => strs.greetdel_off,
GreetType.Bye => strs.byedel_off,
_ => strs.error
})
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task BoostMsg([Leftover] string? text = null)
public async Task SetMsg(GreetType type, string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
{
var boostMessage = _service.GetBoostMessage(ctx.Guild.Id);
await Response().Confirm(strs.boostmsg_cur(boostMessage?.SanitizeMentions())).SendAsync();
var conf = await _service.GetGreetSettingsAsync(ctx.Guild.Id, type);
var msg = conf?.MessageText ?? GreetService.GetDefaultGreet(type);
await Response()
.Confirm(
type switch
{
GreetType.Boost => strs.boostmsg_cur(msg),
GreetType.Greet => strs.greetmsg_cur(msg),
GreetType.Bye => strs.byemsg_cur(msg),
GreetType.GreetDm => strs.greetdmmsg_cur(msg),
_ => strs.error
})
.SendAsync();
return;
}
var sendBoostEnabled = _service.SetBoostMessage(ctx.Guild.Id, ref text);
var isEnabled = await _service.SetMessage(ctx.Guild.Id, type, text);
await Response().Confirm(strs.boostmsg_new).SendAsync();
if (!sendBoostEnabled)
await Response().Pending(strs.boostmsg_enable($"`{prefix}boost`")).SendAsync();
}
await Response()
.Confirm(type switch
{
GreetType.Boost => strs.boostmsg_new,
GreetType.Greet => strs.greetmsg_new,
GreetType.Bye => strs.byemsg_new,
GreetType.GreetDm => strs.greetdmmsg_new,
_ => strs.error
})
.SendAsync();
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDel(int timer = 30)
{
if (timer is < 0 or > 600)
return;
await _service.SetGreetDel(ctx.Guild.Id, timer);
if (timer > 0)
await Response().Confirm(strs.greetdel_on(timer)).SendAsync();
else
await Response().Pending(strs.greetdel_off).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Greet()
{
var enabled = await _service.SetGreet(ctx.Guild.Id, ctx.Channel.Id);
if (enabled)
await Response().Confirm(strs.greet_on).SendAsync();
else
await Response().Pending(strs.greet_off).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetMsg([Leftover] string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
if (!isEnabled)
{
var greetMsg = _service.GetGreetMsg(ctx.Guild.Id);
await Response().Confirm(strs.greetmsg_cur(greetMsg?.SanitizeMentions())).SendAsync();
return;
var cmdName = type switch
{
GreetType.Greet => "greet",
GreetType.Bye => "bye",
GreetType.Boost => "boost",
GreetType.GreetDm => "greetdm",
_ => "unknown_command"
};
await Response().Pending(strs.boostmsg_enable($"`{prefix}{cmdName}`")).SendAsync();
}
var sendGreetEnabled = _service.SetGreetMessage(ctx.Guild.Id, ref text);
await Response().Confirm(strs.greetmsg_new).SendAsync();
if (!sendGreetEnabled)
await Response().Pending(strs.greetmsg_enable($"`{prefix}greet`")).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDm()
{
var enabled = await _service.SetGreetDm(ctx.Guild.Id);
if (enabled)
await Response().Confirm(strs.greetdm_on).SendAsync();
else
await Response().Confirm(strs.greetdm_off).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDmMsg([Leftover] string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
{
var dmGreetMsg = _service.GetDmGreetMsg(ctx.Guild.Id);
await Response().Confirm(strs.greetdmmsg_cur(dmGreetMsg?.SanitizeMentions())).SendAsync();
return;
}
var sendGreetEnabled = _service.SetGreetDmMessage(ctx.Guild.Id, ref text);
await Response().Confirm(strs.greetdmmsg_new).SendAsync();
if (!sendGreetEnabled)
await Response().Pending(strs.greetdmmsg_enable($"`{prefix}greetdm`")).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Bye()
{
var enabled = await _service.SetBye(ctx.Guild.Id, ctx.Channel.Id);
if (enabled)
await Response().Confirm(strs.bye_on).SendAsync();
else
await Response().Confirm(strs.bye_off).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task ByeMsg([Leftover] string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
{
var byeMsg = _service.GetByeMessage(ctx.Guild.Id);
await Response().Confirm(strs.byemsg_cur(byeMsg?.SanitizeMentions())).SendAsync();
return;
}
var sendByeEnabled = _service.SetByeMessage(ctx.Guild.Id, ref text);
await Response().Confirm(strs.byemsg_new).SendAsync();
if (!sendByeEnabled)
await Response().Pending(strs.byemsg_enable($"`{prefix}bye`")).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task ByeDel(int timer = 30)
{
await _service.SetByeDel(ctx.Guild.Id, timer);
if (timer > 0)
await Response().Confirm(strs.byedel_on(timer)).SendAsync();
else
await Response().Pending(strs.byedel_off).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task ByeTest([Leftover] IGuildUser? user = null)
public async Task Test(GreetType type, IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;
await _service.ByeTest((ITextChannel)ctx.Channel, user);
var enabled = _service.GetByeEnabled(ctx.Guild.Id);
if (!enabled)
await Response().Pending(strs.byemsg_enable($"`{prefix}bye`")).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task GreetTest([Leftover] IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;
await _service.GreetTest((ITextChannel)ctx.Channel, user);
var enabled = _service.GetGreetEnabled(ctx.Guild.Id);
if (!enabled)
await Response().Pending(strs.greetmsg_enable($"`{prefix}greet`")).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task GreetDmTest([Leftover] IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;
var success = await _service.GreetDmTest(user);
if (success)
await ctx.OkAsync();
else
await ctx.WarningAsync();
var enabled = _service.GetGreetDmEnabled(ctx.Guild.Id);
if (!enabled)
await Response().Pending(strs.greetdmmsg_enable($"`{prefix}greetdm`")).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async Task BoostTest([Leftover] IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;
await _service.BoostTest((ITextChannel)ctx.Channel, user);
var enabled = _service.GetBoostEnabled(ctx.Guild.Id);
if (!enabled)
await _service.Test(ctx.Guild.Id, type, (ITextChannel)ctx.Channel, user);
var conf = await _service.GetGreetSettingsAsync(ctx.Guild.Id, type);
if (conf?.IsEnabled is not true)
await Response().Pending(strs.boostmsg_enable($"`{prefix}boost`")).SendAsync();
}
}

View File

@@ -1,71 +0,0 @@
namespace NadekoBot.Services;
public class GreetGrouper<T>
{
private readonly Dictionary<ulong, HashSet<T>> _group;
private readonly object _locker = new();
public GreetGrouper()
=> _group = new();
/// <summary>
/// Creates a group, if group already exists, adds the specified user
/// </summary>
/// <param name="guildId">Id of the server for which to create group for</param>
/// <param name="toAddIfExists">User to add if group already exists</param>
/// <returns></returns>
public bool CreateOrAdd(ulong guildId, T toAddIfExists)
{
lock (_locker)
{
if (_group.TryGetValue(guildId, out var list))
{
list.Add(toAddIfExists);
return false;
}
_group[guildId] = new();
return true;
}
}
/// <summary>
/// Remove the specified amount of items from the group. If all items are removed, group will be removed.
/// </summary>
/// <param name="guildId">Id of the group</param>
/// <param name="count">Maximum number of items to retrieve</param>
/// <param name="items">Items retrieved</param>
/// <returns>Whether the group has no more items left and is deleted</returns>
public bool ClearGroup(ulong guildId, int count, out IReadOnlyCollection<T> items)
{
lock (_locker)
{
if (_group.TryGetValue(guildId, out var set))
{
// if we want more than there are, return everything
if (count >= set.Count)
{
items = set;
_group.Remove(guildId);
return true;
}
// if there are more in the group than what's needed
// take the requested number, remove them from the set
// and return them
var toReturn = set.TakeWhile(_ => count-- != 0).ToList();
foreach (var item in toReturn)
set.Remove(item);
items = toReturn;
// returning falsemeans group is not yet deleted
// because there are items left
return false;
}
items = Array.Empty<T>();
return true;
}
}
}

View File

@@ -1,58 +1,85 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using LinqToDB.Tools;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db.Models;
using System.Threading.Channels;
namespace NadekoBot.Services;
public class GreetService : INService, IReadyExecutor
{
public bool GroupGreets
=> _bss.Data.GroupGreets;
private readonly DbService _db;
private readonly ConcurrentDictionary<ulong, GreetSettings> _guildConfigsCache;
private ConcurrentDictionary<GreetType, ConcurrentHashSet<ulong>> _enabled = new();
private readonly DiscordSocketClient _client;
private readonly GreetGrouper<IGuildUser> _greets = new();
private readonly GreetGrouper<IUser> _byes = new();
private readonly BotConfigService _bss;
private readonly IReplacementService _repSvc;
private readonly IBotCache _cache;
private readonly IMessageSenderService _sender;
private readonly Channel<(GreetSettings, IUser, ITextChannel?)> _greetQueue =
Channel.CreateBounded<(GreetSettings, IUser, ITextChannel?)>(
new BoundedChannelOptions(60)
{
FullMode = BoundedChannelFullMode.DropOldest
});
public GreetService(
DiscordSocketClient client,
IBot bot,
DbService db,
BotConfigService bss,
IMessageSenderService sender,
IReplacementService repSvc)
IReplacementService repSvc,
IBotCache cache
)
{
_db = db;
_client = client;
_bss = bss;
_repSvc = repSvc;
_cache = cache;
_sender = sender;
_guildConfigsCache = new(bot.AllGuildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
_client.UserJoined += OnUserJoined;
_client.UserLeft += OnUserLeft;
bot.JoinedGuild += OnBotJoinedGuild;
_client.LeftGuild += OnClientLeftGuild;
_client.GuildMemberUpdated += ClientOnGuildMemberUpdated;
foreach (var type in Enum.GetValues<GreetType>())
{
_enabled[type] = new();
}
}
public async Task OnReadyAsync()
{
while (true)
// cache all enabled guilds
await using (var uow = _db.GetDbContext())
{
var (conf, user, compl) = await _greetDmQueue.Reader.ReadAsync();
var res = await GreetDmUserInternal(conf, user);
compl.TrySetResult(res);
await Task.Delay(2000);
var guilds = _client.Guilds.Select(x => x.Id).ToList();
var enabled = await uow.GetTable<GreetSettings>()
.Where(x => x.GuildId.In(guilds))
.Where(x => x.IsEnabled)
.Select(x => new
{
x.GuildId,
x.GreetType
})
.ToListAsync();
foreach (var e in enabled)
{
_enabled[e.GreetType].Add(e.GuildId);
}
}
_client.UserJoined += OnUserJoined;
_client.UserLeft += OnUserLeft;
_client.LeftGuild += OnClientLeftGuild;
_client.GuildMemberUpdated += ClientOnGuildMemberUpdated;
var timer = new PeriodicTimer(TimeSpan.FromSeconds(2));
while (await timer.WaitForNextTickAsync())
{
var (conf, user, ch) = await _greetQueue.Reader.ReadAsync();
await GreetUsers(conf, ch, user);
}
}
@@ -65,61 +92,38 @@ public class GreetService : INService, IReadyExecutor
&& newUser.PremiumSince is { } newDate
&& newDate > oldDate))
{
var conf = GetOrAddSettingsForGuild(newUser.Guild.Id);
if (!conf.SendBoostMessage)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
var conf = await GetGreetSettingsAsync(newUser.Guild.Id, GreetType.Boost);
_ = Task.Run(TriggerBoostMessage(conf, newUser));
if (conf is null || !conf.IsEnabled)
return;
ITextChannel? channel = null;
if (conf.ChannelId is { } cid)
channel = newUser.Guild.GetTextChannel(cid);
if (channel is null)
return;
await GreetUsers(conf, channel, newUser);
});
}
return Task.CompletedTask;
}
private Func<Task> TriggerBoostMessage(GreetSettings conf, SocketGuildUser user)
=> async () =>
{
var channel = user.Guild.GetTextChannel(conf.BoostMessageChannelId);
if (channel is null)
return;
await SendBoostMessage(conf, user, channel);
};
private async Task<bool> SendBoostMessage(GreetSettings conf, IGuildUser user, ITextChannel channel)
private async Task OnClientLeftGuild(SocketGuild guild)
{
if (string.IsNullOrWhiteSpace(conf.BoostMessage))
return false;
var toSend = SmartText.CreateFrom(conf.BoostMessage);
try
foreach (var gt in Enum.GetValues<GreetType>())
{
var newContent = await _repSvc.ReplaceAsync(toSend,
new(client: _client, guild: user.Guild, channel: channel, users: user));
var toDelete = await _sender.Response(channel).Text(newContent).Sanitize(false).SendAsync();
if (conf.BoostMessageDeleteAfter > 0)
toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Error sending boost message");
_enabled[gt].TryRemove(guild.Id);
}
return false;
}
private Task OnClientLeftGuild(SocketGuild arg)
{
_guildConfigsCache.TryRemove(arg.Id, out _);
return Task.CompletedTask;
}
private Task OnBotJoinedGuild(GuildConfig gc)
{
_guildConfigsCache[gc.GuildId] = GreetSettings.Create(gc);
return Task.CompletedTask;
await using var uow = _db.GetDbContext();
await uow.GetTable<GreetSettings>()
.Where(x => x.GuildId == guild.Id)
.DeleteAsync();
}
private Task OnUserLeft(SocketGuild guild, SocketUser user)
@@ -128,35 +132,20 @@ public class GreetService : INService, IReadyExecutor
{
try
{
var conf = GetOrAddSettingsForGuild(guild.Id);
var conf = await GetGreetSettingsAsync(guild.Id, GreetType.Bye);
if (!conf.SendChannelByeMessage)
if (conf is null)
return;
var channel = guild.TextChannels.FirstOrDefault(c => c.Id == conf.ByeMessageChannelId);
var channel = guild.TextChannels.FirstOrDefault(c => c.Id == conf.ChannelId);
if (channel is null) //maybe warn the server owner that the channel is missing
return;
if (GroupGreets)
{
// if group is newly created, greet that user right away,
// but any user which joins in the next 5 seconds will
// be greeted in a group greet
if (_byes.CreateOrAdd(guild.Id, user))
{
// greet single user
await ByeUsers(conf, channel, new[] { user });
var groupClear = false;
while (!groupClear)
{
await Task.Delay(5000);
groupClear = _byes.ClearGroup(guild.Id, 5, out var toBye);
await ByeUsers(conf, channel, toBye);
}
}
await SetGreet(guild.Id, null, GreetType.Bye, false);
return;
}
else
await ByeUsers(conf, channel, new[] { user });
await _greetQueue.Writer.WriteAsync((conf, user, channel));
}
catch
{
@@ -166,98 +155,61 @@ public class GreetService : INService, IReadyExecutor
return Task.CompletedTask;
}
public string? GetDmGreetMsg(ulong id)
private readonly TypedKey<GreetSettings?> _greetSettingsKey = new("greet_settings");
public async Task<GreetSettings?> GetGreetSettingsAsync(ulong gid, GreetType type)
=> await _cache.GetOrAddAsync<GreetSettings?>(_greetSettingsKey,
() => InternalGetGreetSettingsAsync(gid, type),
TimeSpan.FromSeconds(3));
private async Task<GreetSettings?> InternalGetGreetSettingsAsync(ulong gid, GreetType type)
{
using var uow = _db.GetDbContext();
return uow.GuildConfigsForId(id, set => set).DmGreetMessageText;
await using var uow = _db.GetDbContext();
var res = await uow.GetTable<GreetSettings>()
.Where(x => x.GuildId == gid && x.GreetType == type)
.FirstOrDefaultAsync();
if (res is not null)
res.MessageText ??= GetDefaultGreet(type);
return res;
}
public string? GetGreetMsg(ulong gid)
private async Task GreetUsers(GreetSettings conf, ITextChannel? channel, IUser user)
{
using var uow = _db.GetDbContext();
return uow.GuildConfigsForId(gid, set => set).ChannelGreetMessageText;
}
if (conf.GreetType == GreetType.GreetDm)
{
if (user is not IGuildUser gu)
return;
public string? GetBoostMessage(ulong gid)
{
using var uow = _db.GetDbContext();
return uow.GuildConfigsForId(gid, set => set).BoostMessage;
}
await GreetDmUserInternal(conf, gu);
return;
}
public GreetSettings GetGreetSettings(ulong gid)
{
if (_guildConfigsCache.TryGetValue(gid, out var gs))
return gs;
using var uow = _db.GetDbContext();
return GreetSettings.Create(uow.GuildConfigsForId(gid, set => set));
}
private Task ByeUsers(GreetSettings conf, ITextChannel channel, IUser user)
=> ByeUsers(conf, channel, new[] { user });
private async Task ByeUsers(GreetSettings conf, ITextChannel channel, IReadOnlyCollection<IUser> users)
{
if (!users.Any())
if (channel is null)
return;
var repCtx = new ReplacementContext(client: _client,
guild: channel.Guild,
channel: channel,
users: users.ToArray());
user: user);
var text = SmartText.CreateFrom(conf.ChannelByeMessageText);
var text = SmartText.CreateFrom(conf.MessageText);
text = await _repSvc.ReplaceAsync(text, repCtx);
try
{
var toDelete = await _sender.Response(channel).Text(text).Sanitize(false).SendAsync();
if (conf.AutoDeleteByeMessagesTimer > 0)
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
if (conf.AutoDeleteTimer > 0)
toDelete.DeleteAfter(conf.AutoDeleteTimer);
}
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
|| ex.DiscordCode == DiscordErrorCode.MissingPermissions
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel)
{
Log.Warning(ex,
"Missing permissions to send a bye message, the bye message will be disabled on server: {GuildId}",
channel.GuildId);
await SetBye(channel.GuildId, channel.Id, false);
}
catch (Exception ex)
{
Log.Warning(ex, "Error embeding bye message");
}
}
private Task GreetUsers(GreetSettings conf, ITextChannel channel, IGuildUser user)
=> GreetUsers(conf, channel, new[] { user });
private async Task GreetUsers(GreetSettings conf, ITextChannel channel, IReadOnlyCollection<IGuildUser> users)
{
if (users.Count == 0)
return;
var repCtx = new ReplacementContext(client: _client,
guild: channel.Guild,
channel: channel,
users: users.ToArray());
var text = SmartText.CreateFrom(conf.ChannelGreetMessageText);
text = await _repSvc.ReplaceAsync(text, repCtx);
try
{
var toDelete = await _sender.Response(channel).Text(text).Sanitize(false).SendAsync();
if (conf.AutoDeleteGreetMessagesTimer > 0)
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
}
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
|| ex.DiscordCode == DiscordErrorCode.MissingPermissions
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel)
catch (HttpException ex) when (ex.DiscordCode is DiscordErrorCode.InsufficientPermissions
or DiscordErrorCode.MissingPermissions
or DiscordErrorCode.UnknownChannel)
{
Log.Warning(ex,
"Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}",
channel.GuildId);
await SetGreet(channel.GuildId, channel.Id, false);
await SetGreet(channel.GuildId, channel.Id, GreetType.Greet, false);
}
catch (Exception ex)
{
@@ -265,19 +217,11 @@ public class GreetService : INService, IReadyExecutor
}
}
private readonly Channel<(GreetSettings, IGuildUser, TaskCompletionSource<bool>)> _greetDmQueue =
Channel.CreateBounded<(GreetSettings, IGuildUser, TaskCompletionSource<bool>)>(new BoundedChannelOptions(60)
{
// The limit of 60 users should be only hit when there's a raid. In that case
// probably the best thing to do is to drop newest (raiding) users
FullMode = BoundedChannelFullMode.DropNewest
});
private async Task<bool> GreetDmUser(GreetSettings conf, IGuildUser user)
{
var completionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
await _greetDmQueue.Writer.WriteAsync((conf, user, completionSource));
await _greetQueue.Writer.WriteAsync((conf, user, null));
return await completionSource.Task;
}
@@ -285,13 +229,8 @@ public class GreetService : INService, IReadyExecutor
{
try
{
// var rep = new ReplacementBuilder()
// .WithUser(user)
// .WithServer(_client, (SocketGuild)user.Guild)
// .Build();
var repCtx = new ReplacementContext(client: _client, guild: user.Guild, users: user);
var smartText = SmartText.CreateFrom(conf.DmGreetMessageText);
var repCtx = new ReplacementContext(client: _client, guild: user.Guild, user: user);
var smartText = SmartText.CreateFrom(conf.MessageText);
smartText = await _repSvc.ReplaceAsync(smartText, repCtx);
if (smartText is SmartPlainText pt)
@@ -372,38 +311,21 @@ public class GreetService : INService, IReadyExecutor
{
try
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
var conf = await GetGreetSettingsAsync(user.GuildId, GreetType.Greet);
if (conf.SendChannelGreetMessage)
if (conf is not null && conf.IsEnabled && conf.ChannelId is { } channelId)
{
var channel = await user.Guild.GetTextChannelAsync(conf.GreetMessageChannelId);
var channel = await user.Guild.GetTextChannelAsync(channelId);
if (channel is not null)
{
if (GroupGreets)
{
// if group is newly created, greet that user right away,
// but any user which joins in the next 5 seconds will
// be greeted in a group greet
if (_greets.CreateOrAdd(user.GuildId, user))
{
// greet single user
await GreetUsers(conf, channel, new[] { user });
var groupClear = false;
while (!groupClear)
{
await Task.Delay(5000);
groupClear = _greets.ClearGroup(user.GuildId, 5, out var toGreet);
await GreetUsers(conf, channel, toGreet);
}
}
}
else
await GreetUsers(conf, channel, new[] { user });
await _greetQueue.Writer.WriteAsync((conf, user, channel));
}
}
if (conf.SendDmGreetMessage)
await GreetDmUser(conf, user);
var confDm = await GetGreetSettingsAsync(user.GuildId, GreetType.GreetDm);
if (confDm?.IsEnabled ?? false)
await GreetDmUser(confDm, user);
}
catch
{
@@ -413,256 +335,146 @@ public class GreetService : INService, IReadyExecutor
return Task.CompletedTask;
}
public string? GetByeMessage(ulong gid)
{
using var uow = _db.GetDbContext();
return uow.GuildConfigsForId(gid, set => set).ChannelByeMessageText;
}
public GreetSettings GetOrAddSettingsForGuild(ulong guildId)
{
if (_guildConfigsCache.TryGetValue(guildId, out var settings))
return settings;
using (var uow = _db.GetDbContext())
public static string GetDefaultGreet(GreetType greetType)
=> greetType switch
{
var gc = uow.GuildConfigsForId(guildId, set => set);
settings = GreetSettings.Create(gc);
GreetType.Boost => "%user.mention% has boosted the server!",
GreetType.Greet => "%user.mention% has joined the server!",
GreetType.Bye => "%user.name% has left the server!",
GreetType.GreetDm => "Welcome to the server %user.name%",
_ => "%user.name% did something new!"
};
public async Task<bool> SetGreet(
ulong guildId,
ulong? channelId,
GreetType greetType,
bool? value = null)
{
await using var uow = _db.GetDbContext();
var q = uow.GetTable<GreetSettings>();
if(value is null)
value = !_enabled[greetType].Contains(guildId);
if (value is { } v)
{
await q
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
GreetType = greetType,
IsEnabled = v,
ChannelId = channelId,
},
(old) => new()
{
IsEnabled = v,
ChannelId = channelId,
},
() => new()
{
GuildId = guildId,
GreetType = greetType,
});
}
_guildConfigsCache.TryAdd(guildId, settings);
return settings;
if (value is true)
{
_enabled[greetType].Add(guildId);
return true;
}
_enabled[greetType].TryRemove(guildId);
return false;
}
public async Task<bool> SetGreet(ulong guildId, ulong channelId, bool? value = null)
public async Task<bool> SetMessage(ulong guildId, GreetType greetType, string? message)
{
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
var enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage;
conf.GreetMessageChannelId = channelId;
await using (var uow = _db.GetDbContext())
{
await uow.GetTable<GreetSettings>()
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
GreetType = greetType,
MessageText = message
},
x => new()
{
MessageText = message
},
() => new()
{
GuildId = guildId,
GreetType = greetType
});
}
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
var conf = await GetGreetSettingsAsync(guildId, greetType);
await uow.SaveChangesAsync();
return enabled;
return conf?.IsEnabled ?? false;
}
public bool SetGreetMessage(ulong guildId, ref string message)
public async Task<bool> SetDeleteTimer(ulong guildId, GreetType greetType, int timer)
{
message = message.SanitizeMentions();
if (string.IsNullOrWhiteSpace(message))
throw new ArgumentNullException(nameof(message));
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.ChannelGreetMessageText = message;
var greetMsgEnabled = conf.SendChannelGreetMessage;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache.AddOrUpdate(guildId, toAdd, (_, _) => toAdd);
uow.SaveChanges();
return greetMsgEnabled;
}
public async Task<bool> SetGreetDm(ulong guildId, bool? value = null)
{
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
var enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
return enabled;
}
public bool SetGreetDmMessage(ulong guildId, ref string? message)
{
message = message?.SanitizeMentions();
if (string.IsNullOrWhiteSpace(message))
throw new ArgumentNullException(nameof(message));
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.DmGreetMessageText = message;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
uow.SaveChanges();
return conf.SendDmGreetMessage;
}
public async Task<bool> SetBye(ulong guildId, ulong channelId, bool? value = null)
{
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
var enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage;
conf.ByeMessageChannelId = channelId;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
return enabled;
}
public bool SetByeMessage(ulong guildId, ref string? message)
{
message = message?.SanitizeMentions();
if (string.IsNullOrWhiteSpace(message))
throw new ArgumentNullException(nameof(message));
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.ChannelByeMessageText = message;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
uow.SaveChanges();
return conf.SendChannelByeMessage;
}
public async Task SetByeDel(ulong guildId, int timer)
{
if (timer is < 0 or > 600)
return;
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.AutoDeleteByeMessagesTimer = timer;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
}
public async Task SetGreetDel(ulong guildId, int timer)
{
if (timer is < 0 or > 600)
return;
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.AutoDeleteGreetMessagesTimer = timer;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
}
public bool SetBoostMessage(ulong guildId, ref string message)
{
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.BoostMessage = message;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
uow.SaveChanges();
return conf.SendBoostMessage;
}
public async Task SetBoostDel(ulong guildId, int timer)
{
if (timer is < 0 or > 600)
if (timer < 0 || timer > 3600)
throw new ArgumentOutOfRangeException(nameof(timer));
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.BoostMessageDeleteAfter = timer;
await using (var uow = _db.GetDbContext())
{
await uow.GetTable<GreetSettings>()
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
GreetType = greetType,
AutoDeleteTimer = timer,
},
x => new()
{
AutoDeleteTimer = timer
},
() => new()
{
GuildId = guildId,
GreetType = greetType
});
}
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
var conf = await GetGreetSettingsAsync(guildId, greetType);
await uow.SaveChangesAsync();
return conf?.IsEnabled ?? false;
}
public async Task<bool> ToggleBoost(ulong guildId, ulong channelId, bool? forceState = null)
public async Task<bool> Test(
ulong guildId,
GreetType type,
IMessageChannel channel,
IGuildUser user)
{
await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
var conf = await GetGreetSettingsAsync(guildId, type);
if (conf is null)
return false;
if (forceState is not bool fs)
conf.SendBoostMessage = !conf.SendBoostMessage;
else
conf.SendBoostMessage = fs;
conf.BoostMessageChannelId = channelId;
await uow.SaveChangesAsync();
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
return conf.SendBoostMessage;
await SendMessage(conf, channel, user);
return true;
}
#region Get Enabled Status
public bool GetGreetDmEnabled(ulong guildId)
public async Task<bool> SendMessage(GreetSettings conf, IMessageChannel channel, IGuildUser user)
{
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
return conf.SendDmGreetMessage;
if (conf.GreetType == GreetType.GreetDm)
{
await _greetQueue.Writer.WriteAsync((conf, user, channel as ITextChannel));
return await GreetDmUser(conf, user);
}
if (channel is not ITextChannel ch)
return false;
await GreetUsers(conf, ch, user);
return true;
}
public bool GetGreetEnabled(ulong guildId)
{
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
return conf.SendChannelGreetMessage;
}
public bool GetByeEnabled(ulong guildId)
{
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
return conf.SendChannelByeMessage;
}
public bool GetBoostEnabled(ulong guildId)
{
using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set);
return conf.SendBoostMessage;
}
#endregion
#region Test Messages
public Task ByeTest(ITextChannel channel, IGuildUser user)
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
return ByeUsers(conf, channel, user);
}
public Task GreetTest(ITextChannel channel, IGuildUser user)
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
return GreetUsers(conf, channel, user);
}
public Task<bool> GreetDmTest(IGuildUser user)
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
return GreetDmUser(conf, user);
}
public Task<bool> BoostTest(ITextChannel channel, IGuildUser user)
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
return SendBoostMessage(conf, user, channel);
}
#endregion
}

View File

@@ -1,45 +1,21 @@
using NadekoBot.Db.Models;
namespace NadekoBot.Services;
public enum GreetType
{
Greet,
GreetDm,
Bye,
Boost,
}
public class GreetSettings
{
public int AutoDeleteGreetMessagesTimer { get; set; }
public int AutoDeleteByeMessagesTimer { get; set; }
public ulong GreetMessageChannelId { get; set; }
public ulong ByeMessageChannelId { get; set; }
public bool SendDmGreetMessage { get; set; }
public string? DmGreetMessageText { get; set; }
public bool SendChannelGreetMessage { get; set; }
public string? ChannelGreetMessageText { get; set; }
public bool SendChannelByeMessage { get; set; }
public string? ChannelByeMessageText { get; set; }
public bool SendBoostMessage { get; set; }
public string? BoostMessage { get; set; }
public int BoostMessageDeleteAfter { get; set; }
public ulong BoostMessageChannelId { get; set; }
public static GreetSettings Create(GuildConfig g)
=> new()
{
AutoDeleteByeMessagesTimer = g.AutoDeleteByeMessagesTimer,
AutoDeleteGreetMessagesTimer = g.AutoDeleteGreetMessagesTimer,
GreetMessageChannelId = g.GreetMessageChannelId,
ByeMessageChannelId = g.ByeMessageChannelId,
SendDmGreetMessage = g.SendDmGreetMessage,
DmGreetMessageText = g.DmGreetMessageText,
SendChannelGreetMessage = g.SendChannelGreetMessage,
ChannelGreetMessageText = g.ChannelGreetMessageText,
SendChannelByeMessage = g.SendChannelByeMessage,
ChannelByeMessageText = g.ChannelByeMessageText,
SendBoostMessage = g.SendBoostMessage,
BoostMessage = g.BoostMessage,
BoostMessageDeleteAfter = g.BoostMessageDeleteAfter,
BoostMessageChannelId = g.BoostMessageChannelId
};
public int Id { get; set; }
public ulong GuildId { get; set; }
public GreetType GreetType { get; set; }
public string? MessageText { get; set; }
public bool IsEnabled { get; set; }
public ulong? ChannelId { get; set; }
public int AutoDeleteTimer { get; set; }
}

View File

@@ -18,11 +18,17 @@ public partial class Administration
await Response().Confirm(strs.ropl_disabled).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task AddPlaying(ActivityType t, [Leftover] string status)
public Task AddPlaying([Leftover] string status)
=> AddPlaying(ActivityType.CustomStatus, status);
[Cmd]
[OwnerOnly]
public async Task AddPlaying(ActivityType statusType, [Leftover] string status)
{
await _service.AddPlaying(t, status);
await _service.AddPlaying(statusType, status);
await Response().Confirm(strs.ropl_added).SendAsync();
}

View File

@@ -243,43 +243,54 @@ public class UserPunishService : INService, IReadyExecutor
public async Task CheckAllWarnExpiresAsync()
{
await using var uow = _db.GetDbContext();
var cleared = await uow.Set<Warning>()
.Where(x => uow.Set<GuildConfig>()
.Any(y => y.GuildId == x.GuildId
&& y.WarnExpireHours > 0
&& y.WarnExpireAction == WarnExpireAction.Clear)
var toClear = await uow.GetTable<Warning>()
.Where(x => uow.GetTable<GuildConfig>()
.Count(y => y.GuildId == x.GuildId
&& y.WarnExpireHours > 0
&& y.WarnExpireAction == WarnExpireAction.Clear)
> 0
&& x.Forgiven == false
&& x.DateAdded
< DateTime.UtcNow.AddHours(-uow.Set<GuildConfig>()
< DateTime.UtcNow.AddHours(-uow.GetTable<GuildConfig>()
.Where(y => x.GuildId == y.GuildId)
.Select(y => y.WarnExpireHours)
.First()))
.UpdateAsync(_ => new()
{
Forgiven = true,
ForgivenBy = "expiry"
});
.Select(x => x.Id)
.ToListAsyncLinqToDB();
var deleted = await uow.Set<Warning>()
.Where(x => uow.Set<GuildConfig>()
.Any(y => y.GuildId == x.GuildId
&& y.WarnExpireHours > 0
&& y.WarnExpireAction == WarnExpireAction.Delete)
&& x.DateAdded
< DateTime.UtcNow.AddHours(-uow.Set<GuildConfig>()
.Where(y => x.GuildId == y.GuildId)
.Select(y => y.WarnExpireHours)
.First()))
.DeleteAsync();
var cleared = await uow.GetTable<Warning>()
.Where(x => toClear.Contains(x.Id))
.UpdateAsync(_ => new()
{
Forgiven = true,
ForgivenBy = "expiry"
});
var toDelete = await uow.GetTable<Warning>()
.Where(x => uow.GetTable<GuildConfig>()
.Count(y => y.GuildId == x.GuildId
&& y.WarnExpireHours > 0
&& y.WarnExpireAction == WarnExpireAction.Delete)
> 0
&& x.DateAdded
< DateTime.UtcNow.AddHours(-uow.GetTable<GuildConfig>()
.Where(y => x.GuildId == y.GuildId)
.Select(y => y.WarnExpireHours)
.First()))
.Select(x => x.Id)
.ToListAsyncLinqToDB();
var deleted = await uow.GetTable<Warning>()
.Where(x => toDelete.Contains(x.Id))
.DeleteAsync();
if (cleared > 0 || deleted > 0)
{
Log.Information("Cleared {ClearedWarnings} warnings and deleted {DeletedWarnings} warnings due to expiry",
cleared,
deleted);
toDelete.Count);
}
await uow.SaveChangesAsync();
}
public async Task CheckWarnExpiresAsync(ulong guildId)