Compare commits

...

3 commits

31 changed files with 19070 additions and 8310 deletions

View file

@ -1,76 +0,0 @@
using System.Linq;
using System.Threading.Tasks;
using Ellie.Common;
using EllieBot.Services;
using NUnit.Framework;
namespace EllieBot.Tests
{
public class GroupGreetTests
{
private GreetGrouper<int> _grouper;
[SetUp]
public void Setup()
=> _grouper = new GreetGrouper<int>();
[Test]
public void CreateTest()
{
var created = _grouper.CreateOrAdd(0, 5);
Assert.True(created);
}
[Test]
public void CreateClearTest()
{
_grouper.CreateOrAdd(0, 5);
_grouper.ClearGroup(0, 5, out var items);
Assert.AreEqual(0, items.Count());
}
[Test]
public void NotCreatedTest()
{
_grouper.CreateOrAdd(0, 5);
var created = _grouper.CreateOrAdd(0, 4);
Assert.False(created);
}
[Test]
public void ClearAddedTest()
{
_grouper.CreateOrAdd(0, 5);
_grouper.CreateOrAdd(0, 4);
_grouper.ClearGroup(0, 5, out var items);
var list = items.ToList();
Assert.AreEqual(1, list.Count, $"Count was {list.Count}");
Assert.AreEqual(4, list[0]);
}
[Test]
public async Task ClearManyTest()
{
_grouper.CreateOrAdd(0, 5);
// add 15 items
await Enumerable.Range(10, 15)
.Select(x => Task.Run(() => _grouper.CreateOrAdd(0, x))).WhenAll();
// get 5 at most
_grouper.ClearGroup(0, 5, out var items);
var list = items.ToList();
Assert.AreEqual(5, list.Count, $"Count was {list.Count}");
// try to get 15, but there should be 10 left
_grouper.ClearGroup(0, 15, out items);
list = items.ToList();
Assert.AreEqual(10, list.Count, $"Count was {list.Count}");
}
}
}

View file

@ -1,5 +1,6 @@
#nullable disable #nullable disable
using DryIoc; using DryIoc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using EllieBot.Common.Configs; using EllieBot.Common.Configs;
@ -89,15 +90,19 @@ public sealed class Bot : IBot
public IReadOnlyList<ulong> GetCurrentGuildIds() public IReadOnlyList<ulong> GetCurrentGuildIds()
=> Client.Guilds.Select(x => x.Id).ToList().AsReadOnly(); => Client.Guilds.Select(x => x.Id).ToList().AsReadOnly();
=> Client.Guilds.Select(x => x.Id).ToList().AsReadOnly();
private async Task AddServices()
private async Task AddServices() private async Task AddServices()
{ {
var startingGuildIdList = GetCurrentGuildIds(); var startingGuildIdList = GetCurrentGuildIds().ToList();
var startTime = Stopwatch.GetTimestamp(); var startTime = Stopwatch.GetTimestamp();
var bot = Client.CurrentUser; var bot = Client.CurrentUser;
await using (var uow = _db.GetDbContext())
await using (var uow = _db.GetDbContext()) await using (var uow = _db.GetDbContext())
{ {
AllGuildConfigs = await uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList);
uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId); uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId);
AllGuildConfigs = await uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList); AllGuildConfigs = await uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList);
} }
@ -161,7 +166,8 @@ public sealed class Bot : IBot
LoadTypeReaders(a); LoadTypeReaders(a);
} }
Log.Information("All services loaded in {ServiceLoadTime:F2}s", Stopwatch.GetElapsedTime(startTime).TotalSeconds); Log.Information("All services loaded in {ServiceLoadTime:F2}s",
Stopwatch.GetElapsedTime(startTime).TotalSeconds);
} }
private void LoadTypeReaders(Assembly assembly) private void LoadTypeReaders(Assembly assembly)
@ -266,6 +272,7 @@ public sealed class Bot : IBot
try try
{ {
await AddServices(); await AddServices();
await AddServices();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -273,7 +280,9 @@ public sealed class Bot : IBot
Helpers.ReadErrorAndExit(9); Helpers.ReadErrorAndExit(9);
} }
Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, Stopwatch.GetElapsedTime(startTime).TotalSeconds); Log.Information("Shard {ShardId} connected in {Elapsed:F2}s",
Client.ShardId,
Stopwatch.GetElapsedTime(startTime).TotalSeconds);
var commandHandler = Services.GetRequiredService<CommandHandler>(); var commandHandler = Services.GetRequiredService<CommandHandler>();
// start handling messages received in commandhandler // start handling messages received in commandhandler

View file

@ -10,6 +10,7 @@ namespace EllieBot.Db;
public abstract class EllieContext : DbContext public abstract class EllieContext : DbContext
{ {
public DbSet<GuildConfig> GuildConfigs { get; set; } public DbSet<GuildConfig> GuildConfigs { get; set; }
public DbSet<GreetSettings> GreetSettings { get; set; }
public DbSet<Quote> Quotes { get; set; } public DbSet<Quote> Quotes { get; set; }
public DbSet<Reminder> Reminders { get; set; } public DbSet<Reminder> Reminders { get; set; }
@ -678,6 +679,18 @@ public abstract class EllieContext : DbContext
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
#endregion #endregion
#region GreetSettings
modelBuilder
.Entity<GreetSettings>(gs => gs.HasIndex(x => new
{
x.GuildId,
x.GreetType
})
.IsUnique());
#endregion
} }
#if DEBUG #if DEBUG

View file

@ -1,4 +1,5 @@
#nullable disable #nullable disable
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using LinqToDB.EntityFrameworkCore; using LinqToDB.EntityFrameworkCore;
using EllieBot.Db.Models; using EllieBot.Db.Models;
@ -52,13 +53,19 @@ public static class GuildConfigExtensions
.Include(gc => gc.XpSettings) .Include(gc => gc.XpSettings)
.ThenInclude(x => x.ExclusionList); .ThenInclude(x => x.ExclusionList);
public static Task<GuildConfig[]> GetAllGuildConfigs( public static async Task<GuildConfig[]> GetAllGuildConfigs(
this DbSet<GuildConfig> configs, this DbSet<GuildConfig> configs,
IReadOnlyList<ulong> availableGuilds) List<ulong> availableGuilds)
=> configs.IncludeEverything() {
.AsNoTracking() var result = await configs
.AsQueryable()
.Include(x => x.CommandCooldowns)
.Where(x => availableGuilds.Contains(x.GuildId)) .Where(x => availableGuilds.Contains(x.GuildId))
.ToArrayAsyncEF(); .AsNoTracking()
.ToArrayAsync();
return result;
}
/// <summary> /// <summary>
/// Gets and creates if it doesn't exist a config for a guild. /// Gets and creates if it doesn't exist a config for a guild.
@ -84,7 +91,8 @@ public static class GuildConfigExtensions
if (config is null) if (config is null)
{ {
ctx.Set<GuildConfig>().Add(config = new() ctx.Set<GuildConfig>()
.Add(config = new()
{ {
GuildId = guildId, GuildId = guildId,
Permissions = Permissionv2.GetDefaultPermlist, Permissions = Permissionv2.GetDefaultPermlist,
@ -153,14 +161,16 @@ public static class GuildConfigExtensions
public static GuildConfig GcWithPermissionsFor(this DbContext ctx, ulong guildId) public static GuildConfig GcWithPermissionsFor(this DbContext ctx, ulong guildId)
{ {
var config = ctx.Set<GuildConfig>().AsQueryable() var config = ctx.Set<GuildConfig>()
.AsQueryable()
.Where(gc => gc.GuildId == guildId) .Where(gc => gc.GuildId == guildId)
.Include(gc => gc.Permissions) .Include(gc => gc.Permissions)
.FirstOrDefault(); .FirstOrDefault();
if (config is null) // if there is no guildconfig, create new one if (config is null) // if there is no guildconfig, create new one
{ {
ctx.Set<GuildConfig>().Add(config = new() ctx.Set<GuildConfig>()
.Add(config = new()
{ {
GuildId = guildId, GuildId = guildId,
Permissions = Permissionv2.GetDefaultPermlist Permissions = Permissionv2.GetDefaultPermlist
@ -186,6 +196,7 @@ public static class GuildConfigExtensions
.SelectMany(gc => gc.FollowedStreams) .SelectMany(gc => gc.FollowedStreams)
.ToList(); .ToList();
public static XpSettings XpSettingsFor(this DbContext ctx, ulong guildId) public static XpSettings XpSettingsFor(this DbContext ctx, ulong guildId)
{ {
var gc = ctx.GuildConfigsForId(guildId, var gc = ctx.GuildConfigsForId(guildId,

View file

@ -13,21 +13,23 @@ public class GuildConfig : DbEntity
public string AutoAssignRoleIds { get; set; } public string AutoAssignRoleIds { get; set; }
//greet stuff // //greet stuff
public int AutoDeleteGreetMessagesTimer { get; set; } = 30; // public int AutoDeleteGreetMessagesTimer { get; set; } = 30;
public int AutoDeleteByeMessagesTimer { get; set; } = 30; // public int AutoDeleteByeMessagesTimer { get; set; } = 30;
//
public ulong GreetMessageChannelId { get; set; } // public ulong GreetMessageChannelId { get; set; }
public ulong ByeMessageChannelId { get; set; } // public ulong ByeMessageChannelId { get; set; }
//
public bool SendDmGreetMessage { get; set; } // public bool SendDmGreetMessage { get; set; }
public string DmGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; // public string DmGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!";
//
public bool SendChannelGreetMessage { get; set; } // public bool SendChannelGreetMessage { get; set; }
public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; // public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!";
//
public bool SendChannelByeMessage { get; set; } // public bool SendChannelByeMessage { get; set; }
public string ChannelByeMessageText { get; set; } = "%user% has left!"; // public string ChannelByeMessageText { get; set; } = "%user% has left!";
// public bool SendBoostMessage { get; set; }
// pulic int BoostMessageDeleteAfter { get; set; }
//self assignable roles //self assignable roles
public bool ExclusiveSelfAssignedRoles { get; set; } public bool ExclusiveSelfAssignedRoles { get; set; }
@ -98,10 +100,6 @@ public class GuildConfig : DbEntity
#region Boost Message #region Boost Message
public bool SendBoostMessage { get; set; }
public string BoostMessage { get; set; } = "%user% just boosted this server!";
public ulong BoostMessageChannelId { get; set; }
public int BoostMessageDeleteAfter { get; set; }
public bool StickyRoles { get; set; } public bool StickyRoles { get; set; }
#endregion #endregion

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,202 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EllieBot.Migrations.Mysql
{
/// <inheritdoc />
public partial class greetsettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "autodeletebyemessagestimer",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "autodeletegreetmessagestimer",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "boostmessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "boostmessagechannelid",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "boostmessagedeleteafter",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "byemessagechannelid",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "channelbyemessagetext",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "channelgreetmessagetext",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "dmgreetmessagetext",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "greetmessagechannelid",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "sendboostmessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "sendchannelbyemessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "sendchannelgreetmessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "senddmgreetmessage",
table: "guildconfigs");
migrationBuilder.CreateTable(
name: "greetsettings",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
greettype = table.Column<int>(type: "int", nullable: false),
messagetext = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
isenabled = table.Column<bool>(type: "tinyint(1)", nullable: false),
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: true),
autodeletetimer = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_greetsettings", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_greetsettings_guildid_greettype",
table: "greetsettings",
columns: new[] { "guildid", "greettype" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "greetsettings");
migrationBuilder.AddColumn<int>(
name: "autodeletebyemessagestimer",
table: "guildconfigs",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "autodeletegreetmessagestimer",
table: "guildconfigs",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "boostmessage",
table: "guildconfigs",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<ulong>(
name: "boostmessagechannelid",
table: "guildconfigs",
type: "bigint unsigned",
nullable: false,
defaultValue: 0ul);
migrationBuilder.AddColumn<int>(
name: "boostmessagedeleteafter",
table: "guildconfigs",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<ulong>(
name: "byemessagechannelid",
table: "guildconfigs",
type: "bigint unsigned",
nullable: false,
defaultValue: 0ul);
migrationBuilder.AddColumn<string>(
name: "channelbyemessagetext",
table: "guildconfigs",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<string>(
name: "channelgreetmessagetext",
table: "guildconfigs",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<string>(
name: "dmgreetmessagetext",
table: "guildconfigs",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<ulong>(
name: "greetmessagechannelid",
table: "guildconfigs",
type: "bigint unsigned",
nullable: false,
defaultValue: 0ul);
migrationBuilder.AddColumn<bool>(
name: "sendboostmessage",
table: "guildconfigs",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "sendchannelbyemessage",
table: "guildconfigs",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "sendchannelgreetmessage",
table: "guildconfigs",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "senddmgreetmessage",
table: "guildconfigs",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
}
}

View file

@ -17,7 +17,7 @@ namespace EllieBot.Migrations.Mysql
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "8.0.4") .HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
@ -1221,42 +1221,10 @@ namespace EllieBot.Migrations.Mysql
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("autoassignroleids"); .HasColumnName("autoassignroleids");
b.Property<int>("AutoDeleteByeMessagesTimer")
.HasColumnType("int")
.HasColumnName("autodeletebyemessagestimer");
b.Property<int>("AutoDeleteGreetMessagesTimer")
.HasColumnType("int")
.HasColumnName("autodeletegreetmessagestimer");
b.Property<bool>("AutoDeleteSelfAssignedRoleMessages") b.Property<bool>("AutoDeleteSelfAssignedRoleMessages")
.HasColumnType("tinyint(1)") .HasColumnType("tinyint(1)")
.HasColumnName("autodeleteselfassignedrolemessages"); .HasColumnName("autodeleteselfassignedrolemessages");
b.Property<string>("BoostMessage")
.HasColumnType("longtext")
.HasColumnName("boostmessage");
b.Property<ulong>("BoostMessageChannelId")
.HasColumnType("bigint unsigned")
.HasColumnName("boostmessagechannelid");
b.Property<int>("BoostMessageDeleteAfter")
.HasColumnType("int")
.HasColumnName("boostmessagedeleteafter");
b.Property<ulong>("ByeMessageChannelId")
.HasColumnType("bigint unsigned")
.HasColumnName("byemessagechannelid");
b.Property<string>("ChannelByeMessageText")
.HasColumnType("longtext")
.HasColumnName("channelbyemessagetext");
b.Property<string>("ChannelGreetMessageText")
.HasColumnType("longtext")
.HasColumnName("channelgreetmessagetext");
b.Property<bool>("CleverbotEnabled") b.Property<bool>("CleverbotEnabled")
.HasColumnType("tinyint(1)") .HasColumnType("tinyint(1)")
.HasColumnName("cleverbotenabled"); .HasColumnName("cleverbotenabled");
@ -1277,10 +1245,6 @@ namespace EllieBot.Migrations.Mysql
.HasColumnType("tinyint(1)") .HasColumnType("tinyint(1)")
.HasColumnName("disableglobalexpressions"); .HasColumnName("disableglobalexpressions");
b.Property<string>("DmGreetMessageText")
.HasColumnType("longtext")
.HasColumnName("dmgreetmessagetext");
b.Property<bool>("ExclusiveSelfAssignedRoles") b.Property<bool>("ExclusiveSelfAssignedRoles")
.HasColumnType("tinyint(1)") .HasColumnType("tinyint(1)")
.HasColumnName("exclusiveselfassignedroles"); .HasColumnName("exclusiveselfassignedroles");
@ -1301,10 +1265,6 @@ namespace EllieBot.Migrations.Mysql
.HasColumnType("bigint unsigned") .HasColumnType("bigint unsigned")
.HasColumnName("gamevoicechannel"); .HasColumnName("gamevoicechannel");
b.Property<ulong>("GreetMessageChannelId")
.HasColumnType("bigint unsigned")
.HasColumnName("greetmessagechannelid");
b.Property<ulong>("GuildId") b.Property<ulong>("GuildId")
.HasColumnType("bigint unsigned") .HasColumnType("bigint unsigned")
.HasColumnName("guildid"); .HasColumnName("guildid");
@ -1329,22 +1289,6 @@ namespace EllieBot.Migrations.Mysql
.HasColumnType("longtext") .HasColumnType("longtext")
.HasColumnName("prefix"); .HasColumnName("prefix");
b.Property<bool>("SendBoostMessage")
.HasColumnType("tinyint(1)")
.HasColumnName("sendboostmessage");
b.Property<bool>("SendChannelByeMessage")
.HasColumnType("tinyint(1)")
.HasColumnName("sendchannelbyemessage");
b.Property<bool>("SendChannelGreetMessage")
.HasColumnType("tinyint(1)")
.HasColumnName("sendchannelgreetmessage");
b.Property<bool>("SendDmGreetMessage")
.HasColumnType("tinyint(1)")
.HasColumnName("senddmgreetmessage");
b.Property<bool>("StickyRoles") b.Property<bool>("StickyRoles")
.HasColumnType("tinyint(1)") .HasColumnType("tinyint(1)")
.HasColumnName("stickyroles"); .HasColumnName("stickyroles");
@ -3168,6 +3112,49 @@ namespace EllieBot.Migrations.Mysql
b.ToTable("xpshopowneditem", (string)null); b.ToTable("xpshopowneditem", (string)null);
}); });
modelBuilder.Entity("EllieBot.Services.GreetSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<int>("AutoDeleteTimer")
.HasColumnType("int")
.HasColumnName("autodeletetimer");
b.Property<ulong?>("ChannelId")
.HasColumnType("bigint unsigned")
.HasColumnName("channelid");
b.Property<int>("GreetType")
.HasColumnType("int")
.HasColumnName("greettype");
b.Property<ulong>("GuildId")
.HasColumnType("bigint unsigned")
.HasColumnName("guildid");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)")
.HasColumnName("isenabled");
b.Property<string>("MessageText")
.HasColumnType("longtext")
.HasColumnName("messagetext");
b.HasKey("Id")
.HasName("pk_greetsettings");
b.HasIndex("GuildId", "GreetType")
.IsUnique()
.HasDatabaseName("ix_greetsettings_guildid_greettype");
b.ToTable("greetsettings", (string)null);
});
modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b => modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b =>
{ {
b.HasOne("EllieBot.Db.Models.GuildConfig", null) b.HasOne("EllieBot.Db.Models.GuildConfig", null)

View file

@ -9,12 +9,6 @@ namespace EllieBot.Migrations.PostgreSql
{ {
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.AddColumn<decimal>(
name: "rolerequirement",
table: "shopentry",
type: "numeric(20,0)",
nullable: true);
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "autopublishchannel", name: "autopublishchannel",
columns: table => new columns: table => new
@ -41,10 +35,6 @@ namespace EllieBot.Migrations.PostgreSql
{ {
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "autopublishchannel"); name: "autopublishchannel");
migrationBuilder.DropColumn(
name: "rolerequirement",
table: "shopentry");
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,196 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace EllieBot.Migrations.PostgreSql
{
/// <inheritdoc />
public partial class greetsettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "autodeletebyemessagestimer",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "autodeletegreetmessagestimer",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "boostmessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "boostmessagechannelid",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "boostmessagedeleteafter",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "byemessagechannelid",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "channelbyemessagetext",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "channelgreetmessagetext",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "dmgreetmessagetext",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "greetmessagechannelid",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "sendboostmessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "sendchannelbyemessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "sendchannelgreetmessage",
table: "guildconfigs");
migrationBuilder.DropColumn(
name: "senddmgreetmessage",
table: "guildconfigs");
migrationBuilder.CreateTable(
name: "greetsettings",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
greettype = table.Column<int>(type: "integer", nullable: false),
messagetext = table.Column<string>(type: "text", nullable: true),
isenabled = table.Column<bool>(type: "boolean", nullable: false),
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
autodeletetimer = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_greetsettings", x => x.id);
});
migrationBuilder.CreateIndex(
name: "ix_greetsettings_guildid_greettype",
table: "greetsettings",
columns: new[] { "guildid", "greettype" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "greetsettings");
migrationBuilder.AddColumn<int>(
name: "autodeletebyemessagestimer",
table: "guildconfigs",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "autodeletegreetmessagestimer",
table: "guildconfigs",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "boostmessage",
table: "guildconfigs",
type: "text",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "boostmessagechannelid",
table: "guildconfigs",
type: "numeric(20,0)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<int>(
name: "boostmessagedeleteafter",
table: "guildconfigs",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<decimal>(
name: "byemessagechannelid",
table: "guildconfigs",
type: "numeric(20,0)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<string>(
name: "channelbyemessagetext",
table: "guildconfigs",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "channelgreetmessagetext",
table: "guildconfigs",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "dmgreetmessagetext",
table: "guildconfigs",
type: "text",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "greetmessagechannelid",
table: "guildconfigs",
type: "numeric(20,0)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<bool>(
name: "sendboostmessage",
table: "guildconfigs",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "sendchannelbyemessage",
table: "guildconfigs",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "sendchannelgreetmessage",
table: "guildconfigs",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "senddmgreetmessage",
table: "guildconfigs",
type: "boolean",
nullable: false,
defaultValue: false);
}
}
}

View file

@ -17,7 +17,7 @@ namespace EllieBot.Migrations.PostgreSql
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "8.0.4") .HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@ -1220,42 +1220,10 @@ namespace EllieBot.Migrations.PostgreSql
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("autoassignroleids"); .HasColumnName("autoassignroleids");
b.Property<int>("AutoDeleteByeMessagesTimer")
.HasColumnType("integer")
.HasColumnName("autodeletebyemessagestimer");
b.Property<int>("AutoDeleteGreetMessagesTimer")
.HasColumnType("integer")
.HasColumnName("autodeletegreetmessagestimer");
b.Property<bool>("AutoDeleteSelfAssignedRoleMessages") b.Property<bool>("AutoDeleteSelfAssignedRoleMessages")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("autodeleteselfassignedrolemessages"); .HasColumnName("autodeleteselfassignedrolemessages");
b.Property<string>("BoostMessage")
.HasColumnType("text")
.HasColumnName("boostmessage");
b.Property<decimal>("BoostMessageChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("boostmessagechannelid");
b.Property<int>("BoostMessageDeleteAfter")
.HasColumnType("integer")
.HasColumnName("boostmessagedeleteafter");
b.Property<decimal>("ByeMessageChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("byemessagechannelid");
b.Property<string>("ChannelByeMessageText")
.HasColumnType("text")
.HasColumnName("channelbyemessagetext");
b.Property<string>("ChannelGreetMessageText")
.HasColumnType("text")
.HasColumnName("channelgreetmessagetext");
b.Property<bool>("CleverbotEnabled") b.Property<bool>("CleverbotEnabled")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("cleverbotenabled"); .HasColumnName("cleverbotenabled");
@ -1276,10 +1244,6 @@ namespace EllieBot.Migrations.PostgreSql
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("disableglobalexpressions"); .HasColumnName("disableglobalexpressions");
b.Property<string>("DmGreetMessageText")
.HasColumnType("text")
.HasColumnName("dmgreetmessagetext");
b.Property<bool>("ExclusiveSelfAssignedRoles") b.Property<bool>("ExclusiveSelfAssignedRoles")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("exclusiveselfassignedroles"); .HasColumnName("exclusiveselfassignedroles");
@ -1300,10 +1264,6 @@ namespace EllieBot.Migrations.PostgreSql
.HasColumnType("numeric(20,0)") .HasColumnType("numeric(20,0)")
.HasColumnName("gamevoicechannel"); .HasColumnName("gamevoicechannel");
b.Property<decimal>("GreetMessageChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("greetmessagechannelid");
b.Property<decimal>("GuildId") b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)") .HasColumnType("numeric(20,0)")
.HasColumnName("guildid"); .HasColumnName("guildid");
@ -1328,22 +1288,6 @@ namespace EllieBot.Migrations.PostgreSql
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("prefix"); .HasColumnName("prefix");
b.Property<bool>("SendBoostMessage")
.HasColumnType("boolean")
.HasColumnName("sendboostmessage");
b.Property<bool>("SendChannelByeMessage")
.HasColumnType("boolean")
.HasColumnName("sendchannelbyemessage");
b.Property<bool>("SendChannelGreetMessage")
.HasColumnType("boolean")
.HasColumnName("sendchannelgreetmessage");
b.Property<bool>("SendDmGreetMessage")
.HasColumnType("boolean")
.HasColumnName("senddmgreetmessage");
b.Property<bool>("StickyRoles") b.Property<bool>("StickyRoles")
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("stickyroles"); .HasColumnName("stickyroles");
@ -3163,6 +3107,49 @@ namespace EllieBot.Migrations.PostgreSql
b.ToTable("xpshopowneditem", (string)null); b.ToTable("xpshopowneditem", (string)null);
}); });
modelBuilder.Entity("EllieBot.Services.GreetSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AutoDeleteTimer")
.HasColumnType("integer")
.HasColumnName("autodeletetimer");
b.Property<decimal?>("ChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("channelid");
b.Property<int>("GreetType")
.HasColumnType("integer")
.HasColumnName("greettype");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<bool>("IsEnabled")
.HasColumnType("boolean")
.HasColumnName("isenabled");
b.Property<string>("MessageText")
.HasColumnType("text")
.HasColumnName("messagetext");
b.HasKey("Id")
.HasName("pk_greetsettings");
b.HasIndex("GuildId", "GreetType")
.IsUnique()
.HasDatabaseName("ix_greetsettings_guildid_greettype");
b.ToTable("greetsettings", (string)null);
});
modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b => modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b =>
{ {
b.HasOne("EllieBot.Db.Models.GuildConfig", null) b.HasOne("EllieBot.Db.Models.GuildConfig", null)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,219 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EllieBot.Migrations
{
/// <inheritdoc />
public partial class greetsettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "GreetSettings",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
GreetType = table.Column<int>(type: "INTEGER", nullable: false),
MessageText = table.Column<string>(type: "TEXT", nullable: true),
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: true),
AutoDeleteTimer = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GreetSettings", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_GreetSettings_GuildId_GreetType",
table: "GreetSettings",
columns: new[] { "GuildId", "GreetType" },
unique: true);
migrationBuilder.Sql("""
INSERT INTO GreetSettings (GuildId, GreetType, MessageText, IsEnabled, ChannelId, AutoDeleteTimer)
SELECT GuildId, 0, ChannelGreetMessageText, SendChannelGreetMessage, GreetMessageChannelId, AutoDeleteGreetMessagesTimer
FROM GuildConfigs
WHERE SendChannelGreetMessage = 1;
INSERT INTO GreetSettings (GuildId, GreetType, MessageText, IsEnabled, ChannelId, AutoDeleteTimer)
SELECT GuildId, 1, DmGreetMessageText, SendDmGreetMessage, GreetMessageChannelId, 0
FROM GuildConfigs
WHERE SendDmGreetMessage = 1;
INSERT INTO GreetSettings (GuildId, GreetType, MessageText, IsEnabled, ChannelId, AutoDeleteTimer)
SELECT GuildId, 2, ChannelByeMessageText, SendChannelByeMessage, ByeMessageChannelId, AutoDeleteByeMessagesTimer
FROM GuildConfigs
WHERE SendChannelByeMessage = 1;
INSERT INTO GreetSettings (GuildId, GreetType, MessageText, IsEnabled, ChannelId, AutoDeleteTimer)
SELECT GuildId, 3, BoostMessage, SendBoostMessage, BoostMessageChannelId, BoostMessageDeleteAfter
FROM GuildConfigs
WHERE SendBoostMessage = 1;
""");
migrationBuilder.DropColumn(
name: "AutoDeleteByeMessagesTimer",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "AutoDeleteGreetMessagesTimer",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "BoostMessage",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "BoostMessageChannelId",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "BoostMessageDeleteAfter",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "ByeMessageChannelId",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "ChannelByeMessageText",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "ChannelGreetMessageText",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "DmGreetMessageText",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "GreetMessageChannelId",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "SendBoostMessage",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "SendChannelByeMessage",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "SendChannelGreetMessage",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "SendDmGreetMessage",
table: "GuildConfigs");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GreetSettings");
migrationBuilder.AddColumn<int>(
name: "AutoDeleteByeMessagesTimer",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "AutoDeleteGreetMessagesTimer",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "BoostMessage",
table: "GuildConfigs",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<ulong>(
name: "BoostMessageChannelId",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0ul);
migrationBuilder.AddColumn<int>(
name: "BoostMessageDeleteAfter",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<ulong>(
name: "ByeMessageChannelId",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0ul);
migrationBuilder.AddColumn<string>(
name: "ChannelByeMessageText",
table: "GuildConfigs",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ChannelGreetMessageText",
table: "GuildConfigs",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DmGreetMessageText",
table: "GuildConfigs",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<ulong>(
name: "GreetMessageChannelId",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: 0ul);
migrationBuilder.AddColumn<bool>(
name: "SendBoostMessage",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "SendChannelByeMessage",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "SendChannelGreetMessage",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "SendDmGreetMessage",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
}
}

View file

@ -15,7 +15,7 @@ namespace EllieBot.Migrations
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); modelBuilder.HasAnnotation("ProductVersion", "8.0.8");
modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b => modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b =>
{ {
@ -907,33 +907,9 @@ namespace EllieBot.Migrations
b.Property<string>("AutoAssignRoleIds") b.Property<string>("AutoAssignRoleIds")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int>("AutoDeleteByeMessagesTimer")
.HasColumnType("INTEGER");
b.Property<int>("AutoDeleteGreetMessagesTimer")
.HasColumnType("INTEGER");
b.Property<bool>("AutoDeleteSelfAssignedRoleMessages") b.Property<bool>("AutoDeleteSelfAssignedRoleMessages")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("BoostMessage")
.HasColumnType("TEXT");
b.Property<ulong>("BoostMessageChannelId")
.HasColumnType("INTEGER");
b.Property<int>("BoostMessageDeleteAfter")
.HasColumnType("INTEGER");
b.Property<ulong>("ByeMessageChannelId")
.HasColumnType("INTEGER");
b.Property<string>("ChannelByeMessageText")
.HasColumnType("TEXT");
b.Property<string>("ChannelGreetMessageText")
.HasColumnType("TEXT");
b.Property<bool>("CleverbotEnabled") b.Property<bool>("CleverbotEnabled")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -949,9 +925,6 @@ namespace EllieBot.Migrations
b.Property<bool>("DisableGlobalExpressions") b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("DmGreetMessageText")
.HasColumnType("TEXT");
b.Property<bool>("ExclusiveSelfAssignedRoles") b.Property<bool>("ExclusiveSelfAssignedRoles")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -967,9 +940,6 @@ namespace EllieBot.Migrations
b.Property<ulong?>("GameVoiceChannel") b.Property<ulong?>("GameVoiceChannel")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<ulong>("GreetMessageChannelId")
.HasColumnType("INTEGER");
b.Property<ulong>("GuildId") b.Property<ulong>("GuildId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -988,18 +958,6 @@ namespace EllieBot.Migrations
b.Property<string>("Prefix") b.Property<string>("Prefix")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<bool>("SendBoostMessage")
.HasColumnType("INTEGER");
b.Property<bool>("SendChannelByeMessage")
.HasColumnType("INTEGER");
b.Property<bool>("SendChannelGreetMessage")
.HasColumnType("INTEGER");
b.Property<bool>("SendDmGreetMessage")
.HasColumnType("INTEGER");
b.Property<bool>("StickyRoles") b.Property<bool>("StickyRoles")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -2351,6 +2309,38 @@ namespace EllieBot.Migrations
b.ToTable("XpShopOwnedItem"); b.ToTable("XpShopOwnedItem");
}); });
modelBuilder.Entity("EllieBot.Services.GreetSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("AutoDeleteTimer")
.HasColumnType("INTEGER");
b.Property<ulong?>("ChannelId")
.HasColumnType("INTEGER");
b.Property<int>("GreetType")
.HasColumnType("INTEGER");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("MessageText")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GuildId", "GreetType")
.IsUnique();
b.ToTable("GreetSettings");
});
modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b => modelBuilder.Entity("EllieBot.Db.Models.AntiAltSetting", b =>
{ {
b.HasOne("EllieBot.Db.Models.GuildConfig", null) b.HasOne("EllieBot.Db.Models.GuildConfig", null)

View file

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

View file

@ -5,238 +5,223 @@ public partial class Administration
[Group] [Group]
public partial class GreetCommands : EllieModule<GreetService> public partial class GreetCommands : EllieModule<GreetService>
{ {
[Cmd] public async Task Toggle(GreetType type)
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async Task Boost()
{ {
var enabled = await _service.ToggleBoost(ctx.Guild.Id, ctx.Channel.Id); var enabled = await _service.SetGreet(ctx.Guild.Id, ctx.Channel.Id, type);
if (enabled) if (enabled)
await Response().Confirm(strs.boost_on).SendAsync(); 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 else
await Response().Pending(strs.boost_off).SendAsync(); 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();
} }
[Cmd]
[RequireContext(ContextType.Guild)] public async Task SetDel(GreetType type, int timer)
[UserPerm(GuildPerm.ManageGuild)]
public async Task BoostDel(int timer = 30)
{ {
if (timer is < 0 or > 600) if (timer is < 0 or > 600)
return; return;
await _service.SetBoostDel(ctx.Guild.Id, timer); await _service.SetDeleteTimer(ctx.Guild.Id, type, timer);
if (timer > 0) 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 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)] public async Task SetMsg(GreetType type, string? text = null)
[UserPerm(GuildPerm.ManageGuild)]
public async Task BoostMsg([Leftover] string? text = null)
{ {
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
{ {
var boostMessage = _service.GetBoostMessage(ctx.Guild.Id); await _service.SetMessage(ctx.Guild.Id, type, null);
await Response().Confirm(strs.boostmsg_cur(boostMessage?.SanitizeMentions())).SendAsync(); var conf = await _service.GetGreetSettingsAsync(ctx.Guild.Id, type);
var msg = conf?.MessageText ?? "No message set.";
await Response()
.Confirm(
type switch
{
GreetType.Boost => strs.boostmsg_cur(msg.SanitizeMentions()),
GreetType.Greet => strs.greetmsg_cur(msg.SanitizeMentions()),
GreetType.Bye => strs.byemsg_cur(msg.SanitizeMentions()),
GreetType.GreetDm => strs.greetdmmsg_cur(msg.SanitizeMentions()),
_ => strs.error
})
.SendAsync();
return; 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(); await Response()
if (!sendBoostEnabled) .Confirm(type switch
await Response().Pending(strs.boostmsg_enable($"`{prefix}boost`")).SendAsync(); {
GreetType.Boost => strs.boostmsg_new,
GreetType.Greet => strs.greetmsg_new,
GreetType.Bye => strs.byemsg_new,
GreetType.GreetDm => strs.greetdmmsg_new,
_ => strs.error
})
.SendAsync();
if (!isEnabled)
{
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();
}
} }
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDel(int timer = 30) public Task Boost()
{ => Toggle(GreetType.Boost);
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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
public async Task Greet() public Task BoostDel(int timer = 30)
{ => SetDel(GreetType.Boost, timer);
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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
public async Task GreetMsg([Leftover] string? text = null) public Task BoostMsg([Leftover] string? text = null)
{ => SetMsg(GreetType.Boost, text);
if (string.IsNullOrWhiteSpace(text))
{
var greetMsg = _service.GetGreetMsg(ctx.Guild.Id);
await Response().Confirm(strs.greetmsg_cur(greetMsg?.SanitizeMentions())).SendAsync();
return;
}
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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDm() public Task Greet()
{ => Toggle(GreetType.Greet);
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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
public async Task GreetDmMsg([Leftover] string? text = null) public Task GreetDel(int timer = 30)
{ => SetDel(GreetType.Greet, timer);
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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
public async Task Bye() public Task GreetMsg([Leftover] string? text = null)
{ => SetMsg(GreetType.Greet, text);
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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
public async Task ByeMsg([Leftover] string? text = null) public Task GreetDm()
{ => Toggle(GreetType.GreetDm);
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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
public async Task ByeDel(int timer = 30) public Task GreetDmMsg([Leftover] string? text = null)
{ => SetMsg(GreetType.GreetDm, text);
await _service.SetByeDel(ctx.Guild.Id, timer);
if (timer > 0) [Cmd]
await Response().Confirm(strs.byedel_on(timer)).SendAsync(); [RequireContext(ContextType.Guild)]
else [UserPerm(GuildPerm.ManageGuild)]
await Response().Pending(strs.byedel_off).SendAsync(); 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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)] [Ratelimit(5)]
public async Task ByeTest([Leftover] IGuildUser? user = null) public Task GreetTest([Leftover] IGuildUser? user = null)
{ => Test(GreetType.Greet, user);
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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)] [Ratelimit(5)]
public async Task GreetTest([Leftover] IGuildUser? user = null) public Task GreetDmTest([Leftover] IGuildUser? user = null)
{ => Test(GreetType.GreetDm, user);
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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)] [Ratelimit(5)]
public async Task GreetDmTest([Leftover] IGuildUser? user = null) public Task ByeTest([Leftover] IGuildUser? user = null)
{ => Test(GreetType.Bye, user);
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] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)] [UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)] [Ratelimit(5)]
public async Task BoostTest([Leftover] IGuildUser? user = null) public Task BoostTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Boost, user);
public async Task Test(GreetType type, IGuildUser? user = null)
{ {
user ??= (IGuildUser)ctx.User; user ??= (IGuildUser)ctx.User;
await _service.BoostTest((ITextChannel)ctx.Channel, user); await _service.Test(ctx.Guild.Id, type, (ITextChannel)ctx.Channel, user);
var enabled = _service.GetBoostEnabled(ctx.Guild.Id); var conf = await _service.GetGreetSettingsAsync(ctx.Guild.Id, type);
if (!enabled) if (conf?.IsEnabled is not true)
await Response().Pending(strs.boostmsg_enable($"`{prefix}boost`")).SendAsync(); await Response().Pending(strs.boostmsg_enable($"`{prefix}boost`")).SendAsync();
} }
} }

View file

@ -1,71 +0,0 @@
namespace EllieBot.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,76 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using LinqToDB.Tools;
using EllieBot.Common.ModuleBehaviors; using EllieBot.Common.ModuleBehaviors;
using EllieBot.Db.Models;
using System.Threading.Channels; using System.Threading.Channels;
namespace EllieBot.Services; namespace EllieBot.Services;
public class GreetService : IEService, IReadyExecutor public class GreetService : IEService, IReadyExecutor
{ {
public bool GroupGreets
=> _bss.Data.GroupGreets;
private readonly DbService _db; private readonly DbService _db;
private readonly ConcurrentDictionary<ulong, GreetSettings> _guildConfigsCache; private ConcurrentDictionary<GreetType, ConcurrentHashSet<ulong>> _enabled = new();
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private readonly GreetGrouper<IGuildUser> _greets = new();
private readonly GreetGrouper<IUser> _byes = new();
private readonly BotConfigService _bss; private readonly BotConfigService _bss;
private readonly IReplacementService _repSvc; private readonly IReplacementService _repSvc;
private readonly IBotCache _cache;
private readonly IMessageSenderService _sender; private readonly IMessageSenderService _sender;
private readonly Channel<(GreetSettings, IUser, ITextChannel?)> _greetQueue =
Channel.CreateBounded<(GreetSettings, IUser, ITextChannel?)>(
new BoundedChannelOptions(60)
{
FullMode = BoundedChannelFullMode.DropOldest
});
public GreetService( public GreetService(
DiscordSocketClient client, DiscordSocketClient client,
IBot bot,
DbService db, DbService db,
BotConfigService bss, BotConfigService bss,
IMessageSenderService sender, IMessageSenderService sender,
IReplacementService repSvc) IReplacementService repSvc,
IBotCache cache
)
{ {
_db = db; _db = db;
_client = client; _client = client;
_bss = bss; _bss = bss;
_repSvc = repSvc; _repSvc = repSvc;
_cache = cache;
_sender = sender; _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;
} }
public async Task OnReadyAsync() 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 guilds = _client.Guilds.Select(x => x.Id).ToList();
var res = await GreetDmUserInternal(conf, user); var enabled = await uow.GetTable<GreetSettings>()
compl.TrySetResult(res); .Where(x => x.GuildId.In(guilds))
await Task.Delay(2000); .Where(x => x.IsEnabled)
.ToListAsync();
_enabled = enabled.GroupBy(x => x.GreetType, v => v.GuildId)
.ToDictionary(x => x.Key, x => x.ToHashSet().ToConcurrentSet())
.ToConcurrent();
}
_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 +83,38 @@ public class GreetService : IEService, IReadyExecutor
&& newUser.PremiumSince is { } newDate && newUser.PremiumSince is { } newDate
&& newDate > oldDate)) && newDate > oldDate))
{ {
var conf = GetOrAddSettingsForGuild(newUser.Guild.Id); _ = Task.Run(async () =>
if (!conf.SendBoostMessage)
return Task.CompletedTask;
_ = Task.Run(TriggerBoostMessage(conf, newUser));
}
return Task.CompletedTask;
}
private Func<Task> TriggerBoostMessage(GreetSettings conf, SocketGuildUser user)
=> async () =>
{ {
var channel = user.Guild.GetTextChannel(conf.BoostMessageChannelId); var conf = await GetGreetSettingsAsync(newUser.Guild.Id, GreetType.Boost);
if (conf is null || !conf.IsEnabled)
return;
ITextChannel? channel = null;
if (conf.ChannelId is { } cid)
channel = newUser.Guild.GetTextChannel(cid);
if (channel is null) if (channel is null)
return; return;
await SendBoostMessage(conf, user, channel); await GreetUsers(conf, channel, newUser);
}; });
private async Task<bool> SendBoostMessage(GreetSettings conf, IGuildUser user, ITextChannel channel)
{
if (string.IsNullOrWhiteSpace(conf.BoostMessage))
return false;
var toSend = SmartText.CreateFrom(conf.BoostMessage);
try
{
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");
} }
return false;
}
private Task OnClientLeftGuild(SocketGuild arg)
{
_guildConfigsCache.TryRemove(arg.Id, out _);
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task OnBotJoinedGuild(GuildConfig gc) private async Task OnClientLeftGuild(SocketGuild guild)
{ {
_guildConfigsCache[gc.GuildId] = GreetSettings.Create(gc); foreach (var gt in Enum.GetValues<GreetType>())
return Task.CompletedTask; {
_enabled[gt].TryRemove(guild.Id);
}
await using var uow = _db.GetDbContext();
await uow.GetTable<GreetSettings>()
.Where(x => x.GuildId == guild.Id)
.DeleteAsync();
} }
private Task OnUserLeft(SocketGuild guild, SocketUser user) private Task OnUserLeft(SocketGuild guild, SocketUser user)
@ -128,35 +123,20 @@ public class GreetService : IEService, IReadyExecutor
{ {
try try
{ {
var conf = GetOrAddSettingsForGuild(guild.Id); var conf = await GetGreetSettingsAsync(guild.Id, GreetType.Bye);
if (!conf.SendChannelByeMessage) if (conf is null)
return; 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 if (channel is null) //maybe warn the server owner that the channel is missing
{
await SetGreet(guild.Id, null, GreetType.Bye, false);
return; return;
}
if (GroupGreets) await _greetQueue.Writer.WriteAsync((conf, user, channel));
{
// 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);
}
}
}
else
await ByeUsers(conf, channel, new[] { user });
} }
catch catch
{ {
@ -166,98 +146,58 @@ public class GreetService : IEService, IReadyExecutor
return Task.CompletedTask; return Task.CompletedTask;
} }
public string? GetDmGreetMsg(ulong id) private readonly TypedKey<GreetSettings?> _greetSettingsKey = new();
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(); await using var uow = _db.GetDbContext();
return uow.GuildConfigsForId(id, set => set).DmGreetMessageText; var res = await uow.GetTable<GreetSettings>()
.Where(x => x.GuildId == gid && x.GreetType == type)
.FirstOrDefaultAsync();
return res;
} }
public string? GetGreetMsg(ulong gid) private async Task GreetUsers(GreetSettings conf, ITextChannel? channel, IUser user)
{ {
using var uow = _db.GetDbContext(); if (conf.GreetType == GreetType.GreetDm)
return uow.GuildConfigsForId(gid, set => set).ChannelGreetMessageText; {
if (user is not IGuildUser gu)
return;
await GreetDmUserInternal(conf, gu);
return;
} }
public string? GetBoostMessage(ulong gid) if (channel is null)
{
using var uow = _db.GetDbContext();
return uow.GuildConfigsForId(gid, set => set).BoostMessage;
}
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())
return; return;
var repCtx = new ReplacementContext(client: _client, var repCtx = new ReplacementContext(client: _client,
guild: channel.Guild, guild: channel.Guild,
channel: channel, 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); text = await _repSvc.ReplaceAsync(text, repCtx);
try try
{ {
var toDelete = await _sender.Response(channel).Text(text).Sanitize(false).SendAsync(); var toDelete = await _sender.Response(channel).Text(text).Sanitize(false).SendAsync();
if (conf.AutoDeleteByeMessagesTimer > 0) if (conf.AutoDeleteTimer > 0)
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer); toDelete.DeleteAfter(conf.AutoDeleteTimer);
} }
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions catch (HttpException ex) when (ex.DiscordCode is DiscordErrorCode.InsufficientPermissions
|| ex.DiscordCode == DiscordErrorCode.MissingPermissions or DiscordErrorCode.MissingPermissions
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel) or 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)
{ {
Log.Warning(ex, Log.Warning(ex,
"Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}", "Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}",
channel.GuildId); channel.GuildId);
await SetGreet(channel.GuildId, channel.Id, false); await SetGreet(channel.GuildId, channel.Id, GreetType.Greet, false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -265,19 +205,11 @@ public class GreetService : IEService, 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) private async Task<bool> GreetDmUser(GreetSettings conf, IGuildUser user)
{ {
var completionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); 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; return await completionSource.Task;
} }
@ -285,13 +217,8 @@ public class GreetService : IEService, IReadyExecutor
{ {
try try
{ {
// var rep = new ReplacementBuilder() var repCtx = new ReplacementContext(client: _client, guild: user.Guild, user: user);
// .WithUser(user) var smartText = SmartText.CreateFrom(conf.MessageText);
// .WithServer(_client, (SocketGuild)user.Guild)
// .Build();
var repCtx = new ReplacementContext(client: _client, guild: user.Guild, users: user);
var smartText = SmartText.CreateFrom(conf.DmGreetMessageText);
smartText = await _repSvc.ReplaceAsync(smartText, repCtx); smartText = await _repSvc.ReplaceAsync(smartText, repCtx);
if (smartText is SmartPlainText pt) if (smartText is SmartPlainText pt)
@ -372,38 +299,21 @@ public class GreetService : IEService, IReadyExecutor
{ {
try 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 (channel is not null)
{ {
if (GroupGreets) await _greetQueue.Writer.WriteAsync((conf, user, channel));
{
// 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 });
} }
} }
if (conf.SendDmGreetMessage) var confDm = await GetGreetSettingsAsync(user.GuildId, GreetType.GreetDm);
await GreetDmUser(conf, user);
if (confDm?.IsEnabled ?? false)
await GreetDmUser(confDm, user);
} }
catch catch
{ {
@ -413,256 +323,165 @@ public class GreetService : IEService, IReadyExecutor
return Task.CompletedTask; return Task.CompletedTask;
} }
public string? GetByeMessage(ulong gid)
private static string GetDefaultGreet(GreetType greetType)
=> greetType switch
{ {
using var uow = _db.GetDbContext(); GreetType.Boost => "%user.name% has boosted the server!",
return uow.GuildConfigsForId(gid, set => set).ChannelByeMessageText; GreetType.Greet => "%user.name% 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 GreetSettings GetOrAddSettingsForGuild(ulong guildId) public async Task<bool> SetGreet(
{ ulong guildId,
if (_guildConfigsCache.TryGetValue(guildId, out var settings)) ulong? channelId,
return settings; GreetType greetType,
bool? value = null)
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guildId, set => set);
settings = GreetSettings.Create(gc);
}
_guildConfigsCache.TryAdd(guildId, settings);
return settings;
}
public async Task<bool> SetGreet(ulong guildId, ulong channelId, bool? value = null)
{ {
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var conf = uow.GuildConfigsForId(guildId, set => set); var q = uow.GetTable<GreetSettings>();
var enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage;
conf.GreetMessageChannelId = channelId;
var toAdd = GreetSettings.Create(conf); if (value is { } v)
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
return enabled;
}
public bool SetGreetMessage(ulong guildId, ref string message)
{ {
message = message.SanitizeMentions(); var defaultGreet = GetDefaultGreet(greetType);
if (string.IsNullOrWhiteSpace(message)) await q
throw new ArgumentNullException(nameof(message)); .InsertOrUpdateAsync(() => new()
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(); GuildId = guildId,
var conf = uow.GuildConfigsForId(guildId, set => set); GreetType = greetType,
var enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage; MessageText = defaultGreet,
IsEnabled = v,
ChannelId = channelId,
},
(old) => new()
{
IsEnabled = v,
ChannelId = channelId,
},
() => new()
{
GuildId = guildId,
GreetType = greetType,
});
}
else
{
var result = await q
.Where(x => x.GuildId == guildId && x.GreetType == greetType)
.UpdateWithOutputAsync((old) => new()
{
IsEnabled = !old.IsEnabled
},
(o, n) => n.IsEnabled);
var toAdd = GreetSettings.Create(conf); if (result.Length > 0)
_guildConfigsCache[guildId] = toAdd; value = result[0];
await uow.SaveChangesAsync();
return enabled;
} }
public bool SetGreetDmMessage(ulong guildId, ref string? message) if (value is true)
{
_enabled[greetType].Add(guildId);
}
else
{
_enabled[greetType].TryRemove(guildId);
}
return true;
}
public async Task<bool> SetMessage(ulong guildId, GreetType greetType, string? message)
{ {
message = message?.SanitizeMentions(); message = message?.SanitizeMentions();
if (string.IsNullOrWhiteSpace(message)) if (string.IsNullOrWhiteSpace(message))
throw new ArgumentNullException(nameof(message)); message = GetDefaultGreet(greetType);
using var uow = _db.GetDbContext(); await using (var uow = _db.GetDbContext())
var conf = uow.GuildConfigsForId(guildId, set => set); {
conf.DmGreetMessageText = message; await uow.GetTable<GreetSettings>()
.InsertOrUpdateAsync(() => new()
var toAdd = GreetSettings.Create(conf); {
_guildConfigsCache[guildId] = toAdd; GuildId = guildId,
GreetType = greetType,
uow.SaveChanges(); MessageText = message
return conf.SendDmGreetMessage; },
x => new()
{
MessageText = message
},
() => new()
{
GuildId = guildId,
GreetType = greetType
});
} }
public async Task<bool> SetBye(ulong guildId, ulong channelId, bool? value = null) var conf = await GetGreetSettingsAsync(guildId, greetType);
{
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); return conf?.IsEnabled ?? false;
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
return enabled;
} }
public bool SetByeMessage(ulong guildId, ref string? message) public async Task<bool> SetDeleteTimer(ulong guildId, GreetType greetType, int timer)
{ {
message = message?.SanitizeMentions(); if (timer < 0 || timer > 3600)
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)
throw new ArgumentOutOfRangeException(nameof(timer)); throw new ArgumentOutOfRangeException(nameof(timer));
await using var uow = _db.GetDbContext(); await using (var uow = _db.GetDbContext())
var conf = uow.GuildConfigsForId(guildId, set => set);
conf.BoostMessageDeleteAfter = timer;
var toAdd = GreetSettings.Create(conf);
_guildConfigsCache[guildId] = toAdd;
await uow.SaveChangesAsync();
}
public async Task<bool> ToggleBoost(ulong guildId, ulong channelId, bool? forceState = null)
{ {
await using var uow = _db.GetDbContext(); await uow.GetTable<GreetSettings>()
var conf = uow.GuildConfigsForId(guildId, set => set); .InsertOrUpdateAsync(() => new()
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;
}
#region Get Enabled Status
public bool GetGreetDmEnabled(ulong guildId)
{ {
using var uow = _db.GetDbContext(); GuildId = guildId,
var conf = uow.GuildConfigsForId(guildId, set => set); GreetType = greetType,
return conf.SendDmGreetMessage; AutoDeleteTimer = timer,
} },
x => new()
public bool GetGreetEnabled(ulong guildId)
{ {
using var uow = _db.GetDbContext(); AutoDeleteTimer = timer
var conf = uow.GuildConfigsForId(guildId, set => set); },
return conf.SendChannelGreetMessage; () => new()
}
public bool GetByeEnabled(ulong guildId)
{ {
using var uow = _db.GetDbContext(); GuildId = guildId,
var conf = uow.GuildConfigsForId(guildId, set => set); GreetType = greetType
return conf.SendChannelByeMessage; });
} }
public bool GetBoostEnabled(ulong guildId) var conf = await GetGreetSettingsAsync(guildId, greetType);
return conf?.IsEnabled ?? false;
}
public async Task<bool> Test(
ulong guildId,
GreetType type,
IMessageChannel channel,
IGuildUser user)
{ {
using var uow = _db.GetDbContext(); var conf = await GetGreetSettingsAsync(guildId, type);
var conf = uow.GuildConfigsForId(guildId, set => set); if (conf is null)
return conf.SendBoostMessage; return false;
await SendMessage(conf, channel, user);
return true;
} }
#endregion public async Task<bool> SendMessage(GreetSettings conf, IMessageChannel channel, IGuildUser user)
#region Test Messages
public Task ByeTest(ITextChannel channel, IGuildUser user)
{ {
var conf = GetOrAddSettingsForGuild(user.GuildId); if (conf.GreetType == GreetType.GreetDm)
return ByeUsers(conf, channel, user);
}
public Task GreetTest(ITextChannel channel, IGuildUser user)
{ {
var conf = GetOrAddSettingsForGuild(user.GuildId); return await GreetDmUser(conf, user);
return GreetUsers(conf, channel, user);
} }
public Task<bool> GreetDmTest(IGuildUser user) if (channel is not ITextChannel ch)
{ return false;
var conf = GetOrAddSettingsForGuild(user.GuildId);
return GreetDmUser(conf, user);
}
public Task<bool> BoostTest(ITextChannel channel, IGuildUser user) await GreetUsers(conf, ch, user);
{ return true;
var conf = GetOrAddSettingsForGuild(user.GuildId);
return SendBoostMessage(conf, user, channel);
} }
#endregion
} }

View file

@ -2,44 +2,43 @@ using EllieBot.Db.Models;
namespace EllieBot.Services; namespace EllieBot.Services;
public enum GreetType
{
Greet,
GreetDm,
Bye,
Boost,
}
public class GreetSettings public class GreetSettings
{ {
public int AutoDeleteGreetMessagesTimer { get; set; } public int Id { get; set; }
public int AutoDeleteByeMessagesTimer { get; set; }
public ulong GreetMessageChannelId { get; set; } public ulong GuildId { get; set; }
public ulong ByeMessageChannelId { get; set; } public GreetType GreetType { get; set; }
public string? MessageText { get; set; }
public bool IsEnabled { get; set; }
public ulong? ChannelId { get; set; }
public bool SendDmGreetMessage { get; set; } public int AutoDeleteTimer { get; set; }
public string? DmGreetMessageText { get; set; }
public bool SendChannelGreetMessage { get; set; } // public int AutoDeleteGreetMessagesTimer { get; set; }
public string? ChannelGreetMessageText { get; set; } // public int AutoDeleteByeMessagesTimer { get; set; }
//
public bool SendChannelByeMessage { get; set; } // public ulong GreetMessageChannelId { get; set; }
public string? ChannelByeMessageText { get; set; } // public ulong ByeMessageChannelId { get; set; }
//
public bool SendBoostMessage { get; set; } // public bool SendDmGreetMessage { get; set; }
public string? BoostMessage { get; set; } // public string? DmGreetMessageText { get; set; }
public int BoostMessageDeleteAfter { get; set; } //
public ulong BoostMessageChannelId { get; set; } // public bool SendChannelGreetMessage { get; set; }
// public string? ChannelGreetMessageText { get; set; }
public static GreetSettings Create(GuildConfig g) //
=> new() // public bool SendChannelByeMessage { get; set; }
{ // public string? ChannelByeMessageText { get; set; }
AutoDeleteByeMessagesTimer = g.AutoDeleteByeMessagesTimer, //
AutoDeleteGreetMessagesTimer = g.AutoDeleteGreetMessagesTimer, // public bool SendBoostMessage { get; set; }
GreetMessageChannelId = g.GreetMessageChannelId, // public string? BoostMessage { get; set; }
ByeMessageChannelId = g.ByeMessageChannelId, // public int BoostMessageDeleteAfter { get; set; }
SendDmGreetMessage = g.SendDmGreetMessage, // public ulong BoostMessageChannelId { get; set; }
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
};
} }

View file

@ -243,14 +243,16 @@ public class UserPunishService : IEService, IReadyExecutor
public async Task CheckAllWarnExpiresAsync() public async Task CheckAllWarnExpiresAsync()
{ {
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var cleared = await uow.Set<Warning>()
.Where(x => uow.Set<GuildConfig>() var cleared = await uow.GetTable<Warning>()
.Any(y => y.GuildId == x.GuildId .Where(x => uow.GetTable<GuildConfig>()
.Count(y => y.GuildId == x.GuildId
&& y.WarnExpireHours > 0 && y.WarnExpireHours > 0
&& y.WarnExpireAction == WarnExpireAction.Clear) && y.WarnExpireAction == WarnExpireAction.Clear)
> 0
&& x.Forgiven == false && x.Forgiven == false
&& x.DateAdded && x.DateAdded
< DateTime.UtcNow.AddHours(-uow.Set<GuildConfig>() < DateTime.UtcNow.AddHours(-uow.GetTable<GuildConfig>()
.Where(y => x.GuildId == y.GuildId) .Where(y => x.GuildId == y.GuildId)
.Select(y => y.WarnExpireHours) .Select(y => y.WarnExpireHours)
.First())) .First()))
@ -260,13 +262,14 @@ public class UserPunishService : IEService, IReadyExecutor
ForgivenBy = "expiry" ForgivenBy = "expiry"
}); });
var deleted = await uow.Set<Warning>() var deleted = await uow.GetTable<Warning>()
.Where(x => uow.Set<GuildConfig>() .Where(x => uow.GetTable<GuildConfig>()
.Any(y => y.GuildId == x.GuildId .Count(y => y.GuildId == x.GuildId
&& y.WarnExpireHours > 0 && y.WarnExpireHours > 0
&& y.WarnExpireAction == WarnExpireAction.Delete) && y.WarnExpireAction == WarnExpireAction.Delete)
> 0
&& x.DateAdded && x.DateAdded
< DateTime.UtcNow.AddHours(-uow.Set<GuildConfig>() < DateTime.UtcNow.AddHours(-uow.GetTable<GuildConfig>()
.Where(y => x.GuildId == y.GuildId) .Where(y => x.GuildId == y.GuildId)
.Select(y => y.WarnExpireHours) .Select(y => y.WarnExpireHours)
.First())) .First()))
@ -278,8 +281,6 @@ public class UserPunishService : IEService, IReadyExecutor
cleared, cleared,
deleted); deleted);
} }
await uow.SaveChangesAsync();
} }
public async Task CheckWarnExpiresAsync(ulong guildId) public async Task CheckWarnExpiresAsync(ulong guildId)

View file

@ -36,7 +36,7 @@ public static class EllieExpressionExtensions
var repCtx = new ReplacementContext(client: client, var repCtx = new ReplacementContext(client: client,
guild: (ctx.Channel as ITextChannel)?.Guild as SocketGuild, guild: (ctx.Channel as ITextChannel)?.Guild as SocketGuild,
channel: ctx.Channel, channel: ctx.Channel,
users: ctx.Author user: ctx.Author
) )
.WithOverride("%target%", .WithOverride("%target%",
() => canMentionEveryone () => canMentionEveryone

View file

@ -31,7 +31,7 @@ public class HelpService : IExecNoCommand, IEService
return; return;
} }
var repCtx = new ReplacementContext(guild: guild, channel: msg.Channel, users: msg.Author) var repCtx = new ReplacementContext(guild: guild, channel: msg.Channel, user: msg.Author)
.WithOverride("%prefix%", () => _bss.Data.Prefix) .WithOverride("%prefix%", () => _bss.Data.Prefix)
.WithOverride("%bot.prefix%", () => _bss.Data.Prefix); .WithOverride("%bot.prefix%", () => _bss.Data.Prefix);

View file

@ -262,7 +262,7 @@ public sealed class RepeaterService : IReadyExecutor, IEService
var repCtx = new ReplacementContext(client: _client, var repCtx = new ReplacementContext(client: _client,
guild: guild, guild: guild,
channel: channel, channel: channel,
users: guild.CurrentUser); user: guild.CurrentUser);
try try
{ {

View file

@ -1,6 +1,5 @@
#nullable disable #nullable disable
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using EllieBot.Common.Yml; using EllieBot.Common.Yml;
namespace EllieBot.Services; namespace EllieBot.Services;
@ -22,7 +21,6 @@ public sealed class BotCredsProvider : IBotCredsProvider
private readonly object _reloadLock = new(); private readonly object _reloadLock = new();
private readonly IDisposable _changeToken;
public BotCredsProvider(int? totalShards = null, string credPath = null) public BotCredsProvider(int? totalShards = null, string credPath = null)
{ {
@ -70,7 +68,6 @@ public sealed class BotCredsProvider : IBotCredsProvider
Console.WriteLine(ex.ToString()); Console.WriteLine(ex.ToString());
} }
_changeToken = ChangeToken.OnChange(() => _config.GetReloadToken(), Reload);
Reload(); Reload();
} }
@ -141,6 +138,7 @@ public sealed class BotCredsProvider : IBotCredsProvider
{ {
creds.BotCache = BotCacheImplemenation.Redis; creds.BotCache = BotCacheImplemenation.Redis;
} }
if (creds.Version <= 6) if (creds.Version <= 6)
{ {
creds.Version = 7; creds.Version = 7;

View file

@ -72,6 +72,22 @@ public static class EnumerableExtensions
where TKey : notnull where TKey : notnull
=> new(dict); => new(dict);
/// <summary>
/// Initializes a new instance of the <see cref="ConcurrentDictionary{TKey,TValue}" /> class
/// that contains elements copied from the specified <see cref="IEnumerable{T}" />
/// has the default concurrency level, has the default initial capacity,
/// and uses the default comparer for the key type.
/// </summary>
/// <param name="dict">
/// The <see cref="IEnumerable{T}" /> whose elements are copied to the new
/// <see cref="ConcurrentDictionary{TKey,TValue}" />.
/// </param>
/// <returns>A new instance of the <see cref="ConcurrentDictionary{TKey,TValue}" /> class</returns>
public static ConcurrentHashSet<TValue> ToConcurrentSet<TValue>(
this IReadOnlyCollection<TValue> dict)
where TValue : notnull
=> new(dict);
public static IndexedCollection<T> ToIndexed<T>(this IEnumerable<T> enumerable) public static IndexedCollection<T> ToIndexed<T>(this IEnumerable<T> enumerable)
where T : class, IIndexed where T : class, IIndexed
=> new(enumerable); => new(enumerable);

View file

@ -84,19 +84,6 @@ public sealed partial class BotConfig : ICloneable<BotConfig>
[Comment("""Which string will be used to recognize the commands""")] [Comment("""Which string will be used to recognize the commands""")]
public string Prefix { get; set; } public string Prefix { get; set; }
[Comment("""
Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
1st user who joins will get greeted immediately
If more users join within the next 5 seconds, they will be greeted in groups of 5.
This will cause %user.mention% and other placeholders to be replaced with multiple users.
Keep in mind this might break some of your embeds - for example if you have %user.avatar% in the thumbnail,
it will become invalid, as it will resolve to a list of avatars of grouped users.
note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited,
and (slightly) reduce the greet spam in those servers.
""")]
public bool GroupGreets { get; set; }
[Comment(""" [Comment("""
Whether the bot will rotate through all specified statuses. Whether the bot will rotate through all specified statuses.
This setting can be changed via .ropl command. This setting can be changed via .ropl command.
@ -144,7 +131,6 @@ public sealed partial class BotConfig : ICloneable<BotConfig>
Blocked = blocked; Blocked = blocked;
Prefix = "."; Prefix = ".";
RotateStatuses = false; RotateStatuses = false;
GroupGreets = false;
DmHelpTextKeywords = DmHelpTextKeywords =
[ [
"help", "help",

View file

@ -1,5 +1,6 @@
#nullable disable #nullable disable
using LinqToDB; using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using System.Linq.Expressions; using System.Linq.Expressions;
namespace EllieBot.Common; namespace EllieBot.Common;

View file

@ -7,7 +7,7 @@ public sealed class ReplacementContext
public DiscordSocketClient? Client { get; } public DiscordSocketClient? Client { get; }
public IGuild? Guild { get; } public IGuild? Guild { get; }
public IMessageChannel? Channel { get; } public IMessageChannel? Channel { get; }
public IUser[]? Users { get; } public IUser? User { get; }
private readonly List<ReplacementInfo> _overrides = new(); private readonly List<ReplacementInfo> _overrides = new();
private readonly HashSet<string> _tokens = new(); private readonly HashSet<string> _tokens = new();
@ -21,7 +21,8 @@ public sealed class ReplacementContext
public IReadOnlyList<RegexReplacementInfo> RegexOverrides public IReadOnlyList<RegexReplacementInfo> RegexOverrides
=> _regexOverrides.AsReadOnly(); => _regexOverrides.AsReadOnly();
public ReplacementContext(ICommandContext cmdContext) : this(cmdContext.Client as DiscordSocketClient, public ReplacementContext(ICommandContext cmdContext)
: this(cmdContext.Client as DiscordSocketClient,
cmdContext.Guild, cmdContext.Guild,
cmdContext.Channel, cmdContext.Channel,
cmdContext.User) cmdContext.User)
@ -32,12 +33,12 @@ public sealed class ReplacementContext
DiscordSocketClient? client = null, DiscordSocketClient? client = null,
IGuild? guild = null, IGuild? guild = null,
IMessageChannel? channel = null, IMessageChannel? channel = null,
params IUser[]? users) IUser? user = null)
{ {
Client = client; Client = client;
Guild = guild; Guild = guild;
Channel = channel; Channel = channel;
Users = users; User = user;
} }
public ReplacementContext WithOverride(string key, Func<ValueTask<string>> repFactory) public ReplacementContext WithOverride(string key, Func<ValueTask<string>> repFactory)

View file

@ -40,8 +40,8 @@ public sealed class ReplacementService : IReplacementService, IEService
if (repCtx.Guild is not null) if (repCtx.Guild is not null)
obj.Add(repCtx.Guild); obj.Add(repCtx.Guild);
if (repCtx.Users is not null) if (repCtx.User is not null)
obj.Add(repCtx.Users); obj.Add(repCtx.User);
if (repCtx.Channel is not null) if (repCtx.Channel is not null)
obj.Add(repCtx.Channel); obj.Add(repCtx.Channel);
@ -86,9 +86,9 @@ public sealed class ReplacementService : IReplacementService, IEService
objs.Add(repCtx.Channel); objs.Add(repCtx.Channel);
} }
if (repCtx.Users is not null) if (repCtx.User is not null)
{ {
objs.Add(repCtx.Users); objs.Add(repCtx.User);
} }
if (repCtx.Guild is not null) if (repCtx.Guild is not null)
@ -117,9 +117,9 @@ public sealed class ReplacementService : IReplacementService, IEService
objs.Add(repCtx.Channel); objs.Add(repCtx.Channel);
} }
if (repCtx.Users is not null) if (repCtx.User is not null)
{ {
objs.Add(repCtx.Users); objs.Add(repCtx.User);
} }
if (repCtx.Guild is not null) if (repCtx.Guild is not null)

View file

@ -1623,12 +1623,12 @@ gencurlist:
- page: - page:
desc: "The current page number for pagination." desc: "The current page number for pagination."
choose: choose:
desc: Chooses a thing from a list of things desc: Chooses a thing from a list of things. Seperate items with a semicolon ;
ex: ex:
- Get up;Sleep;Sleep more - Get up;Sleep;Sleep more
params: params:
- list: - list:
desc: "The type of items in the collection being searched." desc: "The items separated by ;"
rps: rps:
desc: |- desc: |-
Play a game of Rocket-Paperclip-Scissors with Ellie. Play a game of Rocket-Paperclip-Scissors with Ellie.